Resolving API Authentication Errors (401 Unauthorized)
đ 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.
{"error": "unauthorized", "message": "Invalid or expired token"}{"error": "authentication_failed", "code": 401}WWW-Authenticate: Bearer error="invalid_token"
Example Error 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.
2. Invalid or Malformed Token
Common scenarios include:
- Token was copied incorrectly (extra spaces, line breaks, truncated)
- Token format is invalid (not a valid JWT structure)
- Token was revoked manually through the admin portal
- Using a token from a different environment (staging vs. production)
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:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIx...
- â
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:
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:
- Log in to the Company X Admin Portal at
https://admin.xxx.com - Navigate to Settings â API â Applications
- Locate your application by client_id
- Verify status shows "Active" (not "Revoked" or "Suspended")
- Check the "Created" and "Last Used" timestamps
4Refresh Your Access Token
If your token has expired, use your refresh token to obtain a new access token:
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:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "new_refresh_token_here",
"scope": "read write admin"
}
5Check Token Scopes
Verify that your token has the required scopes for the API endpoint you're accessing:
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:
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):
# 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):
# Check time synchronization status
w32tm /query /status
# Force time sync
w32tm /resync
7Test with a Fresh Token
Generate a completely new token from scratch to rule out any issues with cached or stored tokens:
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:
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 -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: 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:
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:
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:
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
- 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
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:
- Log all 401 errors with request details
- Set up alerts for sudden spikes in auth failures
- Track token refresh frequency
- Monitor API rate limits alongside auth metrics
5. Use Token Caching Wisely
- 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
Was this article helpful?
Your feedback helps us improve our documentation
Still having issues? Contact Support or visit our Community Forum