HTTP Security Headers
Every response from the auth service includes the following security headers:| Header | Value | Purpose |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Forces HTTPS for one year, covering subdomains |
X-Content-Type-Options | nosniff | Prevents browsers from MIME-sniffing responses |
X-Frame-Options | DENY | Prevents the service from being embedded in iframes (clickjacking protection) |
X-XSS-Protection | 0 | Disables legacy XSS auditors (modern CSP is preferred) |
Referrer-Policy | strict-origin-when-cross-origin | Limits referrer leakage across origins |
Permissions-Policy | camera=(), microphone=(), geolocation=() | Denies access to sensitive browser APIs |
If you are running behind a reverse proxy (Nginx, Cloudflare, AWS ALB), make sure HSTS is not duplicated. The proxy typically handles TLS termination and HSTS, while the auth service adds the defense-in-depth headers.
Rate Limiting
All endpoints are protected by per-IP sliding-window rate limits powered by Redis.| Endpoint | Limit | Window |
|---|---|---|
| All endpoints (global default) | 100 req | 1 minute |
POST /v1/authorize | 10 req | 1 minute |
POST /v1/token | 20 req | 1 minute |
POST /v1/token/refresh | 20 req | 1 minute |
GET /.well-known/jwks.json | Exempt | — |
429 Too Many Requests with a Retry-After header indicating when the window resets. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers.
See the full Rate Limits guide for SDK integration examples and retry strategies.
Self-hosted tuning
Rate limits are configured inapps/auth-service/src/server.ts (global) and in individual route files (per-endpoint). Adjust limits based on your traffic profile:
CORS Policy
The auth service registers@fastify/cors with default settings, allowing requests from any origin. This is appropriate for the hosted API because authentication is API-key-based (not cookie-based).
For self-hosted deployments handling browser-based consent flows, you may want to restrict allowed origins:
Admin API Authentication
The admin endpoints (/v1/admin/*) use a separate ADMIN_API_KEY environment variable. The key is validated against the Authorization: Bearer <key> header. If ADMIN_API_KEY is not set, all admin endpoints return 503 Service Unavailable.
SSO State Parameter
During SSO login flows (OIDC, SAML, LDAP), the auth service encodes session context (organization ID and connection ID) into astate parameter. The state is serialized as a base64url-encoded JSON payload and validated on callback to prevent CSRF attacks.
The SSO callback endpoints verify the state parameter structure before accepting any authentication response. Invalid or missing state values return 400 Bad Request.
JWT Expiry Validation
Every grant token is a RS256-signed JWT. Token verification (both online viaPOST /v1/tokens/verify and offline via verifyGrantToken()) always checks the exp claim. Expired tokens are rejected regardless of signature validity.
Default token lifetimes:
| Token type | Default TTL |
|---|---|
| Grant token | Matches the expiresIn parameter on authorization (e.g., 24h) |
| Refresh token | 30 days, single-use |
| Principal session token | Matches expiresIn from POST /v1/principal-sessions |
Scope Format Validation
Scopes follow theresource:action format and are validated at multiple points:
- Agent registration — scopes declared during
agents.register()are stored and used as the maximum allowable set - Authorization request — requested scopes must be a subset of the agent’s declared scopes
- Delegation — delegated scopes must be a subset of the parent grant’s scopes
400 Bad Request.
Input Sanitization
The auth service uses Fastify’s built-in JSON parser with strict validation. All request bodies must be valid JSON withContent-Type: application/json. Key protections:
- Request IDs — every request is assigned a
randomUUID()for tracing, attached asx-request-idin the response - SQL injection — all database queries use parameterized queries via
postgres.jstagged template literals (never string concatenation) - Type coercion — Fastify validates request parameters before route handlers execute
- Error messages — internal errors return generic messages; stack traces are never leaked to clients
Body Size Limits
Fastify enforces a default body size limit of 1 MB for all routes. This prevents denial-of-service attacks via oversized payloads. If a request exceeds the limit, Fastify returns413 Payload Too Large before the route handler runs.
For self-hosted deployments that need to accept larger policy bundles or compliance exports, increase the limit per-route:
API Key Hashing
API keys are never stored in plaintext. The auth service hashes every API key with SHA-256 before storing it in the database. Authentication works by hashing the incoming key and comparing it against stored hashes. This means:- Database breaches do not expose raw API keys
- API keys cannot be recovered — only rotated via
POST /v1/keys/rotate
Production Hardening Checklist
Rate limiting is enabled (Redis required)
CORS origins are restricted for browser-facing deployments
ADMIN_API_KEY is set to a strong random value
RSA_PRIVATE_KEY is a real 2048-bit key (not auto-generated)
TLS is terminated at the reverse proxy or load balancer
Database and Redis are on private networks, not publicly accessible
JWT_ISSUER matches your exact public URL
Grant tokens use short TTLs with refresh tokens for long workflows
Scopes follow least-privilege design with
resource:action formatAudit logging is enabled for all sensitive agent actions