JWT Structure & Validation
AuthOS uses JSON Web Tokens (JWT) for stateless, secure authentication. This guide details the token structure, signing mechanism, and how to validate tokens in your backend services.
JWT Overview
A JWT consists of three parts separated by dots (.):
- Header: Algorithm and token type
- Payload: User data and claims
- Signature: Cryptographic proof of integrity
aaaaa.bbbbb.ccccc
Header.Payload.Signature
Signing Algorithm
All JWTs are signed using RS256 (RSA Signature with SHA-256).
- Asymmetric: Uses a Private Key (held by AuthOS) to sign, and a Public Key (exposed via JWKS) to verify.
- Security: You do not need any secrets to validate tokens.
Token Structure
1. Header
The header typically contains:
{
"alg": "RS256",
"typ": "JWT",
"kid": "sso-key-2025-01-01"
}
alg: Algorithm used (RS256)typ: Token type (JWT)kid: Key ID, essential for identifying which public key to use for validation.
2. Payload (Claims)
The payload contains statements about the user and authentication context (Claims).
{
"sub": "user_123456789",
"email": "user@example.com",
"org": "acme-corp",
"service": "billing-app",
"plan": "enterprise",
"features": ["audit-logs", "sso"],
"iat": 1704067200,
"exp": 1704068100,
"iss": "https://sso.example.com",
"aud": "billing-app"
}
Standard Claims
sub(Subject): Unique user identifier.iss(Issuer): URL of the AuthOS instance.aud(Audience): The service or client the token is intended for.exp(Expiration Time): Unix timestamp when the token expires.iat(Issued At): Unix timestamp when the token was created.
Custom Claims
org: Organization slug (if authenticated in org context).service: Service slug (if authenticated for a specific service).plan: Subscription tier active for this user/org.features: Array of enabled feature flags.is_platform_owner: Boolean indicating super-admin status.
Token Validation
Your backend must validate every token before granting access.
JWKS Endpoint
Public keys are exposed at:
GET /.well-known/jwks.json
Response:
{
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "sso-key-2025-01-01",
"n": "base64-modulus...",
"e": "AQAB"
}
]
}
Validation Steps
- Extract Token: Get from
Authorization: Bearer <token>header. - Decode Header: Read the
kidfrom the JWT header. - Fetch Key: Look up the
kidin the JWKS (cache this!). - Verify Signature: Check that the signature matches the payload using the public key.
- Validate Claims: Ensure
expis in the future andiss/audmatch expectations.
Code Examples
Node.js (Express)
Using express-jwt and jwks-rsa:
import { expressjwt } from 'express-jwt';
import jwksRsa from 'jwks-rsa';
const checkJwt = expressjwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'https://sso.example.com/.well-known/jwks.json'
}),
audience: 'your-service-slug',
issuer: 'https://sso.example.com',
algorithms: ['RS256']
});
app.get('/api/private', checkJwt, (req, res) => {
res.send(`Hello ${req.auth.email}!`);
});
Python (Flask)
Using pyjwt:
import jwt
from jwt import PyJWKClient
url = "https://sso.example.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)
def validate_token(token):
signing_key = jwks_client.get_signing_key_from_jwt(token)
data = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience="your-service-slug",
issuer="https://sso.example.com"
)
return data
Best Practices
- Cache JWKS: Don’t fetch keys on every request. Cache them.
- Rate Limit: Limit calls to the JWKS endpoint to prevent DOS.
- Check Scope/Context: Just because a token is valid doesn’t mean the user has access. Check
organdfeaturesclaims. - Handle Rotation: Keys rotate periodically. Ensure your cache invalidates or refreshes when it encounters an unknown
kid.