Skip to main content

Debug Utilities

Tools for troubleshooting authentication and verifying your security context.


Debug Authentication Claims

Inspect the claims and permissions decoded from your JWT authentication token.

Endpoint: GET /debug/auth

Request

curl -X GET "https://dev.api.authsec.dev/exsvc/debug/auth" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json"

Response

Status: 200 OK

{
"authenticated": true,
"claims": {
"sub": "user_123abc",
"email": "[email protected]",
"tenant_id": "tenant_xyz789",
"roles": ["admin", "service_manager"],
"permissions": [
"services:read",
"services:write",
"services:delete",
"credentials:read"
],
"scopes": ["api:full_access"],
"iat": 1707662400,
"exp": 1707748800,
"iss": "https://dev.api.authsec.dev/authmgr",
"aud": "external-service-api"
},
"token_info": {
"type": "Bearer",
"expires_in": 86400,
"issued_at": "2026-02-11T13:00:00Z",
"expires_at": "2026-02-12T13:00:00Z",
"is_expired": false
}
}

Response Fields

FieldTypeDescription
authenticatedbooleanWhether authentication was successful
claimsobjectDecoded JWT claims
token_infoobjectToken metadata and validity info

Claims Object

ClaimTypeDescription
substringSubject (user ID)
emailstringUser email address
tenant_idstringAssociated tenant
rolesarrayUser roles
permissionsarrayGranular permissions
scopesarrayOAuth scopes
iatintegerIssued at (Unix timestamp)
expintegerExpiration time (Unix timestamp)
issstringToken issuer
audstringIntended audience

Token Info

FieldTypeDescription
typestringToken type (Bearer)
expires_inintegerSeconds until expiration
issued_atstringISO 8601 timestamp
expires_atstringISO 8601 timestamp
is_expiredbooleanWhether token is expired

Common Use Cases

1. Verify Token Permissions

Check if your token has required permissions for an operation:

import requests

def verify_permissions(token: str, required_permissions: list) -> bool:
"""
Verify token has required permissions
"""
response = requests.get(
"https://dev.api.authsec.dev/exsvc/debug/auth",
headers={"Authorization": f"Bearer {token}"}
)

if response.status_code != 200:
return False

data = response.json()
user_permissions = data['claims'].get('permissions', [])

# Check if all required permissions are present
has_all = all(perm in user_permissions for perm in required_permissions)

if not has_all:
missing = set(required_permissions) - set(user_permissions)
print(f"Missing permissions: {missing}")

return has_all

# Usage
token = "your_jwt_token"
required = ["services:write", "credentials:read"]
if verify_permissions(token, required):
print("✅ Token has all required permissions")
else:
print("❌ Token missing required permissions")

2. Check Token Expiration

Monitor token validity and refresh if needed:

from datetime import datetime

def check_token_expiration(token: str) -> dict:
"""
Check if token is expiring soon
"""
response = requests.get(
"https://dev.api.authsec.dev/exsvc/debug/auth",
headers={"Authorization": f"Bearer {token}"}
)

if response.status_code != 200:
return {"valid": False, "reason": "Invalid token"}

data = response.json()
token_info = data['token_info']

if token_info['is_expired']:
return {"valid": False, "reason": "Token expired"}

expires_in = token_info['expires_in']

if expires_in < 300: # Less than 5 minutes
return {
"valid": True,
"warning": "Token expiring soon",
"expires_in_seconds": expires_in,
"action": "refresh_recommended"
}

return {
"valid": True,
"expires_in_seconds": expires_in
}

# Usage
status = check_token_expiration("your_jwt_token")
if status.get('warning'):
print(f"⚠️ {status['warning']}")
print(f"Expires in: {status['expires_in_seconds']} seconds")

3. Debug Authorization Failures

When receiving 403 Forbidden errors:

def debug_authorization_failure(token: str, required_role: str = None):
"""
Debug why authorization is failing
"""
response = requests.get(
"https://dev.api.authsec.dev/exsvc/debug/auth",
headers={"Authorization": f"Bearer {token}"}
)

if response.status_code != 200:
print("❌ Token is invalid or malformed")
return

data = response.json()
claims = data['claims']

print("🔍 Token Debug Information:")
print(f"User: {claims.get('email', 'N/A')}")
print(f"Tenant: {claims.get('tenant_id', 'N/A')}")
print(f"Roles: {', '.join(claims.get('roles', []))}")
print(f"Permissions: {', '.join(claims.get('permissions', []))}")

if required_role:
user_roles = claims.get('roles', [])
if required_role not in user_roles:
print(f"\n❌ Missing required role: {required_role}")
print(f"Available roles: {', '.join(user_roles)}")
else:
print(f"\n✅ Has required role: {required_role}")

# Check token expiration
if data['token_info']['is_expired']:
print("\n❌ Token is EXPIRED")
print(f"Expired at: {data['token_info']['expires_at']}")

Troubleshooting Guide

Problem: 401 Unauthorized on Debug Endpoint

Possible Causes:

  1. Token is missing from Authorization header
  2. Token is malformed
  3. Token signature is invalid

Solutions:

# Check if header is formatted correctly
curl -v -H "Authorization: Bearer YOUR_TOKEN" \
https://dev.api.authsec.dev/exsvc/debug/auth

# Verify token format (should have 3 parts separated by dots)
echo "YOUR_TOKEN" | awk -F. '{print NF-1}'
# Should output: 2

Problem: Token Shows Expired

Possible Causes:

  1. Token has genuinely expired
  2. Clock skew between systems
  3. Token was revoked

Solutions:

# Check current time vs expiration
import time

def check_exp_time(exp_timestamp):
current_time = int(time.time())
time_diff = exp_timestamp - current_time

