---
summary: The backend-orchestrated OIDC login flow and the session token.
title: Login (OAuth + PKCE)
path: authentication/login
status: published
---

# Login

ScaiCrew uses a **backend-orchestrated** OAuth Authorization-Code + PKCE flow against ScaiKey's
platform OIDC. The browser never touches the token endpoint — the backend is the confidential client
and mints its own session token once login succeeds.

## The flow

| Step | Endpoint | What happens |
|---|---|---|
| 1 | `POST /v1/auth/login` | Backend creates `state` + PKCE, stashes them, returns ScaiKey's authorize URL. |
| 2 | _(browser)_ | The SPA redirects to ScaiKey; the user authenticates. |
| 3 | `GET /v1/auth/callback` | ScaiKey redirects here; the backend exchanges the code (with the client secret + PKCE verifier), resolves the user's workspaces, and bounces the browser to the SPA with a one-shot `session_code`. |
| 4 | `POST /v1/auth/token` | The SPA redeems the `session_code`. One workspace → a session token; many → a `requires_selection` list, then a second call with the chosen `workspace_id`. |
| 5 | `POST /v1/auth/refresh` | Re-mints a session token from the refresh token, **re-reading the role from the database** so grant changes take effect without re-login. Also how workspace switching works. |

## The session token

The token the SPA presents to `/v1` on every call is **ScaiCrew's own** HS256 token, not ScaiKey's.
It carries the resolved `(subject, tenant, workspace, role)`, so the API reads scope and role
directly from the verified token — no per-request membership lookup. The middleware verifies this
token first and falls back to ScaiKey's JWKS for service / per-member tokens.

## App registration

The backend authenticates as a **GLOBAL confidential WEB app** in ScaiKey (the same app used for
service-to-service `client_credentials`). It must have:

- the OAuth callback registered as a redirect URI (e.g. `https://<host>/v1/auth/callback`),
- the `authorization_code` and `refresh_token` grants,
- the `openid profile email` scopes.

## Dev mode

For local development without OIDC, the backend accepts `X-Dev-Workspace-Id` / `X-Dev-Subject` /
`X-Dev-Role` headers instead of a bearer token. This path is **only** active when the service runs
with `ENV=dev`; in production it is disabled entirely.
