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).is_platform_owner: Boolean indicating super-admin status.jti: Unique identifier for the JWT.
Server-Side Hydration: Claims such as plan, features, and permissions are NOT included in the JWT. Instead, the AuthOS API fetches these from a high-performance internal cache (moka) using the sub (User ID). This allows for instant permission revocation and smaller, more efficient tokens.
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.