API Authentication Issues | Blue Frog Docs

API Authentication Issues

Diagnose and fix API authentication failures including OAuth tokens, API keys, rate limiting, and CORS configuration

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

  1. Open browser DevTools (F12)
  2. Navigate to Network tab
  3. Filter by Fetch/XHR
  4. Trigger API request
  5. Click on request to see details
  6. 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:

  1. Initiate OAuth authorization
  2. User grants permissions
  3. Receive authorization code
  4. Exchange code for access token
  5. Use access token in requests
  6. 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:

  1. 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
  2. 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 .gitignore
    
  3. Load 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'
      }
    });
    
  4. 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:

Platform Integration Guide
Shopify Shopify API Authentication
WordPress WordPress REST API Authentication
Wix Wix Velo API Authentication
Squarespace Squarespace API Integration
Webflow Webflow API Authentication

Verification

After implementing fixes:

  1. Test authentication flow:

    • Clear all cached credentials
    • Perform fresh authentication
    • Verify tokens/keys are received
    • Test token refresh if applicable
    • Confirm permissions granted
  2. 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
  3. 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
  4. 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
  5. 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

  1. Hardcoded credentials - Store in environment variables
  2. Expired tokens - Implement refresh logic
  3. Wrong environment credentials - Use separate keys per environment
  4. Missing Authorization header - Always include authentication
  5. CORS misconfiguration - Set proper allowed origins
  6. No rate limit handling - Implement exponential backoff
  7. Insecure credential storage - Never commit to version control
  8. No token expiration handling - Check expiry before requests
  9. Incorrect header format - Follow API documentation exactly
  10. 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

Further Reading

// SYS.FOOTER