Skip to content

Authentication

Every route requires a session, established one of three ways: OIDC (the production path — bring your own identity provider), a break-glass password login for the one account defined directly in the environment, or an API key for a script or service. This page assumes you already know what OIDC is; if you don’t, your identity provider’s own docs are the right place to start, not here.

flowchart LR
    oidc["OIDC login"] --> cookie
    bootstrap["Break-glass password"] --> cookie
    apikey["API key\n(Bearer token)"] -.->|"no cookie —\nchecked per request"| ok
    cookie["Session cookie\n(signed JWT)"] --> ok["Authenticated request"]

Whichever path is used, the result funnels down to the same thing: a signed, stateless JWT in an HttpOnly cookie, or a Bearer token checked on each request for API keys. GET /auth/me reads the cookie back; POST /auth/logout clears it. The frontend calls GET /auth/config to know whether OIDC is even set up — an unconfigured discovery URL hides the login button entirely rather than showing one that would just error.

Logging in never writes a row, and checking a session never round-trips to Postgres — the cookie is verified by signature alone. The cost is revocation: there’s no “log this one session out” lever, only the signing secret itself, rotating which invalidates every session at once. For a deployment where the identity provider is the real source of truth on who’s allowed in, that trade reads as the right one: the rare “kick everyone out now” case is handled, and the common case pays no database cost at all.

Two different password hashes, for two different threat models

Section titled “Two different password hashes, for two different threat models”

A principal’s secret — a bootstrap password or an API key — is stored as one of two different hash formats, picked by which kind of secret it is:

  • A human password is hashed with bcrypt: salted and deliberately slow, because a person-chosen password is comparatively low-entropy and has to resist offline brute-force guessing.
  • An API key is hashed with a plain SHA-256 digest: unsalted and fast, because the key itself is a 32-byte random token — high-entropy enough that brute force isn’t the risk — and a plain digest is what makes an indexed lookup possible when a Bearer token arrives with no username attached to tell Atlas which principal to check it against first.

Only two OIDC claims are singled out by name — everything else your provider returns (email, groups, department, whatever) is stored as-is on the principal and available to authorization’s matching rules with no extra setup:

Terminal window
ATLAS_AUTH_OIDC_ID_CLAIM=sub # becomes the principal's id
ATLAS_AUTH_OIDC_NAME_CLAIM=name # becomes its display name

Changing ATLAS_AUTH_OIDC_ID_CLAIM after real users already exist orphans every principal created under the old claim — treat it as fixed once you’ve gone live.

Minimum to turn the login button on:

Terminal window
ATLAS_AUTH_OIDC_DISCOVERY_URL=https://idp.example.com/realms/atlas/.well-known/openid-configuration
ATLAS_AUTH_OIDC_CLIENT_ID=atlas
ATLAS_AUTH_OIDC_CLIENT_SECRET=...

A fuller configuration — matching roles against an IdP-managed groups claim, and being explicit about claim mapping and PKCE rather than relying on the defaults:

Terminal window
ATLAS_AUTH_OIDC_DISCOVERY_URL=https://idp.example.com/realms/atlas/.well-known/openid-configuration
ATLAS_AUTH_OIDC_CLIENT_ID=atlas
ATLAS_AUTH_OIDC_CLIENT_SECRET=...
ATLAS_AUTH_OIDC_ID_CLAIM=preferred_username
ATLAS_AUTH_OIDC_NAME_CLAIM=name
ATLAS_AUTH_OIDC_SCOPE=openid profile email groups
ATLAS_AUTH_OIDC_ENABLE_PKCE=true
ATLAS_ALLOW_SELF_REGISTRATION=false
ATLAS_AUTH_FRONTEND_URL=https://atlas.example.com

ATLAS_ALLOW_SELF_REGISTRATION=false means an admin must create a principal (or a role’s grant_match must already cover them, e.g. by group) before that identity can log in at all; true auto-provisions on first login instead. See CORS & Authentication for the full variable reference.

Terminal window
curl -c cookies.txt -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "changeme"}'
curl -b cookies.txt http://localhost:8000/api/v1/workspaces