283 lines
8.2 KiB
Markdown
283 lines
8.2 KiB
Markdown
# 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
|
|
```bash
|
|
curl -X POST https://id.example.com/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"user@acme.com","password":"••••••••"}'
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"access": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"refresh": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"token_type": "bearer",
|
|
"expires_in": 900
|
|
}
|
|
```
|
|
|
|
### Refresh Request
|
|
```bash
|
|
curl -X POST https://id.example.com/api/v1/auth/refresh \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"refresh":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."}'
|
|
```
|
|
|
|
### Logout Request
|
|
```bash
|
|
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)
|
|
```json
|
|
{
|
|
"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)
|
|
```json
|
|
{
|
|
"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/logout` or session revocation
|
|
|
|
### ID Token (OIDC)
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```bash
|
|
curl https://id.example.com/.well-known/jwks.json
|
|
```
|
|
|
|
```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
|
|
```bash
|
|
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
|
|
```env
|
|
# 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
|
|
# 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
|
|
|
|
```bash
|
|
# 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:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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 |