Auth

Operator sessions and step-up

How Console and management routes exchange a PAT for a server-owned session, verify it, and require fresh proof for high-impact actions.
Provisional Ledger class: provisional
Verified 2026-10-21Session routes are mounted behind a default-off feature flag; hosted Console integration is not accepted

IBEX does not put a PAT in browser storage for Console use. The operator flow exchanges a PAT for short-lived, server-owned cookies, then uses a separate step-up token for actions that need fresh proof. The session machinery is implemented locally; hosted identity, origin, and Console integration remain gated.

Flow

PAT → POST /v1/operator/session/login
    → HttpOnly access cookie
    → HttpOnly refresh cookie
    → JS-readable CSRF cookie
    → POST /refresh rotates access (and, with Auth, the refresh family)
    → POST /logout revokes the session family and clears cookies

The browser never receives the upstream PAT from these responses, and the API never treats cookie presence alone as authorization.

Routes

All routes are under /v1/operator/session and return 503 SERVICE_DEGRADED when IBEX_OPERATOR_FEATURE_ENABLED is false.

MethodPathInputBehavior
POST/loginJSON { "pat": "ibex_pat_..." }Validates the PAT, issues access/refresh cookies, sets a CSRF cookie
POST/refreshRefresh cookieRotates access; in Auth-owned mode also rotates the refresh token
POST/logoutAccess and/or refresh cookieRevokes the session family when Auth-owned, then clears cookies
GET/meAccess cookie or bearer fallbackReturns the verified session subject and organization

Login accepts a PAT in the request body once. Do not persist that PAT in browser storage, URLs, or logs.

Issuance modes

EnvironmentSigning and verificationStatus
DevelopmentHS256 via JWT_HMAC_SECRET; access 1 hour by defaultProvisional compatibility stub
Staging/productionAuthService issues RS256 access/refresh; API verifies with DASHBOARD_JWT_PUBLIC_KEYS_PEMImplemented local path; hosted Console evidence remains open

AuthService defaults are 15-minute access, 7-day refresh, and 5-minute step-up tokens. The API's development stub has different defaults because it is a local convenience path, not the production issuer. Do not treat the two TTL sets as interchangeable.

The API explicitly does not accept JWT_PRIVATE_KEY_PEM as HMAC material. Signing keys stay in AuthService; the API only holds public verification keys.

Cookies and CSRF

CookieHttpOnlyPurpose
ibex_sessionYesShort-lived access token
ibex_refreshYesRefresh token
ibex_csrfNo; deliberately readable by the ConsoleDouble-submit CSRF token

Cookie security is deployment-owned:

  • DASHBOARD_COOKIE_SECURE=true is required outside development.
  • DASHBOARD_COOKIE_SAMESITE is lax, strict, or none; none requires Secure.
  • DASHBOARD_COOKIE_DOMAIN is set only when the UI and API intentionally share a parent domain.
  • CORS uses an explicit IBEX_ALLOWED_ORIGINS allow-list. Credentialed CORS with * is rejected.

Cookie-authenticated mutations require a matching X-CSRF-Token; bearer-PAT callers are exempt. Login is CSRF-exempt because it establishes the session.

TOTP and step-up

AuthService owns the TOTP primitives:

RPCPurpose
BeginTotpEnrollmentStart enrollment and return an otpauth:// URI
ConfirmTotpEnrollmentConfirm enrollment with a code
CreateStepUpTokenVerify TOTP and mint a short-lived step-up JWT
IssueOperatorSessionMint RS256 access/refresh JWTs for an authenticated caller
RevokeOperatorSessionInvalidate the session and its refresh family

TOTP is disabled by default (IBEX_AUTH_TOTP_ENABLED=false). When enabled, AuthService requires JWT_PRIVATE_KEY_PEM, JWT issuer/audience, and Redis so TOTP attempt lockouts are shared across replicas. TOTP secrets are sealed with the credentials master key.

High-impact API actions can require X-IBEX-Step-Up. The proof must bind to the same organization, subject, and session, and AuthService consumes the step-up JTI before the mutation so replay fails. Missing, expired, wrong-org, or replayed proof is denied without leaking whether the target exists. This is an implemented seam, not evidence that every privileged Console action has an accepted UI and audit flow.

Configuration

VariablePurpose
IBEX_OPERATOR_FEATURE_ENABLEDKill switch for operator session and SSE routes; default false
JWT_ISSUER / JWT_AUDIENCESession claim validation
JWT_ACCESS_TOKEN_TTL / JWT_REFRESH_TOKEN_TTL / JWT_STEP_UP_TOKEN_TTLAuthService-issued TTLs
JWT_PRIVATE_KEY_PEMAuthService RS256 signing key; never given to the API
DASHBOARD_JWT_PUBLIC_KEYS_PEMAPI verification key set
JWT_HMAC_SECRETDevelopment-only provisional HS256 material
DASHBOARD_CSRF_SECRETCSRF double-submit key; required outside development
IBEX_ALLOWED_ORIGINSExact Console origin allow-list
IBEX_AUTH_TOTP_ENABLEDEnables TOTP and step-up RPCs

Failure behavior

ConditionResult
Operator feature disabled503 SERVICE_DEGRADED before route logic
Invalid/revoked PAT at login401 INVALID_TOKEN
AuthService unavailable during login/refresh503 SERVICE_DEGRADED; do not fall back to unsigned cookies
Missing refresh cookie401 INVALID_TOKEN
Access and refresh proofs bind different sessionsLogout fails closed and clears cookies
Step-up missing, stale, replayed, or wrong subjectDeny; no mutation
Public keys missing outside development503 SERVICE_DEGRADED

What remains open

  • Console login, enrollment, invites, and mutation UI are not accepted.
  • Hosted AuthService/session topology, deployment origin, and credentialed CORS/CSRF evidence are incomplete.
  • Two-tenant negative tests, session revocation drills, and rollback runbooks are still required for hosted promotion.
  • The development HS256 path is a compatibility stub and must not become the production issuer.