API Authentication Issues
What This Means
API authentication issues occur when your application cannot properly authenticate with external APIs or services. This prevents data exchange, breaks integrations, and can cause complete service disruptions. Authentication failures manifest as 401 Unauthorized, 403 Forbidden, or CORS errors in your application logs.
Impact on Your Business
Service Disruption:
- Integrations stop working
- Features become unavailable
- Data stops syncing
- User workflows break
- Third-party services inaccessible
Data Loss:
- Missing analytics data
- Incomplete transaction records
- Lost webhook notifications
- Sync gaps in databases
- Reporting inaccuracies
User Experience:
- Login failures
- Feature unavailability
- Error messages displayed
- Functionality degradation
- Customer support burden
Common Causes
Credential Issues:
- Expired OAuth tokens
- Invalid API keys
- Wrong environment credentials (dev keys in production)
- Revoked access permissions
- Incorrectly formatted credentials
Configuration Errors:
- Missing authorization headers
- Incorrect authentication scheme
- CORS misconfiguration
- Domain whitelist issues
- Callback URL mismatches
Rate Limiting:
- Exceeded request quotas
- Too many concurrent requests
- IP-based throttling
- Per-user limits reached
- Burst limit violations
How to Diagnose
Method 1: Test API Credentials Directly
Use curl or Postman to test authentication outside your application:
# Test API key authentication
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.example.com/v1/test
# Test with OAuth token
curl -H "Authorization: Bearer YOUR_OAUTH_TOKEN" \
https://api.example.com/v1/user
# Test basic authentication
curl -u username:password \
https://api.example.com/v1/endpoint
What to Look For:
- 200 OK = Authentication working
- 401 Unauthorized = Invalid credentials
- 403 Forbidden = Valid credentials, insufficient permissions
- 429 Too Many Requests = Rate limited
Method 2: Check Browser DevTools Network Tab
- Open browser DevTools (F12)
- Navigate to Network tab
- Filter by Fetch/XHR
- Trigger API request
- Click on request to see details
- Check "Headers" tab for:
- Request headers (Authorization, API-Key, etc.)
- Response status code
- Response headers
- CORS errors in console
What to Look For:
Request Headers:
Authorization: Bearer abc123xyz // Is token present and formatted correctly?
Response Headers:
HTTP/1.1 401 Unauthorized // Authentication failed
Access-Control-Allow-Origin: * // CORS configuration
Console Errors:
CORS policy: No 'Access-Control-Allow-Origin' header // CORS issue
Method 3: Review Server/Application Logs
Check your application logs for authentication errors:
# Common log patterns
[ERROR] API request failed: 401 Unauthorized
[WARN] OAuth token expired, refresh needed
[ERROR] CORS preflight request failed
[ERROR] Rate limit exceeded: 429 Too Many Requests
[ERROR] Invalid API key: abc123
Method 4: Test OAuth Token Flow
For OAuth integrations, test the complete flow:
- Initiate OAuth authorization
- User grants permissions
- Receive authorization code
- Exchange code for access token
- Use access token in requests
- Refresh token when expired
Check for:
- Redirect URI matches registered callback
- Correct scope permissions requested
- Token expiration handling
- Refresh token implementation
- Token storage security
Method 5: Verify CORS Configuration
Test CORS configuration using browser or curl:
# Send preflight OPTIONS request
curl -X OPTIONS \
-H "Origin: https://your-domain.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
https://api.example.com/v1/endpoint
# Check response headers
Access-Control-Allow-Origin: https://your-domain.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
General Fixes
Fix 1: Refresh Expired OAuth Tokens
Implement automatic token refresh to prevent expiration:
// Store tokens with expiration
const tokenData = {
accessToken: 'abc123xyz',
refreshToken: 'refresh789',
expiresAt: Date.now() + (3600 * 1000) // 1 hour from now
};
// Check if token is expired
function isTokenExpired() {
return Date.now() >= tokenData.expiresAt;
}
// Refresh token before making request
async function makeAuthenticatedRequest(url) {
// Check if token needs refresh
if (isTokenExpired()) {
await refreshAccessToken();
}
// Make request with current token
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${tokenData.accessToken}`
}
});
return response;
}
// Refresh access token
async function refreshAccessToken() {
const response = await fetch('https://api.example.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: tokenData.refreshToken,
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET'
})
});
const data = await response.json();
// Update stored tokens
tokenData.accessToken = data.access_token;
tokenData.refreshToken = data.refresh_token;
tokenData.expiresAt = Date.now() + (data.expires_in * 1000);
}
Fix 2: Verify and Update API Keys
Ensure API keys are valid and properly configured:
Check API key in provider dashboard:
- Login to API provider console
- Navigate to API keys or credentials section
- Verify key is active and not expired
- Check permissions and scopes
- Regenerate if compromised
Update environment variables:
# .env file API_KEY=your_actual_api_key_here API_SECRET=your_api_secret_here # Never commit .env to version control # Add .env to .gitignoreLoad credentials in application:
// Load from environment variables const apiKey = process.env.API_KEY; // Never hardcode credentials // ❌ const apiKey = 'abc123xyz'; // Make authenticated request fetch('https://api.example.com/v1/data', { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });Test different environments:
- Development: Use test/sandbox keys
- Staging: Use staging keys
- Production: Use production keys
- Never mix environment credentials
Fix 3: Configure CORS Headers
Fix CORS issues by configuring proper headers on your server:
Server-side (Node.js/Express):
const express = require('express');
const cors = require('cors');
const app = express();
// Allow all origins (development only)
app.use(cors());
// Or configure specific origins (production)
const corsOptions = {
origin: [
'https://your-domain.com',
'https://www.your-domain.com'
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // 24 hours
};
app.use(cors(corsOptions));
// Handle preflight requests
app.options('*', cors(corsOptions));
Server-side (PHP):
<?php
// Set CORS headers
header('Access-Control-Allow-Origin: https://your-domain.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
// Handle preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
?>
Client-side considerations:
// Include credentials in cross-origin requests
fetch('https://api.example.com/data', {
method: 'POST',
credentials: 'include', // Send cookies
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify(data)
});
Fix 4: Handle Rate Limiting
Implement retry logic with exponential backoff for rate limits:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch(url, options);
// Check for rate limiting
if (response.status === 429) {
// Get retry-after header (seconds)
const retryAfter = response.headers.get('Retry-After') || 60;
const waitTime = parseInt(retryAfter) * 1000;
console.log(`Rate limited. Waiting ${retryAfter}s before retry...`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, waitTime));
retries++;
continue;
}
// Return successful response
if (response.ok) {
return response;
}
// Other error, throw
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
} catch (error) {
console.error(`Request failed (attempt ${retries + 1}/${maxRetries}):`, error);
// If last retry, throw error
if (retries === maxRetries - 1) {
throw error;
}
// Exponential backoff: 1s, 2s, 4s, 8s...
const backoffTime = Math.pow(2, retries) * 1000;
await new Promise(resolve => setTimeout(resolve, backoffTime));
retries++;
}
}
}
// Usage
try {
const response = await fetchWithRetry('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const data = await response.json();
} catch (error) {
console.error('Request failed after retries:', error);
}
Fix 5: Implement Request Throttling
Prevent rate limiting by throttling your requests:
class APIClient {
constructor(rateLimit = 100, perSeconds = 60) {
this.rateLimit = rateLimit; // Max requests
this.perSeconds = perSeconds; // Per time period
this.requestQueue = [];
this.requestCount = 0;
this.windowStart = Date.now();
}
async request(url, options) {
// Wait if rate limit reached
await this.waitForRateLimit();
// Make request
const response = await fetch(url, options);
// Increment counter
this.requestCount++;
return response;
}
async waitForRateLimit() {
const now = Date.now();
const windowElapsed = now - this.windowStart;
// Reset window if period has passed
if (windowElapsed > this.perSeconds * 1000) {
this.windowStart = now;
this.requestCount = 0;
return;
}
// Wait if limit reached
if (this.requestCount >= this.rateLimit) {
const waitTime = (this.perSeconds * 1000) - windowElapsed;
console.log(`Rate limit reached. Waiting ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
this.windowStart = Date.now();
this.requestCount = 0;
}
}
}
// Usage
const api = new APIClient(100, 60); // 100 requests per 60 seconds
async function fetchData() {
const response = await api.request('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
return response.json();
}
Fix 6: Validate Authorization Headers
Ensure authorization headers are correctly formatted:
// Common authentication header formats
// Bearer token (OAuth, JWT)
const headers = {
'Authorization': `Bearer ${accessToken}`
};
// API Key in header
const headers = {
'X-API-Key': apiKey
// or
'Authorization': `ApiKey ${apiKey}`
};
// Basic authentication
const credentials = btoa(`${username}:${password}`);
const headers = {
'Authorization': `Basic ${credentials}`
};
// Custom header
const headers = {
'X-Custom-Auth': authToken
};
// Multiple headers
const headers = {
'Authorization': `Bearer ${accessToken}`,
'X-API-Key': apiKey,
'Content-Type': 'application/json'
};
// Make request
fetch('https://api.example.com/data', {
method: 'GET',
headers: headers
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.catch(error => {
console.error('Authentication failed:', error);
});
Platform-Specific Guides
Detailed implementation instructions for your specific platform:
Verification
After implementing fixes:
Test authentication flow:
- Clear all cached credentials
- Perform fresh authentication
- Verify tokens/keys are received
- Test token refresh if applicable
- Confirm permissions granted
Test API requests:
- Make test requests to all endpoints
- Verify successful responses (200 OK)
- Check response data is correct
- Test with different request methods (GET, POST, PUT, DELETE)
- Verify error handling for invalid requests
Test CORS configuration:
- Test from your application domain
- Check browser console for CORS errors
- Verify preflight OPTIONS requests succeed
- Test with credentials (cookies)
- Confirm headers are correct
Monitor rate limits:
- Track request counts
- Verify throttling is working
- Test retry logic with rate limiting
- Monitor rate limit headers in responses
- Set up alerts for rate limit violations
Test in all environments:
- Development with test credentials
- Staging with staging credentials
- Production with production credentials
- Verify no credential mixing
- Test error scenarios
Common Mistakes
- Hardcoded credentials - Store in environment variables
- Expired tokens - Implement refresh logic
- Wrong environment credentials - Use separate keys per environment
- Missing Authorization header - Always include authentication
- CORS misconfiguration - Set proper allowed origins
- No rate limit handling - Implement exponential backoff
- Insecure credential storage - Never commit to version control
- No token expiration handling - Check expiry before requests
- Incorrect header format - Follow API documentation exactly
- No error logging - Log all authentication failures
Troubleshooting Checklist
- API credentials are valid and active
- Using correct environment credentials
- Authorization header properly formatted
- OAuth tokens refreshed before expiration
- CORS headers configured on server
- Rate limiting handled with retries
- Credentials stored securely
- No credentials in version control
- Error handling implemented
- Logging authentication failures
- Testing in all environments
- Monitoring authentication success rate