JWT Authentication
JSON Web Token authentication for API access across the Bluefly platform.
Overview
All platform APIs use JWT (JSON Web Token) for authentication: - Token-based auth: Stateless authentication via JWT - Unified auth service: Centralized authentication (Compliance Engine) - Role-based access: RBAC with granular permissions - Token rotation: Automatic token refresh - Audit logging: Complete authentication audit trails
Architecture
graph LR
A[Client] -->|1. Login| B[Auth Service]
B -->|2. JWT| A
A -->|3. API Request + JWT| C[API Gateway]
C -->|4. Verify JWT| D[Token Validator]
D -->|5. Allow/Deny| C
C -->|6. Response| A
Getting a JWT Token
1. Username/Password Login
POST /api/v1/auth/login
Content-Type: application/json
Request:
{
"username": "developer@bluefly.io",
"password": "secure-password-123",
"client_id": "llm-platform-web",
"grant_type": "password"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write admin"
}
2. API Key Authentication
For service-to-service authentication:
POST /api/v1/auth/api-key
Content-Type: application/json
Request:
{
"api_key": "sk_live_abc123...",
"client_id": "agent-brain-service"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"service": "agent-brain",
"permissions": ["agent:execute", "mesh:communicate"]
}
3. OAuth 2.0 (GitLab)
For user authentication via GitLab OAuth:
See OAuth 2.0 Guide for complete flow.
Using JWT Tokens
Authorization Header
Include JWT in the Authorization header:
curl -X GET https://gateway.local.bluefly.io/api/v1/chat/completions \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"model": "auto-route",
"messages": [{"role": "user", "content": "Hello"}]
}'
Query Parameter (Not Recommended)
For WebSocket or legacy clients only:
ws://tracer.local.bluefly.io/api/v1/traces/stream?access_token=eyJhbGci...
⚠️ Warning: Query parameters appear in logs. Use Authorization header whenever possible.
JWT Token Structure
JWT tokens are signed with RS256 (RSA SHA-256):
Header
{
"alg": "RS256",
"typ": "JWT",
"kid": "auth-key-2025-01"
}
Payload
{
"sub": "user-123",
"iss": "https://auth.bluefly.io",
"aud": ["https://gateway.local.bluefly.io"],
"exp": 1705323600,
"iat": 1705320000,
"nbf": 1705320000,
"jti": "token-abc123",
"scope": "read write",
"roles": ["developer", "agent-admin"],
"permissions": [
"agent:execute",
"mesh:communicate",
"workflow:create"
],
"user": {
"id": "user-123",
"email": "developer@bluefly.io",
"name": "John Developer"
},
"client_id": "llm-platform-web"
}
Signature
RSASHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
privateKey
)
Token Claims
| Claim | Description | Required |
|---|---|---|
sub |
Subject (user ID) | ✅ |
iss |
Issuer (auth service) | ✅ |
aud |
Audience (target service) | ✅ |
exp |
Expiration time (Unix timestamp) | ✅ |
iat |
Issued at (Unix timestamp) | ✅ |
nbf |
Not before (Unix timestamp) | ❌ |
jti |
JWT ID (unique identifier) | ✅ |
scope |
OAuth 2.0 scopes | ❌ |
roles |
User roles | ❌ |
permissions |
Granular permissions | ❌ |
user |
User metadata | ❌ |
client_id |
Client identifier | ❌ |
Token Refresh
Refresh expired access tokens using refresh token:
POST /api/v1/auth/refresh
Content-Type: application/json
Request:
{
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"grant_type": "refresh_token"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Token Revocation
Revoke a token before expiration:
POST /api/v1/auth/revoke
Content-Type: application/json
Authorization: Bearer eyJhbGci...
Request:
{
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type_hint": "access_token"
}
Response:
{
"revoked": true,
"jti": "token-abc123",
"revokedAt": "2025-01-15T10:35:00Z"
}
Token Validation
Client-Side Validation
Validate JWT locally without API call:
TypeScript:
import * as jose from 'jose';
const JWKS_URL = 'https://auth.bluefly.io/.well-known/jwks.json';
async function validateToken(token: string) {
const JWKS = jose.createRemoteJWKSet(new URL(JWKS_URL));
const { payload } = await jose.jwtVerify(token, JWKS, {
issuer: 'https://auth.bluefly.io',
audience: 'https://gateway.local.bluefly.io'
});
return payload;
}
Server-Side Validation
Validate via auth service:
POST /api/v1/auth/validate
Content-Type: application/json
Request:
{
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response:
{
"valid": true,
"payload": {
"sub": "user-123",
"exp": 1705323600,
"roles": ["developer"],
"permissions": ["agent:execute"]
}
}
Invalid Token:
{
"valid": false,
"error": "token_expired",
"message": "Token expired at 2025-01-15T10:00:00Z"
}
Public Keys (JWKS)
Get public keys for JWT verification:
GET /.well-known/jwks.json
Response:
{
"keys": [
{
"kty": "RSA",
"kid": "auth-key-2025-01",
"use": "sig",
"alg": "RS256",
"n": "xGOr-H7A...",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "auth-key-2024-12",
"use": "sig",
"alg": "RS256",
"n": "yH1s-K8B...",
"e": "AQAB"
}
]
}
Error Responses
401 Unauthorized
{
"error": {
"type": "authentication_error",
"code": "invalid_token",
"message": "Invalid or expired JWT token",
"details": {
"reason": "token_expired",
"expiredAt": "2025-01-15T10:00:00Z"
}
}
}
403 Forbidden
{
"error": {
"type": "authorization_error",
"code": "insufficient_permissions",
"message": "User lacks required permission",
"details": {
"required": "agent:execute",
"userPermissions": ["agent:read"]
}
}
}
Security Best Practices
1. Token Storage
✅ Recommended: - Store in memory (React state, etc.) - Use secure HTTP-only cookies - Use secure session storage
❌ Avoid: - LocalStorage (XSS vulnerable) - URL parameters (logged everywhere) - Unencrypted cookies
2. Token Lifetime
Access Tokens: Short-lived (1 hour) Refresh Tokens: Long-lived (30 days)
3. Token Rotation
Automatically rotate tokens: - Before expiration (proactive refresh) - After security events (force re-auth) - During key rotation (seamless transition)
4. Scope Limitation
Request only required scopes:
{
"scope": "agent:read workflow:execute"
}
Don't request admin unless absolutely necessary.
Rate Limiting
JWT authentication is rate-limited:
| Operation | Limit | Window |
|---|---|---|
| Login | 10 attempts | 15 minutes |
| Token refresh | 100 requests | 1 hour |
| Token validation | 1000 requests | 1 minute |
Rate Limit Headers:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1705320900
Client Libraries
TypeScript
import { AuthClient } from '@bluefly/auth-client';
const auth = new AuthClient({
endpoint: 'https://auth.bluefly.io',
clientId: 'llm-platform-web'
});
// Login
const { access_token, refresh_token } = await auth.login({
username: 'developer@bluefly.io',
password: 'secure-password'
});
// Automatic token refresh
const apiClient = auth.createClient({
baseURL: 'https://gateway.local.bluefly.io',
autoRefresh: true
});
// Use authenticated client
const response = await apiClient.post('/api/v1/chat/completions', {
model: 'auto-route',
messages: [{ role: 'user', content: 'Hello' }]
});
Python
from bluefly import AuthClient
auth = AuthClient(
endpoint="https://auth.bluefly.io",
client_id="llm-platform-cli"
)
# Login
tokens = auth.login(
username="developer@bluefly.io",
password="secure-password"
)
# Use access token
api = auth.create_client(
base_url="https://gateway.local.bluefly.io",
auto_refresh=True
)
response = api.post("/api/v1/chat/completions", json={
"model": "auto-route",
"messages": [{"role": "user", "content": "Hello"}]
})
Audit Logging
All authentication events are logged:
{
"eventType": "login_success",
"userId": "user-123",
"timestamp": "2025-01-15T10:00:00Z",
"ip": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"clientId": "llm-platform-web",
"metadata": {
"mfa": false,
"provider": "local"
}
}
See Security Architecture for audit details.