ARTICLE ID: KB-5891

Resolving API Authentication Errors (401 Unauthorized)

API Authentication Troubleshooting REST API OAuth 2.0
âš ī¸
Data Sanitization Notice: This article has been sanitized for portfolio purposes. All sensitive information including API endpoints, client secrets, organization IDs, and internal URLs have been replaced with generic placeholders (xxx, CLIENT-ABC, etc.).

🔍 Symptoms

When making API requests to Company X's platform, you receive a 401 Unauthorized error response. This indicates that your request lacks valid authentication credentials or the provided credentials are invalid/expired.

❌
Common Error Messages:
  • {"error": "unauthorized", "message": "Invalid or expired token"}
  • {"error": "authentication_failed", "code": 401}
  • WWW-Authenticate: Bearer error="invalid_token"

Example Error Response

HTTP Response
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer realm="Company X API"

{
  "error": "unauthorized",
  "error_description": "The access token is invalid or has expired",
  "error_code": "E-AUTH-001",
  "timestamp": "2024-03-08T14:23:45Z",
  "request_id": "req_xxxxxxxxxx"
}

đŸ–Ĩī¸ Environment

This article applies to the following:

Component Details
Affected Service Company X REST API (v2 and v3)
API Endpoints All authenticated endpoints (api.xxx.com/v2/*, api.xxx.com/v3/*)
Authentication Method OAuth 2.0 Bearer Token
Affected Clients Any application or script making API requests
Platforms All (Windows, Linux, macOS, Mobile)

🔎 Root Causes

A 401 Unauthorized error can occur due to several reasons. Understanding the root cause is essential for proper resolution.

1. Expired Access Token

OAuth 2.0 access tokens have a limited lifespan (default: 3600 seconds / 1 hour). If your application doesn't refresh the token before expiration, API requests will fail.

â„šī¸
Token Lifecycle: Access tokens issued by Company X API expire after 1 hour. Refresh tokens are valid for 30 days and should be used to obtain new access tokens.

2. Invalid or Malformed Token

Common scenarios include:

3. Missing Authorization Header

The API request doesn't include the Authorization header or uses incorrect header formatting.

4. Incorrect Token Scope

The access token doesn't have the required scope/permissions for the requested API endpoint.

5. Revoked or Deleted API Credentials

The client application credentials (client_id/client_secret) were revoked or deleted from the organization's API settings.

6. Clock Skew Issues

Significant time difference between client system and API servers can cause token validation to fail (tokens have iat and exp timestamps).

✅ Resolution Steps

Follow these steps systematically to identify and resolve the authentication issue:

1Verify Token Format and Presence

First, ensure your API request includes a properly formatted Authorization header:

Correct Format
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIx...
âš ī¸
Common Mistakes:
  • ❌ Authorization: eyJhbGci... (missing "Bearer " prefix)
  • ❌ Authorization: bearer eyJhbGci... (lowercase "bearer")
  • ❌ Authentication: Bearer eyJhbGci... (wrong header name)
  • ❌ Extra spaces or line breaks in the token value

2Check Token Expiration

Decode your JWT token to check if it has expired. You can use online tools like jwt.io or decode programmatically:

Python Example
import jwt
import datetime

token = "your_access_token_here"

try:
    # Decode without verification (just to check claims)
    decoded = jwt.decode(token, options={"verify_signature": False})
    
    exp_timestamp = decoded.get('exp')
    exp_datetime = datetime.datetime.fromtimestamp(exp_timestamp)
    
    print(f"Token expires at: {exp_datetime}")
    print(f"Current time: {datetime.datetime.now()}")
    
    if datetime.datetime.now() > exp_datetime:
        print("❌ Token has EXPIRED")
    else:
        remaining = exp_datetime - datetime.datetime.now()
        print(f"✅ Token valid for: {remaining}")
        
except jwt.DecodeError:
    print("❌ Invalid token format")

If token is expired: Request a new token using your refresh token (see Step 4).

3Verify Client Credentials

Confirm that your client credentials (client_id and client_secret) are valid and haven't been revoked:

  1. Log in to the Company X Admin Portal at https://admin.xxx.com
  2. Navigate to Settings → API → Applications
  3. Locate your application by client_id
  4. Verify status shows "Active" (not "Revoked" or "Suspended")
  5. Check the "Created" and "Last Used" timestamps
💡
Pro Tip: If credentials were recently rotated or regenerated, ensure you're using the NEW client_secret, not the old one. The old secret becomes invalid immediately upon rotation.

4Refresh Your Access Token

If your token has expired, use your refresh token to obtain a new access token:

cURL Example
curl -X POST "https://auth.xxx.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=your_refresh_token_here" \
  -d "client_id=your_client_id" \
  -d "client_secret=your_client_secret"

Expected Response:

JSON Response
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "new_refresh_token_here",
  "scope": "read write admin"
}
âš ī¸
Important: Refresh tokens are single-use. When you use a refresh token, a NEW refresh token is issued. Store the new refresh token for future use, and discard the old one.

5Check Token Scopes

Verify that your token has the required scopes for the API endpoint you're accessing:

Decode Token to Check Scopes
import jwt

token = "your_access_token"
decoded = jwt.decode(token, options={"verify_signature": False})

print("Token scopes:", decoded.get('scope'))
# Example output: "read write devices:manage"

Required Scopes by Endpoint:

Endpoint Required Scope
GET /v2/devices read or devices:read
POST /v2/devices/{id}/actions write or devices:manage
GET /v2/policies read or policies:read
PUT /v2/policies/{id} write or policies:write
GET /v2/logs/* logs:read or admin
DELETE /v2/organizations/{id} admin

If your token lacks required scopes, you'll need to request a new token with the appropriate scopes. Modify your OAuth authorization request:

Request Token with Specific Scopes
curl -X POST "https://auth.xxx.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=your_client_id" \
  -d "client_secret=your_client_secret" \
  -d "scope=read write devices:manage logs:read"

6Verify System Time Synchronization

JWT tokens contain timestamp claims (iat, exp, nbf). If your system clock is significantly out of sync, token validation will fail.

Check System Time (Linux/Mac):

bash
# Check current time
date

# Compare with NTP server
ntpdate -q pool.ntp.org

# If time is off, sync with NTP
sudo ntpdate -s time.nist.gov

Check System Time (Windows):

PowerShell
# Check time synchronization status
w32tm /query /status

# Force time sync
w32tm /resync
â„šī¸
Acceptable Clock Skew: Company X API allows up to 5 minutes of clock skew. Beyond that, token validation fails with a 401 error.

7Test with a Fresh Token

Generate a completely new token from scratch to rule out any issues with cached or stored tokens:

Complete Token Request
curl -X POST "https://auth.xxx.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=CLIENT_ID_HERE" \
  -d "client_secret=CLIENT_SECRET_HERE" \
  -d "scope=read write"

Then immediately test the new token with a simple API call:

Test API Call
curl -X GET "https://api.xxx.com/v2/organizations/current" \
  -H "Authorization: Bearer YOUR_NEW_TOKEN_HERE" \
  -H "Content-Type: application/json"

If this works, the issue was with your previous token. If it still fails, proceed to Step 8.

8Enable Verbose Logging and Debug

Use verbose mode to see detailed authentication information:

cURL with Verbose Output
curl -v -X GET "https://api.xxx.com/v2/devices" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  2>&1 | grep -i "authorization\|www-authenticate\|401"

Look for specific clues in the WWW-Authenticate response header:

WWW-Authenticate Header Examples
WWW-Authenticate: Bearer error="invalid_token", error_description="Token has expired"
WWW-Authenticate: Bearer error="invalid_token", error_description="Token signature verification failed"
WWW-Authenticate: Bearer error="insufficient_scope", error_description="Token lacks required scope"

âœ”ī¸ Verification

After implementing the resolution steps, verify that the issue is resolved:

1. Successful API Response

A successful authenticated request should return 200 OK or 201 Created with the expected data:

Success Response Example
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987

{
  "data": {
    "organization_id": "org_xxxxxxxxxx",
    "name": "Client ABC",
    "created_at": "2024-01-15T10:30:00Z",
    ...
  }
}

2. Test Multiple Endpoints

Verify authentication works across different API endpoints to ensure comprehensive access:

Verification Script (Python)
import requests

token = "your_access_token"
headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}

endpoints = [
    "https://api.xxx.com/v2/organizations/current",
    "https://api.xxx.com/v2/devices?limit=5",
    "https://api.xxx.com/v2/policies",
]

for endpoint in endpoints:
    response = requests.get(endpoint, headers=headers)
    if response.status_code == 200:
        print(f"✅ {endpoint} - SUCCESS")
    else:
        print(f"❌ {endpoint} - FAILED ({response.status_code})")
        print(f"   Error: {response.json()}")

3. Validate Token Claims

Ensure your token contains the expected claims and hasn't been tampered with:

đŸ›Ąī¸ Prevention & Best Practices

Implement these practices to avoid authentication issues in your applications:

1. Implement Token Refresh Logic

Always refresh tokens before they expire, not after:

Python Token Manager Example
import time
import requests

class TokenManager:
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token = None
        self.refresh_token = None
        self.expires_at = 0
        
    def get_token(self):
        # Refresh 5 minutes before expiration
        if time.time() >= (self.expires_at - 300):
            self._refresh_token()
        return self.access_token
    
    def _refresh_token(self):
        data = {
            "grant_type": "refresh_token" if self.refresh_token else "client_credentials",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        }
        
        if self.refresh_token:
            data["refresh_token"] = self.refresh_token
            
        response = requests.post("https://auth.xxx.com/oauth/token", data=data)
        
        if response.status_code == 200:
            token_data = response.json()
            self.access_token = token_data["access_token"]
            self.refresh_token = token_data.get("refresh_token")
            self.expires_at = time.time() + token_data["expires_in"]
        else:
            raise Exception(f"Token refresh failed: {response.text}")

# Usage
token_mgr = TokenManager("your_client_id", "your_client_secret")
headers = {"Authorization": f"Bearer {token_mgr.get_token()}"}

2. Store Credentials Securely

🔒
Security Best Practices:
  • Never hardcode client_secret in source code
  • Use environment variables or secure secret management (e.g., AWS Secrets Manager, HashiCorp Vault)
  • Never commit tokens or secrets to version control
  • Rotate credentials regularly (every 90 days)
  • Use different credentials for dev/staging/production

3. Implement Proper Error Handling

Robust Error Handling Example
def make_api_request(endpoint, token):
    headers = {"Authorization": f"Bearer {token}"}
    
    try:
        response = requests.get(endpoint, headers=headers, timeout=30)
        
        if response.status_code == 401:
            # Authentication failed - try refreshing token
            new_token = refresh_access_token()
            headers["Authorization"] = f"Bearer {new_token}"
            response = requests.get(endpoint, headers=headers, timeout=30)
        
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 401:
            print("Authentication failed even after token refresh")
            # Log to monitoring system, alert team
        raise
    except requests.exceptions.Timeout:
        print("API request timed out")
        raise
    except Exception as e:
        print(f"Unexpected error: {e}")
        raise

4. Monitor Token Usage

Implement monitoring to detect authentication issues early:

5. Use Token Caching Wisely

💡
Caching Guidelines:
  • Cache tokens in-memory for the application lifetime
  • If caching to disk/database, encrypt the tokens
  • Set cache TTL to be less than token expiration
  • Invalidate cache when receiving 401 errors