Security¶
Orion uses DB-backed multi-user authentication with the Go gateway as the single enforcement point. The Identity service manages user accounts and tokens, while the gateway handles OAuth flows, JWT signing, and header forwarding.
Authentication Flow¶
Email/Password¶
sequenceDiagram
participant Client
participant Gateway
participant Identity
participant Service
Client->>Gateway: POST /api/v1/auth/login {email, password}
Gateway->>Identity: POST /internal/auth/login
Identity->>Identity: Verify bcrypt hash (cost 12)
Identity->>Gateway: {user, refresh_token}
Gateway->>Gateway: Sign JWT (HS256, 15-min TTL)
Gateway->>Client: {access_token, refresh_token, user}
Client->>Gateway: GET /api/v1/scout/... (Bearer token)
Gateway->>Gateway: Validate JWT
Gateway->>Service: Proxy + X-User-ID, X-User-Role, X-User-Email
Service->>Gateway: Response
Gateway->>Client: Response
OAuth 2.0 (GitHub / Google)¶
sequenceDiagram
participant Client
participant Gateway
participant Provider as GitHub/Google
participant Identity
Client->>Gateway: GET /api/v1/auth/oauth/github
Gateway->>Gateway: Generate state (CSRF protection)
Gateway->>Client: 302 → Provider authorize URL
Client->>Provider: User authorizes app
Provider->>Gateway: Callback with code + state
Gateway->>Gateway: Validate state parameter
Gateway->>Provider: Exchange code → access token
Gateway->>Provider: Fetch user profile
Gateway->>Identity: Find or create user + link OAuth
Identity->>Gateway: {user, refresh_token}
Gateway->>Gateway: Sign JWT (15-min)
Gateway->>Client: Set secure cookies, redirect to dashboard
JWT Configuration¶
| Property | Value |
|---|---|
| Algorithm | HMAC-SHA256 (HS256) |
| Access expiry | 15 minutes |
| Refresh expiry | 30 days |
| Claims | sub, email, name, role, iat, exp |
| Header | Authorization: Bearer <token> |
Access token payload:
{
"sub": "user-uuid",
"email": "admin@orion.local",
"name": "Admin",
"role": "admin",
"iat": 1710000000,
"exp": 1710000900
}
User Header Forwarding¶
After JWT validation, the gateway injects user context headers into all proxied requests:
| Header | Source | Description |
|---|---|---|
X-User-ID |
JWT sub claim |
User UUID, used for per-user data isolation |
X-User-Role |
JWT role claim |
User role (admin, user) |
X-User-Email |
JWT email claim |
User email address |
Downstream services extract these headers via get_current_user() to scope all database queries to the authenticated user.
Password Security¶
- Hashing algorithm — bcrypt with cost factor 12
- Minimum password length — Enforced at registration and password change
- Reset tokens — Short-lived, single-use, sent via email
- No plaintext storage — Passwords are never stored or logged in plaintext
Refresh Token Security¶
| Property | Implementation |
|---|---|
| Format | Opaque string (ort_ prefix) |
| Storage | DB-backed in Identity service |
| Lifetime | 30 days |
| Rotation | New token issued on each refresh, old token revoked |
| Family tracking | Tokens belong to a family; reuse of revoked token invalidates family |
| Theft detection | Revoked token reuse triggers full family revocation |
OAuth Security¶
- State parameter — Random, unguessable state value for CSRF protection on OAuth initiate
- State validation — Callback verifies state matches the value sent during initiation
- Secure cookies — OAuth tokens set with
HttpOnly,Secure,SameSite=Lax - Account linking — OAuth accounts are linked to Orion users by provider + provider user ID
Protected vs Public Endpoints¶
Public (no authentication required)¶
| Endpoint | Purpose |
|---|---|
GET /health |
Liveness probe |
GET /ready |
Readiness probe |
GET /metrics |
Prometheus metrics |
POST /api/v1/auth/login |
Email/password login |
POST /api/v1/auth/register |
User registration |
POST /api/v1/auth/refresh |
Token refresh |
POST /api/v1/auth/forgot-password |
Password reset request |
POST /api/v1/auth/reset-password |
Password reset |
POST /api/v1/auth/verify-email |
Email verification |
GET /api/v1/auth/oauth/github |
GitHub OAuth initiate |
GET /api/v1/auth/oauth/github/callback |
GitHub OAuth callback |
GET /api/v1/auth/oauth/google |
Google OAuth initiate |
GET /api/v1/auth/oauth/google/callback |
Google OAuth callback |
Protected (JWT required)¶
All endpoints under /api/v1/{service}/* require a valid Bearer token.
Admin-only¶
Endpoints under /api/v1/identity/users (except /me) require the admin role.
Middleware Chain¶
The gateway applies middleware in this order:
- RequestID -- Generates/propagates
X-Request-IDheader - Logger -- Structured request logging
- Recoverer -- Panic recovery with stack traces
- CORS -- Cross-origin resource sharing
- Metrics -- Prometheus instrumentation
- Auth -- JWT validation and user header injection (protected routes only)
- RateLimit -- Per-service sliding window rate limiting
:material-rate-limit: Rate Limiting¶
Redis-backed sliding window rate limiter with per-service, per-user limits:
| Service | Read Limit | Write Limit |
|---|---|---|
| Director | 100 req/min | 20 req/min |
| Scout | 10 req/min | -- |
| Pulse | 60 req/min | -- |
| Media | 60 req/min | -- |
| Editor | 60 req/min | -- |
| Publisher | 60 req/min | -- |
| Identity | 60 req/min | -- |
Auth endpoints (/api/v1/auth/*) have stricter rate limits to prevent brute-force attacks.
Rate limit response headers:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetRetry-After(on 429 Too Many Requests)
Network Security¶
- All inter-service traffic flows over the
orion-netDocker bridge network - Services are not directly exposed to the host -- only the gateway and dashboard bind to host ports
- The Identity service is only called internally by the gateway (not exposed via proxy for
/internal/*routes) - WebSocket connections require JWT authentication via query parameter (
?token=<jwt>)
Secure Cookie Settings¶
OAuth callbacks set tokens in cookies with the following attributes:
| Attribute | Value | Purpose |
|---|---|---|
HttpOnly |
true |
Prevent JavaScript access |
Secure |
true (prod) |
HTTPS-only in production |
SameSite |
Lax |
CSRF protection while allowing OAuth |
Path |
/ |
Available to all routes |
Production Checklist
- Change
ORION_JWT_SECRETfrom the default value - Set
OAUTH_REDIRECT_BASEto your production HTTPS URL - Configure real SMTP credentials for transactional emails
- Restrict CORS origins (default allows all)
- Enable TLS termination at the gateway or load balancer
- Rotate JWT secrets periodically
- Store OAuth client secrets in a vault or sealed secrets