Master API security and authentication patterns. Learn JWT tokens, API key management, OAuth flows, and secure API integrations.
API security is paramount in modern web development. Authentication ensures that only authorized users and applications can access protected resources, while proper security practices protect both user data and your application from malicious attacks.
Different APIs use various authentication methods depending on their security requirements and use cases.
1. API Keys:
Simple authentication using a unique identifier
1// Basic API key authentication
2async function fetchWithAPIKey(url, apiKey) {
3 const response = await fetch(url, {
4 headers: {
5 'X-API-Key': apiKey,
6 'Content-Type': 'application/json'
7 }
8 });
9
10 if (!response.ok) {
11 if (response.status === 401) {
12 throw new Error('Invalid API key');
13 }
14 throw new Error('Request failed: ' + response.status);
15 }
16
17 return await response.json();
18}
19
20// Usage
21const data = await fetchWithAPIKey(
22 'https://api.example.com/users',
23 'your-api-key-here'
24);2. Bearer Tokens (JWT):
JSON Web Tokens for stateless authentication
1// JWT token authentication
2async function fetchWithJWT(url, token) {
3 const response = await fetch(url, {
4 headers: {
5 'Authorization': 'Bearer ' + token,
6 'Content-Type': 'application/json'
7 }
8 });
9
10 if (!response.ok) {
11 if (response.status === 401) {
12 throw new Error('Invalid or expired token');
13 }
14 throw new Error('Request failed');
15 }
16
17 return await response.json();
18}
19
20// Login to get JWT token
21async function login(username, password) {
22 const response = await fetch('/api/auth/login', {
23 method: 'POST',
24 headers: {
25 'Content-Type': 'application/json'
26 },
27 body: JSON.stringify({ username, password })
28 });
29
30 if (!response.ok) {
31 throw new Error('Login failed');
32 }
33
34 const data = await response.json();
35 return data.token;
36}3. Basic Authentication:
Username and password encoded in base64
1// Basic authentication
2async function fetchWithBasicAuth(url, username, password) {
3 const credentials = btoa(username + ':' + password);
4
5 const response = await fetch(url, {
6 headers: {
7 'Authorization': 'Basic ' + credentials,
8 'Content-Type': 'application/json'
9 }
10 });
11
12 return await response.json();
13}JSON Web Tokens (JWT) are widely used for stateless authentication. Proper token management is crucial for security.
Token Storage:
1class TokenManager {
2 constructor() {
3 this.token = null;
4 this.refreshToken = null;
5 }
6
7 // Store tokens securely
8 setTokens(accessToken, refreshToken) {
9 this.token = accessToken;
10 this.refreshToken = refreshToken;
11
12 // Store in localStorage (consider security implications)
13 localStorage.setItem('accessToken', accessToken);
14 if (refreshToken) {
15 localStorage.setItem('refreshToken', refreshToken);
16 }
17 }
18
19 // Get current token
20 getToken() {
21 if (!this.token) {
22 this.token = localStorage.getItem('accessToken');
23 }
24 return this.token;
25 }
26
27 // Check if token is expired
28 isTokenExpired(token = this.getToken()) {
29 if (!token) return true;
30
31 try {
32 const payload = JSON.parse(atob(token.split('.')[1]));
33 const currentTime = Date.now() / 1000;
34 return payload.exp < currentTime;
35 } catch {
36 return true;
37 }
38 }
39
40 // Clear tokens
41 clearTokens() {
42 this.token = null;
43 this.refreshToken = null;
44 localStorage.removeItem('accessToken');
45 localStorage.removeItem('refreshToken');
46 }
47}
48
49const tokenManager = new TokenManager();Automatic Token Refresh:
1class AuthenticatedAPIClient {
2 constructor(baseURL) {
3 this.baseURL = baseURL;
4 this.tokenManager = new TokenManager();
5 }
6
7 async request(endpoint, options = {}) {
8 let token = this.tokenManager.getToken();
9
10 // Refresh token if expired
11 if (this.tokenManager.isTokenExpired(token)) {
12 token = await this.refreshAccessToken();
13 }
14
15 const response = await fetch(this.baseURL + endpoint, {
16 ...options,
17 headers: {
18 'Authorization': 'Bearer ' + token,
19 'Content-Type': 'application/json',
20 ...options.headers
21 }
22 });
23
24 // Handle token expiration
25 if (response.status === 401) {
26 try {
27 token = await this.refreshAccessToken();
28
29 // Retry the request with new token
30 return await fetch(this.baseURL + endpoint, {
31 ...options,
32 headers: {
33 'Authorization': 'Bearer ' + token,
34 'Content-Type': 'application/json',
35 ...options.headers
36 }
37 });
38 } catch (refreshError) {
39 // Refresh failed, redirect to login
40 this.handleAuthenticationFailure();
41 throw new Error('Authentication failed');
42 }
43 }
44
45 return response;
46 }
47
48 async refreshAccessToken() {
49 const refreshToken = localStorage.getItem('refreshToken');
50 if (!refreshToken) {
51 throw new Error('No refresh token available');
52 }
53
54 const response = await fetch(this.baseURL + '/auth/refresh', {
55 method: 'POST',
56 headers: {
57 'Content-Type': 'application/json'
58 },
59 body: JSON.stringify({ refreshToken })
60 });
61
62 if (!response.ok) {
63 throw new Error('Token refresh failed');
64 }
65
66 const data = await response.json();
67 this.tokenManager.setTokens(data.accessToken, data.refreshToken);
68 return data.accessToken;
69 }
70
71 handleAuthenticationFailure() {
72 this.tokenManager.clearTokens();
73 // Redirect to login page
74 window.location.href = '/login';
75 }
76}Implementing proper security practices is essential for protecting your application and users.
Environment Variables for API Keys:
1// Never hardcode API keys in your source code
2// Bad:
3const API_KEY = 'sk-1234567890abcdef';
4
5// Good: Use environment variables
6const API_KEY = process.env.REACT_APP_API_KEY;
7const API_SECRET = process.env.API_SECRET; // Server-side onlySecure Headers:
1class SecureAPIClient {
2 constructor(apiKey) {
3 this.apiKey = apiKey;
4 }
5
6 async request(url, options = {}) {
7 const secureHeaders = {
8 'Content-Type': 'application/json',
9 'X-Requested-With': 'XMLHttpRequest',
10 'X-API-Key': this.apiKey,
11 // Add CSRF protection if needed
12 'X-CSRF-Token': this.getCSRFToken(),
13 ...options.headers
14 };
15
16 return await fetch(url, {
17 ...options,
18 headers: secureHeaders,
19 // Include credentials for CORS requests
20 credentials: 'include'
21 });
22 }
23
24 getCSRFToken() {
25 // Get CSRF token from meta tag or cookie
26 const token = document.querySelector('meta[name="csrf-token"]');
27 return token ? token.getAttribute('content') : '';
28 }
29}OAuth 2.0 Flow:
1class OAuthClient {
2 constructor(clientId, redirectUri) {
3 this.clientId = clientId;
4 this.redirectUri = redirectUri;
5 }
6
7 // Redirect to OAuth provider
8 initiateLogin(provider = 'google') {
9 const authUrls = {
10 google: 'https://accounts.google.com/oauth/authorize',
11 github: 'https://github.com/login/oauth/authorize',
12 facebook: 'https://www.facebook.com/v18.0/dialog/oauth'
13 };
14
15 const params = new URLSearchParams({
16 client_id: this.clientId,
17 redirect_uri: this.redirectUri,
18 response_type: 'code',
19 scope: 'openid email profile',
20 state: this.generateState()
21 });
22
23 // Store state for security verification
24 sessionStorage.setItem('oauth_state', params.get('state'));
25
26 window.location.href = authUrls[provider] + '?' + params.toString();
27 }
28
29 // Handle OAuth callback
30 async handleCallback() {
31 const urlParams = new URLSearchParams(window.location.search);
32 const code = urlParams.get('code');
33 const state = urlParams.get('state');
34 const storedState = sessionStorage.getItem('oauth_state');
35
36 // Verify state parameter (CSRF protection)
37 if (state !== storedState) {
38 throw new Error('Invalid state parameter');
39 }
40
41 // Exchange code for access token
42 const response = await fetch('/api/auth/oauth/callback', {
43 method: 'POST',
44 headers: {
45 'Content-Type': 'application/json'
46 },
47 body: JSON.stringify({ code, state })
48 });
49
50 if (!response.ok) {
51 throw new Error('OAuth authentication failed');
52 }
53
54 const data = await response.json();
55
56 // Store tokens
57 const tokenManager = new TokenManager();
58 tokenManager.setTokens(data.accessToken, data.refreshToken);
59
60 return data.user;
61 }
62
63 generateState() {
64 return Math.random().toString(36).substring(2, 15) +
65 Math.random().toString(36).substring(2, 15);
66 }
67}
68
69// Usage
70const oauthClient = new OAuthClient('your-client-id', 'http://localhost:3000/callback');
71
72// Initiate OAuth login
73document.getElementById('google-login').addEventListener('click', () => {
74 oauthClient.initiateLogin('google');
75});
76
77// Handle OAuth callback
78if (window.location.pathname === '/callback') {
79 oauthClient.handleCallback()
80 .then(user => {
81 console.log('Logged in user:', user);
82 window.location.href = '/dashboard';
83 })
84 .catch(error => {
85 console.error('OAuth error:', error);
86 window.location.href = '/login?error=oauth_failed';
87 });
88}Security Best Practices:
1// Security checklist for API authentication
2const securityBestPractices = {
3 storage: {
4 // Store sensitive tokens in httpOnly cookies when possible
5 useHttpOnlyCookies: true,
6 // Avoid localStorage for sensitive data
7 avoidLocalStorageForTokens: true,
8 // Use secure, encrypted storage
9 useSecureStorage: true
10 },
11
12 transmission: {
13 // Always use HTTPS in production
14 useHTTPS: true,
15 // Validate SSL certificates
16 validateSSL: true,
17 // Use secure headers
18 useSecureHeaders: true
19 },
20
21 tokenManagement: {
22 // Implement token expiration
23 useTokenExpiration: true,
24 // Implement token refresh
25 useTokenRefresh: true,
26 // Clear tokens on logout
27 clearTokensOnLogout: true
28 },
29
30 validation: {
31 // Validate tokens on server
32 validateTokensServerSide: true,
33 // Check token signatures
34 verifyTokenSignatures: true,
35 // Implement rate limiting
36 useRateLimiting: true
37 }
38};
39
40// Example secure implementation
41class SecureAuthManager {
42 constructor() {
43 this.tokenEndpoint = '/api/auth/token';
44 this.refreshEndpoint = '/api/auth/refresh';
45 }
46
47 async secureLogin(credentials) {
48 try {
49 const response = await fetch(this.tokenEndpoint, {
50 method: 'POST',
51 headers: {
52 'Content-Type': 'application/json',
53 'X-Requested-With': 'XMLHttpRequest'
54 },
55 body: JSON.stringify(credentials),
56 credentials: 'include' // Include httpOnly cookies
57 });
58
59 if (!response.ok) {
60 throw new Error('Authentication failed');
61 }
62
63 const data = await response.json();
64
65 // Token is stored in httpOnly cookie by server
66 // Only store non-sensitive user info locally
67 localStorage.setItem('user', JSON.stringify({
68 id: data.user.id,
69 name: data.user.name,
70 email: data.user.email
71 }));
72
73 return data.user;
74 } catch (error) {
75 console.error('Login error:', error);
76 throw error;
77 }
78 }
79
80 async secureLogout() {
81 try {
82 await fetch('/api/auth/logout', {
83 method: 'POST',
84 credentials: 'include'
85 });
86 } finally {
87 // Clear all local data
88 localStorage.clear();
89 sessionStorage.clear();
90 }
91 }
92}Your progress is saved in this browser, on this device only.