# Authentication ## Cookie session (live path) The API is a same-origin BFF. Cognito hosted UI issues tokens. The API stores them in host-only cookies: - `__Host-ap_at` access token (HttpOnly) - `__Host-ap_it` ID token (HttpOnly) - `__Host-ap_rt` refresh token (HttpOnly, path `/` when host-prefixed) - `__Host-ap_sess` session hint (not HttpOnly; display name and email) - `__Host-ap_oauth` PKCE state during login Local `NODE_ENV` `development` or `test` drops the `__Host-` prefix and the Secure flag so `http://127.0.0.1:8787` works (`ap_at`, `ap_it`, `ap_rt`). Routes: - `GET /api/auth/login` - `GET /api/auth/callback` - `POST /api/auth/refresh` - `POST /api/auth/logout` - `GET /api/me` reads the ID cookie, verifies it, and upserts `users` by `sub` CloudFront sends `X-Origin-Verify` on `/api/*`. `GET /api/health` skips that check so the ALB probe succeeds. Mutating `/api/*` requests also require a matching `Origin`. ## Cognito tokens Audience check: - ID tokens (`token_use=id`): `aud` must equal `COGNITO_AUDIENCE` (app client id). - Access tokens (`token_use=access`): `client_id` must equal `COGNITO_AUDIENCE`. Identity claims (`sub`, `email`, and `name` or `cognito:username`) are required. `GET /api/me` uses the ID cookie. Optional role claim mapping: - `custom:role` or `role` → `role` (`admin`, `ap_processor`, `approver`, `viewer`) - Missing role defaults to `viewer` Users are upserted by `sub` only. An email already linked to a different `sub` returns HTTP 409 and does not rebind the account. ## Local DEV_AUTH_BYPASS For local development only, set `DEV_AUTH_BYPASS=true`. The middleware uses `DEV_AUTH_SUB`, `DEV_AUTH_EMAIL`, `DEV_AUTH_NAME`, and `DEV_AUTH_ROLE` and skips JWT verification. Align `DEV_AUTH_SUB` with the seeded Cognito subject (default `seed-sub-admin`) so local auth updates the seed user instead of conflicting on email. `DEV_AUTH_BYPASS` is allowed only when `NODE_ENV` is `development` or `test`. Any other value (including `production`, `staging`, and `preview`) rejects startup.