Skip to content
VerifAIer
Home / Docs / API / Auth, tiers & headers
API · Security

Authentication, tiers & headers

Three tiers, two credential types, one honest model. This is session + API-key auth for local-first deployment, not a hosted OAuth/SSO product.

Authentication model

VerifAIer uses two credential types, chosen by tier. Both are local-first primitives, there is no hosted OAuth, OIDC, or SSO integration in the current implementation.

auth/config.py · auth/guards.py · auth/service.py · api/auth_routes.py · cc/auth.py

Session tokens (browser + API clients)

POST /api/auth/login exchanges email + password for a session. The session token is an HMAC keyed by SESSION_SECRET with an 8-hour default TTL (AUTH_TOKEN_TTL=28800). Two transports are accepted, cookie first:

  1. vai_session cookie: set by login, HttpOnly; Secure in production; SameSite=Lax. Browser clients use this automatically. A non-HttpOnly vai_session_active=1 flag cookie lets JS detect a session without reading the token.
  2. X-Session-Token header: for API clients, the CLI and the MCP server. Backwards-compatible; carries the raw token returned in the login response body.
sequenceDiagram
  autonumber
  participant C as Client
  participant API as /api/auth/login
  participant RL as RateLimiter
  participant S as session service
  C->>API: POST {email, password}
  API->>RL: is_allowed(login:IP, 20, 900s)
  alt over limit
    RL-->>C: 429 Too many login attempts
  else allowed
    API->>S: authenticate_user(email, password)
    alt bad credentials
      S-->>C: 401 Invalid credentials
    else ok
      S->>S: create_session(user_id) → HMAC token
      API-->>C: 200 {session_token, ...} + Set-Cookie vai_session (HttpOnly)
    end
  end
Login flow. Source: api/auth_routes.py + auth/service.py + security/rate_limit.py.
Env varDefaultMeaning
SESSION_SECRETephemeral per-processHMAC key. Required in production: if unset, sessions do not survive restarts, and the app refuses to start when AUTH_COOKIE_SECURE=true.
AUTH_COOKIE_NAMEvai_sessionsession cookie name
AUTH_TOKEN_TTL28800session lifetime, seconds (8h)
AUTH_COOKIE_SECUREfalseset true on HTTPS/production
AUTH_COOKIE_SAMESITElaxlax · strict · none

API keys (control tier)

The Control Center data endpoints (/api/control/*) authenticate with an X-API-Key header. Keys are formatted vrf_<8-hex-prefix>_<secret>, stored as PBKDF2-HMAC-SHA256 hashes (100k iterations) with a per-key salt, and scoped to an organization. The prefix is a fast DB lookup index; the full key is shown once at creation and never stored. Provision one with the owner bootstrap script (scripts/cc_bootstrap_owner.pycreate_org_and_key).

Not production SaaS auth (yet). This is a self-hosted authentication system: HMAC sessions, PBKDF2 passwords and PBKDF2 API keys, all local. There is no OAuth 2.0 / OIDC provider, no SSO, no MFA, and no external identity provider in the current code. Treat the deployment gateway (reverse proxy / IdP) as the place to add federated SSO if you need it.

API tiers

TierPrefixesAuth401 when…
open/api/v1/*, /health, /validate, /pipeline, /dev/audit, /sentinel/verifynonenever (open)
session/api/auth/*, /api/vai/*, /api/orgs/*, /api/observability/*, /api/security/*, /api/billing/*vai_session cookie or X-Session-Tokenno/invalid/expired session
API key/api/control/* (data endpoints)X-API-Key: vrf_…missing/invalid key (403 if revoked)

The Control Center also exposes a session-based console subset (/api/control/auth/login, /auth/me, /auth/logout and some member/settings management) that binds an org membership; those use org-membership sessions rather than an API key. See the authenticated APIs.

Authorization model

Authorization today is deliberately coarse and honest about its scope:

  • Session tier, authentication-gated, no role enforcement. The require_authenticated_user guard accepts any valid session ("No role enforcement, all authenticated users are accepted"). The AuthenticatedUser record carries organization_id, membership_id and role fields, but for the current session path they are empty strings, reserved for future RBAC gates, not yet enforced.
  • API-key tier, org-scoped. A validated X-API-Key resolves to an org_id; control-center data is scoped to that organization.
  • Billing, org-access checks. /api/billing/* adds an explicit _require_org_access(org_id, user) check on top of authentication.
  • Control console, role-aware. A subset of /api/control/* uses require_admin_session / require_user_session (org-membership sessions) where an admin vs. member distinction applies.
Truthful limitation. Do not assume fine-grained RBAC on the session tier, it is not implemented. If you need per-resource authorization now, enforce it at your gateway or restrict which tiers you expose.

Headers

Request headers

HeaderWhenNotes
Content-Type: application/jsonall POST/PATCHbodies are JSON
Cookie: vai_session=…session tier, browserset automatically after login
X-Session-Token: <token>session tier, API/CLI/MCPraw token from login response
X-API-Key: vrf_<prefix>_<secret>control tierorg-scoped PBKDF2 key

Response headers

Every response carries hardening headers set by _SecurityHeadersMiddleware (with setdefault, so handlers can override but never lose them):

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin

CORS is applied by CORSMiddleware: origins from CORS_ORIGINS (JSON array, default ["*"] for the extension + dev dashboard), credentials disabled, methods limited to GET, POST, DELETE, OPTIONS. Restrict CORS_ORIGINS in production.

Security considerations

ConcernHow it is handled
Provider secretsread from server env at call time; never accepted from or returned to callers; config stores presence booleans only. See security boundaries.
Credential oracleslogin returns the same 401 for unknown email vs. wrong password; API-key validation returns the same 401 for bad format vs. bad value, no enumeration.
Password / key storagePBKDF2-HMAC-SHA256 with per-record salt; constant-time comparison (hmac.compare_digest).
Session cookiesHttpOnly; Secure in production; SameSite=Lax; short TTL; revocable.
Login abuseper-IP rate limit → 429 (see rate limits).
Startup gatesthe app refuses to start when SESSION_SECRET is ephemeral and AUTH_COOKIE_SECURE=true (production posture); soft-secret warnings otherwise.
Prompts / inputsnever logged, never echoed into evidence, diagnostics or responses.