8.2 KiB
8.2 KiB
Authentication
Overview
Identity Platform implements OAuth 2.0 + OpenID Connect with RS256 asymmetric signing. The platform is the Authorization Server; products are Resource Servers / Relying Parties.
Token Flow (Authorization Code + PKCE)
┌──────────┐ ┌──────────────────┐
│ Product │ │ Identity Platform │
└────┬─────┘ └────────┬─────────┘
│ │
│ 1. GET /.well-known/openid-configuration │
│◄────────────────────────────────────────────│
│ │
│ 2. Redirect user to /authorize │
│────────────────────────────────────────────►│
│ │
│ 3. User authenticates (login/MFA) │
│ │
│ 4. User consents to scopes │
│ │
│ 5. Redirect back with ?code=... │
│◄────────────────────────────────────────────│
│ │
│ 6. POST /api/v1/auth/token (code + verifier)│
│────────────────────────────────────────────►│
│ │
│ 7. { access_token, refresh_token, id_token }│
│◄────────────────────────────────────────────│
│ │
│ 8. Product verifies access_token via JWKS │
│ │
│ 9. API calls with Authorization: Bearer ... │
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/.well-known/openid-configuration |
GET | OIDC Discovery |
/.well-known/jwks.json |
GET | JWKS (public keys) |
/api/v1/auth/login |
POST | Username/password → tokens |
/api/v1/auth/refresh |
POST | Refresh token → new access |
/api/v1/auth/logout |
POST | Revoke session + refresh |
/api/v1/users/me |
GET | Current user info |
Login Request
curl -X POST https://id.example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@acme.com","password":"••••••••"}'
Response:
{
"access": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 900
}
Refresh Request
curl -X POST https://id.example.com/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."}'
Logout Request
curl -X POST https://id.example.com/api/v1/auth/logout \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"refresh":"<refresh_token>"}'
Token Details
Access Token (JWT, RS256)
{
"user_id": "uuid",
"email": "user@acme.com",
"org_id": "uuid",
"scopes": ["openid", "profile", "org:read"],
"session_id": "uuid",
"token_type": "access",
"exp": 1700000000,
"iat": 1699999100,
"iss": "https://id.example.com",
"aud": "bermooda-client-id"
}
- Lifetime: 15 minutes (configurable)
- Signed by: Platform private key
- Verified by: Product via JWKS
Refresh Token (JWT, RS256)
{
"user_id": "uuid",
"session_id": "uuid",
"token_type": "refresh",
"exp": 1702591100,
"iat": 1699999100
}
- Lifetime: 30 days (configurable)
- Stored hashed in DB (not reversible)
- Rotation: New refresh token issued on each use
- Revocable: Instantly via
/auth/logoutor session revocation
ID Token (OIDC)
{
"sub": "uuid",
"email": "user@acme.com",
"email_verified": true,
"name": "User Name",
"preferred_username": "user",
"org_id": "uuid",
"iat": 1699999100,
"exp": 1700000000,
"iss": "https://id.example.com",
"aud": "bermooda-client-id"
}
JWKS (JSON Web Key Set)
curl https://id.example.com/.well-known/jwks.json
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "identity-platform-1",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
Products cache JWKS and rotate keys automatically.
Product Integration
1. Register Application
curl -X POST https://id.example.com/api/v1/applications \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Bermooda",
"redirect_uris": ["https://bermooda.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scopes": ["openid", "profile", "email", "org:read"]
}'
Response: client_id, client_secret (shown once).
2. Configure Product
# Product .env
OIDC_ISSUER=https://id.example.com
OIDC_CLIENT_ID=bermooda-client-id
OIDC_CLIENT_SECRET=••••••••
OIDC_REDIRECT_URI=https://bermooda.example.com/callback
OIDC_SCOPES=openid profile email org:read
3. Verify Tokens (Middleware Example)
# Python/DRF example
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.backends import TokenBackend
token_backend = TokenBackend(
algorithm="RS256",
signing_key=None, # uses JWKS
jwks_url="https://id.example.com/.well-known/jwks.json",
audience="bermooda-client-id",
issuer="https://id.example.com",
)
def verify_token(token: str) -> dict:
return token_backend.decode(token, verify=True)
Security Features
| Feature | Implementation |
|---|---|
| RS256 Signing | Private key in platform only |
| PKCE | Required for public clients |
| Token Rotation | New refresh token on each refresh |
| Session Revocation | Instant, cascades to all tokens |
| Brute-force Protection | Rate limit + auto-lockout (5 failed → 15min) |
| Audit Log | Every auth event recorded |
| Device Tracking | Fingerprint + IP + User-Agent |
MFA (Roadmap)
- TOTP (Google Authenticator, Authy)
- WebAuthn / Passkeys (future)
- Recovery codes
- Per-user enrollment
Session Management
# List active sessions
GET /api/v1/sessions
Authorization: Bearer <access_token>
# Revoke session (current or other)
DELETE /api/v1/sessions/{session_id}
Authorization: Bearer <access_token>
Session object:
{
"id": "uuid",
"user": "uuid",
"device_name": "Chrome on macOS",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2024-01-15T10:30:00Z",
"last_activity": "2024-01-15T10:45:00Z",
"status": "active",
"type": "browser"
}
Rate Limits
| Endpoint | Limit |
|---|---|
/auth/login |
5/min per IP, 10/min per user |
/auth/refresh |
20/min per user |
/auth/logout |
10/min per user |
| API (general) | 100/min per user |
Exceeding → 429 Too Many Requests with Retry-After header.
Error Responses
{
"error": "invalid_grant",
"error_description": "Invalid or expired refresh token",
"error_code": "TOKEN_EXPIRED"
}
Common OIDC errors: invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope.
Testing Checklist
- Login returns access + refresh + id_token
- Access token verified via JWKS
- Refresh rotates token, old refresh revoked
- Logout revokes session + refresh token
- Expired access token → 401
- Revoked session → 401 on API calls
- Rate limit triggers 429
- Failed login recorded in audit log
- PKCE required for public clients