--- name: auth-architecture description: LiteLLM-RS Authentication Architecture. Covers JWT + API Key + RBAC multi-method auth, DashMap-backed rate limiting, the actix-web middleware pipeline, and credential management (gw- prefixed keys, HMAC hashing). Use when adding auth methods, debugging JWT/API-key validation, implementing RBAC permission checks, or tuning rate limiting and auth configuration. --- # Authentication Architecture Guide ## Overview `AuthSystem` (`src/auth/system.rs`) composes three subsystems behind one `authenticate()` entry point: ```rust pub struct AuthSystem { config: Arc, storage: Arc, jwt: Arc, // src/auth/jwt/types.rs api_key: Arc, // src/auth/api_key/creation.rs rbac: Arc, // src/auth/rbac/system.rs } ``` `AuthSystem::authenticate(auth_method, context) -> Result` dispatches on `AuthMethod::{Jwt, ApiKey, Session, None}` (`src/auth/types.rs`). Session auth is a stub that always rejects ("Session authentication is not yet implemented"). Semantic rejections return `AuthResult { success: false, error: Some(..) }`; infrastructure failures return `Err(GatewayError)`. ### Middleware pipeline Wired in `src/server/http.rs` (actix runs wraps in reverse registration order, so the request path is outermost-first): ``` RequestIdMiddleware -> AuditMiddleware -> IpAccessMiddleware -> CORS/Metrics -> AuthMiddleware (src/server/middleware/auth.rs) -> RateLimitMiddleware (src/server/middleware/rate_limit.rs) -> SecurityHeadersMiddleware -> handler ``` `AuthMiddleware` per request: public-route bypass → fail-closed check when both auth methods are disabled (`allow_anonymous` gate) → brute-force lockout via `AuthRateLimiter` → credential extraction → authentication → endpoint/operation authorization → insert `User` / `ApiKey` into request extensions. --- ## Credential Extraction `extract_auth_method_with_api_key_header` (`src/server/middleware/helpers.rs`) resolves credentials in this priority order: 1. `Authorization: Bearer ` → `AuthMethod::Jwt` 2. `Authorization: ApiKey ` → `AuthMethod::ApiKey` 3. `Authorization: gw-...` (raw key, no scheme) → `AuthMethod::ApiKey` 4. Configured API key header (`auth.api_key_header`, default `Authorization`) 5. `X-API-Key` fallback (when the configured header differs) 6. `session=` cookie → `AuthMethod::Session` There is no `Bearer sk-` form: gateway keys are `gw-` prefixed, and a raw `sk-...` value matches nothing. ## API Key Authentication ### Key generation and hashing Keys are generated by `generate_api_key()` in `src/utils/auth/crypto/keys.rs`: a fixed `gw` prefix plus 32 alphanumeric characters (`gw-<32 chars>`, 35 total). They are hashed — never stored in plaintext: ```rust // src/utils/auth/crypto/keys.rs pub fn generate_api_key() -> String; // "gw-" + 32 alphanumerics pub fn hash_api_key(api_key: &str, hmac_secret: Option<&str>) -> String; pub fn extract_api_key_prefix(api_key: &str) -> String; // "gw-a...mnop" display only ``` `hash_api_key` computes HMAC-SHA256 when `api_key_hmac_secret` is configured, otherwise plain SHA-256. Argon2 is used only for user passwords (`src/utils/auth/crypto/password.rs`), never for API keys. ### Handler `ApiKeyHandler::new(storage: Arc, hmac_secret: Option)` stores keys through the database layer and looks them up **by full hash** (`find_api_key_by_hash`) — there is no prefix lookup. ```rust impl ApiKeyHandler { pub async fn create_key(&self, user_id: Option, team_id: Option, name: String, permissions: Vec) -> Result<(ApiKey, String)>; // (stored, raw) pub async fn verify_key(&self, raw_key: &str) -> Result)>>; pub async fn verify_key_detailed(&self, raw_key: &str) -> Result; } ``` Verification rejects inactive or expired keys and keys whose owner user is missing/inactive; it refreshes `last_used_at` throttled to once per 5 minutes (`LAST_USED_THROTTLE`) via an in-memory `DashMap` cache. Names must be 1–255 chars without control characters; permissions must be from `VALID_PERMISSIONS` (creation.rs): `*`, `system.admin`, `analytics.read`, `api.chat`, `api.embeddings`, `api.images`, and dotted read/write/delete grants on `users`, `teams`, `api_keys` (e.g. `users.read`, `api_keys.delete`). Management lives on the same handler: `revoke_key`, `list_user_keys`, `update_permissions`, `update_expiration`, `regenerate_key` (returns a new raw key), `cleanup_expired_keys` (`src/auth/api_key/management.rs`). ## JWT Authentication ### Claims Single `role: String` (not a role list), plus token identity fields (`src/auth/jwt/types.rs`): ```rust pub struct Claims { pub sub: Uuid, // user ID pub iat: u64, pub exp: u64, pub iss: String, // fixed "litellm-rs" pub aud: String, // "api" for access, "refresh" for refresh tokens pub jti: String, // UUID token ID pub role: String, pub permissions: Vec, pub team_id: Option, pub session_id: Option, pub token_type: TokenType, // Access | Refresh | PasswordReset | } // EmailVerification | Invitation ``` ### Handler `JwtHandler::new(config: &AuthConfig)` builds HS256 signing keys from `jwt_secret`; lifetime comes from `jwt_expiration`. There is no configurable audience field — audiences are hard-coded per token kind. ```rust impl JwtHandler { pub async fn create_access_token(&self, user_id: Uuid, role: String, permissions: Vec, team_id: Option, session_id: Option) -> Result; pub async fn create_refresh_token(&self, user_id: Uuid, session_id: Option) -> Result; // exp = expiration * 24 pub async fn create_token_pair(...) -> Result; pub async fn verify_access_token(&self, token: &str) -> Result; pub async fn verify_refresh_token(&self, token: &str) -> Result; } ``` For both public creation methods, `team_id` must currently be `None`; passing `Some(...)` returns `BadRequest("Active team selection requires verified membership")`. Team-scoped access tokens and token pairs are created only through the crate-private `create_access_token_for_verified_team` / `create_token_pair_for_verified_team` paths after active membership has been verified. `verify_access_token` enforces `aud == "api"` and rejects unknown team-scope versions; `verify_refresh_token` enforces `aud == "refresh"` plus `token_type == Refresh`. JWT decode failures map to `GatewayError::Auth("JWT error: ...")`. ## References - [reference/middleware-pipeline.md](reference/middleware-pipeline.md) — AuthMiddleware wiring, brute-force lockout, route-level permission checks - [reference/rbac.md](reference/rbac.md) — Permission/Role structs, RbacSystem checks, role inheritance, default roles - [reference/rate-limiting.md](reference/rate-limiting.md) — DashMap RateLimiter strategies, Redis backend, rate-limit key policy - [reference/configuration.md](reference/configuration.md) — flat `auth:` YAML surface, validation rules, top-level `rate_limit:` section - [reference/security-best-practices.md](reference/security-best-practices.md) — secret policy, HMAC key hashing, redaction, audit events, key lifecycle - [reference/error-types.md](reference/error-types.md) — GatewayError variants used across the auth stack and their HTTP mappings