if time_diff < 0:
print(f"Token expired {abs(time_diff)} seconds ago")
else:
print(f"Token valid for {time_diff} more seconds")

# Check for clock skew
if abs(time_diff) < 300: # Within 5 minutes
print("⚠️ Possible clock skew issue")

Resolution:

  1. Request a new token from Auth Manager
  2. Implement token refresh logic in your application
  3. Verify system clocks are synchronized (NTP)

Problem: Missing Expected Permissions

Possible Causes:

  1. User role doesn't include required permissions
  2. Token generated before permissions were granted
  3. RBAC policy hasn't been applied

Solutions:

  1. Check user's roles in User Management
  2. Verify role permissions in Authorization
  3. Generate a fresh token after permission changes
  4. Contact administrator to grant required permissions

Code Examples

Python

import requests
from typing import Dict, Any

class AuthDebugger:
def __init__(self, base_url: str):
self.base_url = base_url
self.debug_endpoint = f"{base_url}/debug/auth"

def inspect_token(self, token: str) -> Dict[str, Any]:
"""
Get complete token debug information
"""
response = requests.get(
self.debug_endpoint,
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
return response.json()

def print_token_summary(self, token: str):
"""
Print human-readable token summary
"""
try:
data = self.inspect_token(token)
claims = data['claims']
token_info = data['token_info']

print("=" * 50)
print("TOKEN DEBUG SUMMARY")
print("=" * 50)
print(f"Authenticated: {'✅ Yes' if data['authenticated'] else '❌ No'}")
print(f"User: {claims.get('email', 'N/A')}")
print(f"User ID: {claims.get('sub', 'N/A')}")
print(f"Tenant: {claims.get('tenant_id', 'N/A')}")
print(f"\nRoles: {', '.join(claims.get('roles', []))}")
print(f"\nPermissions:")
for perm in claims.get('permissions', []):
print(f" - {perm}")
print(f"\nScopes: {', '.join(claims.get('scopes', []))}")
print(f"\nToken Validity:")
print(f" Issued: {token_info['issued_at']}")
print(f" Expires: {token_info['expires_at']}")
print(f" Expires in: {token_info['expires_in']} seconds")
print(f" Status: {'❌ EXPIRED' if token_info['is_expired'] else '✅ Valid'}")
print("=" * 50)

except requests.exceptions.HTTPError as e:
print(f"❌ Error: {e}")
print(f"Status Code: {e.response.status_code}")

# Usage
debugger = AuthDebugger("https://dev.api.authsec.dev/exsvc")
debugger.print_token_summary("your_jwt_token")

TypeScript

interface AuthDebugResponse {
authenticated: boolean;
claims: {
sub: string;
email: string;
tenant_id: string;
roles: string[];
permissions: string[];
scopes: string[];
iat: number;
exp: number;
iss: string;
aud: string;
};
token_info: {
type: string;
expires_in: number;
issued_at: string;
expires_at: string;
is_expired: boolean;
};
}

class AuthDebugger {
constructor(private baseUrl: string) {}

async inspectToken(token: string): Promise<AuthDebugResponse> {
const response = await fetch(
`${this.baseUrl}/debug/auth`,
{
headers: {
"Authorization": `Bearer ${token}`,
},
}
);

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

return await response.json();
}

async printTokenSummary(token: string): Promise<void> {
try {
const data = await this.inspectToken(token);
const { claims, token_info } = data;

console.log("=".repeat(50));
console.log("TOKEN DEBUG SUMMARY");
console.log("=".repeat(50));
console.log(`Authenticated: ${data.authenticated ? '✅ Yes' : '❌ No'}`);
console.log(`User: ${claims.email}`);
console.log(`User ID: ${claims.sub}`);
console.log(`Tenant: ${claims.tenant_id}`);
console.log(`\nRoles: ${claims.roles.join(', ')}`);
console.log(`\nPermissions:`);
claims.permissions.forEach(perm => console.log(` - ${perm}`));
console.log(`\nScopes: ${claims.scopes.join(', ')}`);
console.log(`\nToken Validity:`);
console.log(` Issued: ${token_info.issued_at}`);
console.log(` Expires: ${token_info.expires_at}`);
console.log(` Expires in: ${token_info.expires_in} seconds`);
console.log(` Status: ${token_info.is_expired ? '❌ EXPIRED' : '✅ Valid'}`);
console.log("=".repeat(50));
} catch (error) {
console.error(`❌ Error: ${error.message}`);
}
}
}

// Usage
const debugger = new AuthDebugger("https://dev.api.authsec.dev/exsvc");
await debugger.printTokenSummary("your_jwt_token");

Best Practices

1. Development vs Production

# Only use debug endpoint in development/staging
import os

DEBUG_MODE = os.getenv('ENVIRONMENT') != 'production'

if DEBUG_MODE:
# Safe to call debug endpoint
debug_info = get_debug_auth(token)
print(f"Debug info: {debug_info}")
else:
# Production: minimal logging
print("Token validated")

2. Sensitive Data Handling

# Don't log full claims in production
def safe_log_claims(claims: dict):
"""
Log claims safely without exposing sensitive data
"""
safe_claims = {
'sub': claims['sub'][:8] + '...', # Partial ID
'tenant_id': claims['tenant_id'][:8] + '...',
'roles': claims['roles'],
'has_permissions': len(claims.get('permissions', [])) > 0,
'expires_in': claims.get('exp', 0) - int(time.time())
}
print(f"Token info: {safe_claims}")

3. Automated Testing

def test_token_permissions():
"""
Automated test for token permissions
"""
token = get_test_token()
debug_response = inspect_token(token)

assert debug_response['authenticated'] == True
assert 'services:read' in debug_response['claims']['permissions']
assert not debug_response['token_info']['is_expired']

print("✅ Token permission tests passed")

Next Steps