# Authentication and identity design

The gateway separates three credentials: a **virtual key** authorizes application requests, an **administrator login** authorizes management, and an **upstream provider credential** pays for inference. A caller with one does not automatically gain the privileges of another.

![Authentication design: validate the virtual key, resolve its identity through caches and PostgreSQL, check active parents and expiry, then derive request permissions](authentication.svg)

## Why this design

Virtual keys let an operator revoke one integration, attribute usage, restrict models and apply budgets without distributing provider secrets. The gateway stores an HMAC-SHA256 digest under an operator-held pepper; the raw key is returned once at creation. Its structured format and checksum detect malformed or wrong-environment input before database work. The checksum is a format check, not authentication.

## Data-plane flow

1. The `/v1/**` filter extracts the bearer key. Missing credentials are refused when virtual keys are required. An optional local trial mode permits a shared anonymous identity; a present invalid key never silently becomes anonymous.
2. Validate format, checksum and environment, then resolve the identity once for the request.
3. Resolve through a short-lived process cache, optional Redis metadata cache, and PostgreSQL. Redis cache failure falls back to the remaining identity sources; an authoritative store failure is not an invalid-key verdict.
4. Check the key, referenced parents, owning account when applicable, and its expiry against the clock. An expiry deadline is checked on each authentication even when metadata came from a cache.
5. Build the effective model allow set and scope path. Downstream admission uses this identity; it does not trust a tenant name supplied by the application.

## Authorization and hierarchy

Scoped grants already decide administrative authorization by default (`POINTSMAN_AUTHZ_CORE_DECIDES=true`), with a documented compatibility fallback for existing gates. Application requests still use the fixed organization/application/user/key policy path (`POINTSMAN_DATAPATH_TREE_ENABLED=false`). These switches govern different decisions. Creating a scope or viewing a policy preview does not enable tree enforcement.

Read [policy-based authorization](authorization.md) for the architecture, scopes, grant roles, delegation, inheritance, model-policy composition, subject limitations and failure behavior. Grants combine by strongest role; model permissions combine by intersection. The Python routing learner is a separate subsystem.

Model access is narrowed by the effective allow set and registry lifecycle/capabilities. Empty permission is a denial. Key-owned restrictions and inherited restrictions are distinct; use effective-policy reads when checking which rule actually binds.

Admin sessions and HTTP Basic administration use a separate security chain. Session-based mutations require CSRF protection, and authorization is checked server-side. Hiding an action in the dashboard is not an access-control boundary. Provider credentials are resolved server-side for the selected deployment/credential member and are not substitutes for a virtual key.

## State and consistency

| State | Owner | Consistency consequence |
|---|---|---|
| Key digest, parents, expiry and grants | PostgreSQL | Durable authority; pepper must be stable and protected |
| Identity metadata | Process cache and optional Redis | TTLs and post-commit invalidation bound staleness; not instant global revocation |
| Scope projection and policy changes | Administrative transaction and change notifications | A failed transactional projection must not leave a partially applied identity change |
| Rotation window | Stored expiry/rotation metadata | Old keys work only through the configured grace deadline; rotation headers tell clients to migrate |

## Failure behavior

| Condition | Behavior |
|---|---|
| Malformed, unknown, inactive, expired or wrong-environment key | 401 |
| Identity lookup cannot establish an authoritative answer | 503; do not tell a valid customer their credential is wrong |
| Valid identity requests an unauthorized model or admin target | Denial at the relevant authorization boundary |
| Redis identity cache unavailable | Fall back to process cache/PostgreSQL; not a blanket authentication bypass |
| Missed invalidation | Cached metadata may remain stale until its TTL; expiry is still checked against time |

## Operating and verifying it

Require virtual keys on exposed deployments, terminate TLS at a trusted boundary, preserve the pepper across restarts, and keep admin/session access separate from application traffic. Check active-parent changes, revocation, rotation, expiry, cross-tenant requests and effective model permissions. Do not place real keys in documentation, browser examples or log searches.

Implementation anchors: `identity/ApiKeyAuthFilter`, `IdentityResolver`, `KeyFormat`, `IdentityAdminService`, `EffectiveAllowSetCalculator`, `core/authz` and `config/SecurityConfig` under the gateway source. Use [key and budget setup](../how-to/issue-keys-and-set-budgets.md), [team hierarchy](../how-to/build-a-team-hierarchy.md), [security](../security.md) and [configuration reference](../reference/configuration.md) for supported settings and commands.
