API Authentication
MyAppAPI supports multiple authentication methods to meet the needs of different integration scenarios. This guide explains how to authenticate your requests using API keys, OAuth 2.0, and JWT tokens, along with best practices for secure implementation.
Authentication Overview
All requests to the MyAppAPI require authentication. Unauthenticated requests will return a 401 Unauthorized
error. We offer three primary authentication methods to support different use cases:
Authentication Method | Best For | Security Level | Implementation Complexity |
---|---|---|---|
API Keys | Server-to-server integrations, internal applications | Medium | Low |
OAuth 2.0 | User-facing applications, third-party integrations | High | Medium-High |
JWT Tokens | Mobile apps, SPAs, microservices | High | Medium |
Regardless of the authentication method you choose, all API requests should be made over HTTPS to ensure secure transmission of credentials. Requests made over plain HTTP will be rejected.
API Key Authentication
API keys provide a simple way to authenticate requests to our API. They are best suited for server-to-server integrations where the key can be stored securely.
Obtaining an API Key
To obtain an API key:
- Log in to your MyAppAPI Dashboard.
- Navigate to the API Keys section.
- Click Create API Key and provide a descriptive name for the key.
- Copy and securely store your API key. For security reasons, we only show the complete key once upon creation.
Important: Keep your API key secure! Never expose it in client-side code or commit it to public repositories. Treat it like a password.
Using API Keys
There are two ways to include your API key in requests:
1. Authorization Header (Recommended)
Include your API key in the Authorization
header with the Bearer
prefix:
GET /v1/resources HTTP/1.1
Host: api.myappapi.com
Authorization: Bearer YOUR_API_KEY
2. Query Parameter
You can also pass your API key as a query parameter. This method is less secure and should be used only when you cannot set HTTP headers:
GET /v1/resources?api_key=YOUR_API_KEY HTTP/1.1
Host: api.myappapi.com
API Key Best Practices
- Use environment variables to store API keys in your application.
- Implement proper API key rotation practices.
- Create separate API keys for different environments (development, staging, production).
- Set appropriate permissions for each API key to follow the principle of least privilege.
- Use dedicated keys for different integrations to simplify tracking and revocation if needed.
API Key Examples
curl -X GET "https://api.myappapi.com/v1/resources" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
// Using fetch
const apiKey = process.env.MY_APP_API_KEY; // Store keys securely
fetch('https://api.myappapi.com/v1/resources', {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
import requests
import os
# Store keys securely
api_key = os.environ.get('MY_APP_API_KEY')
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.myappapi.com/v1/resources',
headers=headers
)
if response.status_code == 200:
data = response.json()
print(data)
else:
print(f"Request failed with status code: {response.status_code}")
require 'net/http'
require 'uri'
require 'json'
# Store keys securely
api_key = ENV['MY_APP_API_KEY']
uri = URI('https://api.myappapi.com/v1/resources')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{api_key}"
request['Content-Type'] = 'application/json'
response = http.request(request)
if response.code.to_i == 200
data = JSON.parse(response.body)
puts data
else
puts "Request failed with status code: #{response.code}"
end
<?php
// Store keys securely
$apiKey = getenv('MY_APP_API_KEY');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.myappapi.com/v1/resources');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
$data = json_decode($response, true);
print_r($data);
} else {
echo "Request failed with status code: " . $httpCode;
}
?>
OAuth 2.0 Authentication
OAuth 2.0 is an authorization framework that enables third-party applications to obtain limited access to a user's account. It's ideal for applications that need to access data on behalf of users without handling their credentials.
OAuth 2.0 Flow Overview
MyAppAPI supports the following OAuth 2.0 flows:
- Authorization Code Flow - For server-side applications
- Authorization Code Flow with PKCE - For mobile and single-page applications
- Client Credentials Flow - For service-to-service authentication
Setting Up OAuth 2.0
To use OAuth 2.0 with MyAppAPI:
- Log in to your MyAppAPI Dashboard.
- Navigate to the OAuth Applications section.
- Click Create Application and provide the required information:
- Application name
- Redirect URI(s)
- Allowed scopes
- Application type
- After creating the application, you'll receive a Client ID and Client Secret.
Note: Store your Client Secret securely and never expose it in client-side code.
Authorization Code Flow with PKCE
For mobile and single-page applications, we recommend using the Authorization Code Flow with PKCE (Proof Key for Code Exchange). This adds an extra layer of security by preventing authorization code interception attacks.
Step 1: Generate a Code Verifier and Challenge
// JavaScript example of code verifier and challenge generation
function generateCodeVerifier() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return base64URLEncode(array);
}
function base64URLEncode(buffer) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await window.crypto.subtle.digest('SHA-256', data);
return base64URLEncode(digest);
}
// Generate and store code verifier
const codeVerifier = generateCodeVerifier();
localStorage.setItem('code_verifier', codeVerifier);
// Generate code challenge
generateCodeChallenge(codeVerifier).then(codeChallenge => {
// Use code challenge in authorization request
console.log(codeChallenge);
});
Step 2: Redirect User to Authorization URL
// Redirect the user to the authorization URL
function redirectToAuthUrl(codeChallenge) {
const authUrl = new URL('https://auth.myappapi.com/oauth/authorize');
authUrl.searchParams.append('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.append('redirect_uri', 'https://your-app.com/callback');
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'read write');
authUrl.searchParams.append('code_challenge', codeChallenge);
authUrl.searchParams.append('code_challenge_method', 'S256');
authUrl.searchParams.append('state', generateRandomState());
window.location = authUrl.toString();
}
function generateRandomState() {
const array = new Uint8Array(16);
window.crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
Step 3: Exchange Authorization Code for Tokens
// Handle the callback and exchange code for tokens
async function handleAuthCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
// Verify state matches original state sent
if (state !== storedState) {
throw new Error('State mismatch - possible CSRF attack');
}
// Retrieve the code verifier from storage
const codeVerifier = localStorage.getItem('code_verifier');
const tokenResponse = await fetch('https://auth.myappapi.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'YOUR_CLIENT_ID',
code_verifier: codeVerifier,
code: code,
redirect_uri: 'https://your-app.com/callback'
})
});
if (!tokenResponse.ok) {
throw new Error('Failed to exchange authorization code for tokens');
}
const tokens = await tokenResponse.json();
// Store tokens securely
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
localStorage.setItem('expires_at', Date.now() + tokens.expires_in * 1000);
return tokens;
}
Step 4: Use Access Token for API Requests
// Use the access token to make API requests
async function makeApiRequest(endpoint) {
// Check if token has expired
const expiresAt = localStorage.getItem('expires_at');
if (Date.now() > expiresAt) {
await refreshAccessToken();
}
const accessToken = localStorage.getItem('access_token');
const response = await fetch(`https://api.myappapi.com/v1/${endpoint}`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
return response.json();
}
// Refresh access token using refresh token
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refresh_token');
const response = await fetch('https://auth.myappapi.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: 'YOUR_CLIENT_ID',
refresh_token: refreshToken
})
});
if (!response.ok) {
// If refresh fails, redirect to login
window.location = '/login';
return;
}
const tokens = await response.json();
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token || refreshToken);
localStorage.setItem('expires_at', Date.now() + tokens.expires_in * 1000);
}
Authorization Code Flow (for Web Apps)
For server-side web applications, you can use the standard Authorization Code Flow. This flow is similar to the PKCE flow but doesn't require code challenges. Instead, it uses a client secret that must be kept securely on your server.
Note: The client secret should never be exposed to the client side. Keep it secure on your server.
Example Server-Side Implementation (Node.js/Express)
const express = require('express');
const axios = require('axios');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
app.use(session({
secret: 'your-session-secret',
resave: false,
saveUninitialized: true
}));
// Config
const config = {
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
redirect_uri: 'http://localhost:3000/callback',
auth_url: 'https://auth.myappapi.com/oauth/authorize',
token_url: 'https://auth.myappapi.com/oauth/token',
api_url: 'https://api.myappapi.com/v1'
};
app.get('/login', (req, res) => {
// Generate and store state
const state = crypto.randomBytes(16).toString('hex');
req.session.oauth_state = state;
// Redirect to authorization endpoint
const authUrl = new URL(config.auth_url);
authUrl.searchParams.append('client_id', config.client_id);
authUrl.searchParams.append('redirect_uri', config.redirect_uri);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'read write');
authUrl.searchParams.append('state', state);
res.redirect(authUrl.toString());
});
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state
if (state !== req.session.oauth_state) {
return res.status(403).send('State mismatch - possible CSRF attack');
}
try {
// Exchange code for tokens
const tokenResponse = await axios.post(config.token_url,
new URLSearchParams({
grant_type: 'authorization_code',
client_id: config.client_id,
client_secret: config.client_secret,
code: code,
redirect_uri: config.redirect_uri
}).toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
// Store tokens in session (or better, in a secure token store)
req.session.tokens = tokenResponse.data;
req.session.tokens.expires_at = Date.now() + tokenResponse.data.expires_in * 1000;
res.redirect('/dashboard');
} catch (error) {
res.status(500).send('Error obtaining access token');
}
});
// API request middleware to check token expiration
async function checkToken(req, res, next) {
if (!req.session.tokens) {
return res.redirect('/login');
}
// Check if token has expired
if (Date.now() > req.session.tokens.expires_at) {
try {
// Refresh the token
const refreshResponse = await axios.post(config.token_url,
new URLSearchParams({
grant_type: 'refresh_token',
client_id: config.client_id,
client_secret: config.client_secret,
refresh_token: req.session.tokens.refresh_token
}).toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
// Update tokens in session
req.session.tokens = refreshResponse.data;
req.session.tokens.expires_at = Date.now() + refreshResponse.data.expires_in * 1000;
} catch (error) {
return res.redirect('/login');
}
}
next();
}
// Protected API route
app.get('/api/resources', checkToken, async (req, res) => {
try {
const apiResponse = await axios.get(`${config.api_url}/resources`, {
headers: {
'Authorization': `Bearer ${req.session.tokens.access_token}`
}
});
res.json(apiResponse.data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch resources' });
}
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Client Credentials Flow
The Client Credentials flow is used for server-to-server authentication when you're accessing your own resources, not user resources. This flow is simpler than the others as it doesn't involve user interaction.
// Node.js example of Client Credentials flow
const axios = require('axios');
const qs = require('querystring');
async function getClientCredentialsToken() {
try {
const tokenResponse = await axios.post('https://auth.myappapi.com/oauth/token',
qs.stringify({
grant_type: 'client_credentials',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
scope: 'read write'
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return tokenResponse.data.access_token;
} catch (error) {
console.error('Error obtaining access token:', error);
throw error;
}
}
async function makeApiRequest() {
try {
const accessToken = await getClientCredentialsToken();
const apiResponse = await axios.get('https://api.myappapi.com/v1/resources', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
return apiResponse.data;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
JWT Authentication
JSON Web Tokens (JWT) provide a compact and self-contained way for securely transmitting information between parties. MyAppAPI supports JWT as a modern approach to authentication, particularly well-suited for microservices and mobile applications.
JWT Authentication Flow
The JWT authentication flow typically involves:
- Client authenticates with username and password (or other credentials)
- Server validates credentials and issues a signed JWT
- Client includes the JWT in subsequent API requests
- Server validates the JWT signature and grants access
JWT Structure
A JWT consists of three parts, separated by dots:
- Header: Specifies the token type and signing algorithm
- Payload: Contains the claims (user data and metadata)
- Signature: Ensures the token hasn't been tampered with
JWT Claims
MyAppAPI JWTs include the following standard claims:
iss
(Issuer): Identifies the token issuersub
(Subject): Identifies the subject of the token (typically user ID)aud
(Audience): Identifies the intended recipientsexp
(Expiration): Timestamp when the token expiresiat
(Issued At): Timestamp when the token was issuedjti
(JWT ID): Unique identifier for the token
Additionally, we include custom claims:
scope
: Space-separated list of permissions granted to the tokenclient_id
: Identifier of the client application
Obtaining JWTs
JWTs are typically obtained through our OAuth 2.0 endpoints. When you request an access token using any of the OAuth flows, you'll receive a JWT as the access token.
Using JWTs with API Requests
To use a JWT with API requests, include it in the Authorization
header with the Bearer
prefix:
GET /v1/resources HTTP/1.1
Host: api.myappapi.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT Security Considerations
- Never store sensitive information in JWT claims as they are easily decoded (though not easily modified without the signing key).
- Always verify the signature on the server before trusting the claims.
- Set appropriate expiration times to limit the window of opportunity for token misuse.
- Store tokens securely on the client, preferably in memory rather than persistent storage.
- Implement token revocation strategies for cases where tokens need to be invalidated before expiration.
JWT Implementation Example
// React example using JWT authentication
import { useState, useEffect, createContext, useContext } from 'react';
// Create an Auth context
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [accessToken, setAccessToken] = useState(null);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check for existing token on mount
const token = localStorage.getItem('access_token');
if (token) {
// Verify token is not expired
const payload = parseJwt(token);
if (payload.exp * 1000 > Date.now()) {
setAccessToken(token);
setUser({
id: payload.sub,
name: payload.name,
email: payload.email,
});
} else {
// Token expired, clear it
localStorage.removeItem('access_token');
}
}
setLoading(false);
}, []);
// Parse JWT without validation (validation happens on the server)
const parseJwt = (token) => {
try {
return JSON.parse(atob(token.split('.')[1]));
} catch (e) {
return null;
}
};
// Login function
const login = async (email, password) => {
try {
const response = await fetch('https://auth.myappapi.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error('Login failed');
}
const data = await response.json();
const { access_token } = data;
// Store token and extract user info
localStorage.setItem('access_token', access_token);
setAccessToken(access_token);
const payload = parseJwt(access_token);
setUser({
id: payload.sub,
name: payload.name,
email: payload.email,
});
return true;
} catch (error) {
console.error('Login error:', error);
return false;
}
};
// Logout function
const logout = () => {
localStorage.removeItem('access_token');
setAccessToken(null);
setUser(null);
};
// API request function with JWT
const apiRequest = async (endpoint, options = {}) => {
if (!accessToken) {
throw new Error('Not authenticated');
}
// Parse token to check expiration
const payload = parseJwt(accessToken);
if (payload.exp * 1000 <= Date.now()) {
// Token expired, logout and prompt for new login
logout();
throw new Error('Session expired. Please login again.');
}
const config = {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
};
try {
const response = await fetch(`https://api.myappapi.com/v1/${endpoint}`, config);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || 'Request failed');
}
return response.json();
} catch (error) {
console.error('API request error:', error);
throw error;
}
};
return (
{children}
);
}
// Hook to use the auth context
export function useAuth() {
return useContext(AuthContext);
}
// Example usage in a component
function ResourcesList() {
const { apiRequest } = useAuth();
const [resources, setResources] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchResources() {
try {
const data = await apiRequest('resources');
setResources(data.data);
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
}
fetchResources();
}, [apiRequest]);
if (loading) return Loading...;
if (error) return Error: {error};
return (
Resources
{resources.map(resource => (
- {resource.name}
))}
);
}
Choosing an Authentication Method
Selecting the right authentication method depends on your use case and security requirements:
Use Case | Recommended Method | Rationale |
---|---|---|
Backend service or script | API Key or Client Credentials | Simplicity and direct service-to-service authentication |
Mobile application | OAuth 2.0 with PKCE | Security without exposing client secrets in the app |
Single-page application (SPA) | OAuth 2.0 with PKCE | Security without exposing client secrets in the browser |
Traditional web application | OAuth 2.0 Authorization Code Flow | Full security with client secret stored securely on the server |
Microservices architecture | JWT Authentication | Stateless authentication with standard token format |
Third-party application | OAuth 2.0 | Delegated authorization without sharing user credentials |
Security Best Practices
Regardless of which authentication method you choose, follow these security best practices:
Secure Storage of Credentials
- API Keys and Client Secrets: Store securely as environment variables or in a secure credentials store, never in source code.
- Access Tokens: In browser environments, prefer in-memory storage over localStorage or sessionStorage when possible.
- Refresh Tokens: Store with highest security as they have longer lifetimes; use HTTP-only cookies on web servers.
HTTPS Everywhere
Always use HTTPS for API requests and OAuth redirects. Never send credentials or tokens over plain HTTP.
Token Lifetimes
Use appropriate token lifetimes based on security needs:
- Short lifetimes (minutes to hours) for access tokens
- Longer lifetimes for refresh tokens with secure storage
- Regular rotation for API keys
Scope and Permissions
Apply the principle of least privilege by:
- Requesting only the permissions your application needs
- Creating different API keys with different permission levels for different functions
- Regularly auditing and updating permission scopes
Token Validation
When using JWTs, always validate:
- The token signature to ensure integrity
- The expiration time to ensure it's not expired
- The issuer to ensure it's from a trusted source
- The audience to ensure it's intended for your application
Security Headers
Implement security headers in your application:
- Content-Security-Policy (CSP)
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Strict-Transport-Security (HSTS)
Credential Rotation
Implement regular rotation of:
- API keys
- Client secrets
- Signing keys for JWTs
Incident Response Plan
Prepare for security incidents by:
- Creating a process for revoking compromised credentials
- Documenting steps to rotate all affected keys and tokens
- Setting up monitoring and alerts for suspicious authentication activity
Troubleshooting Authentication Issues
Common authentication problems and how to solve them:
Issue | Possible Causes | Solutions |
---|---|---|
401 Unauthorized |
|
|
403 Forbidden |
|
|
OAuth redirect errors |
|
|
Invalid token errors |
|
|
Debugging Authentication
When troubleshooting authentication issues:
- Examine the complete error response from the API, which often contains specific error details.
- Use developer tools to inspect network requests and ensure headers are being sent correctly.
- For JWT issues, decode the token at jwt.io to inspect claims (never paste production tokens with sensitive data).
- Check API logs in your MyAppAPI Dashboard for additional error context.
If you continue to experience authentication issues, please contact our support team with details about the problem, including request headers, error responses, and steps to reproduce the issue.