# 13 - Backend Authentication Implementation ## Overview Tenant authentication now uses a hybrid Sanctum setup: - **Tenant Admin PWA** obtains Laravel Sanctum personal access tokens (PATs) via first-party endpoints under `/api/v1/tenant-auth/*`. Tokens carry ability strings (e.g. `tenant-admin`, `tenant:{id}`) and are required for all `/api/v1/tenant/*` routes. - **First-party web flows** (marketing checkout, Filament admin, Google OAuth sign-in) rely on the classic session guard. When a session-based login needs API access (e.g. inside the PWA shell after a Google callback) the frontend exchanges the session for a PAT using the same Sanctum endpoints. - **Legacy compatibility** is preserved through `/api/v1/tenant/me`, which now proxies the Sanctum-backed data while keeping the historical payload shape consumed by older clients. All previous OAuth2/PKCE code paths, tables, and environment variables have been removed. No external authorization server is required. ## Tenant Admin PAT Flow ### Login - **Endpoint**: `POST /api/v1/tenant-auth/login` - **Body** (`application/json`): ```json { "login": "tenant@example.com", "password": "secret" } ``` `login` accepts either an email address or username. Passwords are validated against the user record. - **Response** (`200`): ```json { "token": "plain-text-pat", "token_type": "Bearer", "abilities": ["tenant-admin", "tenant:42"], "user": { "id": 17, "email": "tenant@example.com", "name": "Tenant Admin", "role": "tenant_admin", "tenant_id": 42 } } ``` - **Failure codes**: - `422` with `login` field errors for invalid credentials, unverified email addresses, or roles without tenant access. - `429` throttle when the `tenant-auth` rate limit is exceeded. Every successful login revokes any previous `tenant-admin` PAT for that user before issuing a new token. PATs are plain-text once in the response; the hash is stored in `personal_access_tokens`. ### Token Exchange (session → PAT) - **Endpoint**: `POST /api/v1/tenant-auth/exchange` - **Guards**: `web` session (cookies) with Sanctum stateful middleware. - **Response**: identical payload to `/login`. - **Usage**: The Tenant Admin PWA calls this after a browser session login (e.g. Google Socialite callback or marketing checkout user who opens the PWA shell) to synchronise state without a password prompt. ### Token Introspection & Logout - **`GET /api/v1/tenant-auth/me`** (auth:sanctum) returns the current user, tenant snapshot, and active abilities for the PAT. - **`POST /api/v1/tenant-auth/logout`** invalidates the current PAT and clears it from storage. - **Legacy Proxy**: `/api/v1/tenant/me` now delegates to the new controller to return the historical payload seen by existing tooling while relying on Sanctum for authentication. ### Calling Tenant APIs All tenant APIs continue to require the abilities enforced by middleware: - `auth:sanctum` authenticates the PAT. - `tenant.admin` gate checks that the user has `tenant-admin` or `super-admin` ability. - `tenant.isolation` ensures the `tenant:{id}` ability matches the route tenant, guarding cross-tenant access. Requests must send the PAT in the `Authorization: Bearer {token}` header. Tokens have no server-side expiration (`sanctum.expiration = null`); clients should refresh proactively (e.g. re-login) when a token is revoked or rejected. ## Session-Based Flows ### Marketing Checkout Login - **Endpoint**: `POST /checkout/login` - **Body**: `{ identifier, password, locale? }` - **Remember behaviour**: the checkbox is hidden on the new Tenant Admin login screen but applied automatically – sessions persist via `Auth::login($user, true)`. - Successful logins store a `redirect_intent` in the session when the flow originates from `/event-admin/*`, ensuring post-login navigation goes to `/event-admin/dashboard`. ### Tenant Admin PWA Login Screen - The React shell renders `/event-admin/login` inside the PWA layout. It uses the same credentials form as `/checkout/login` but hits `/api/v1/tenant-auth/login` with fetch requests. - After a successful PAT response, the token is persisted to both `localStorage` and `sessionStorage`; abilities are cached client-side to power feature flags. ### Google Sign-In - Socialite handles the Google redirect/callback under `/event-admin/auth/google`. - On success the user is logged into the web session, their email is marked verified (if needed), and the browser redirects back into the PWA shell. - The shell immediately calls `/api/v1/tenant-auth/exchange` to obtain a PAT, keeping the flow consistent with password logins. - Failures redirect back to the login page with query-string error codes that the PWA surfaces via i18n strings. ## Middleware & Guards | Middleware | Purpose | | --- | --- | | `auth:sanctum` | Validates PATs issued by Sanctum. | | `tenant.admin` | Ensures the authenticated user is a tenant admin or super admin. | | `tenant.isolation` | Loads the tenant model by token ability and adds it to the request for downstream controllers. | | `throttle:tenant-auth` | Rate limits login, exchange, and logout endpoints. | | `EncryptCookies`, `AddQueuedCookiesToResponse`, `StartSession` | Applied to exchange routes so session cookies are available. | ## Security Considerations - PATs are hashed at rest (`personal_access_tokens`), revocation is handled via database deletes. - Tokens are limited to one active entry per user (`tenant-admin` name). Issuing a new token automatically stairs old devices out. - All auth endpoints return generic validation errors to avoid username enumeration. - Session regeneration occurs on every successful login (password or Google) to prevent fixation. - Frontend storage keeps a second copy of the PAT in `sessionStorage` to reduce exposure when a user clears only persistent storage. - Rate limits: configure `tenant-auth` in `RouteServiceProvider` (default `10 requests / minute`). - CSP / XSRF: stateful Sanctum middleware is configured with `SANCTUM_STATEFUL_DOMAINS` so SPA requests inherit `XSRF-TOKEN` cookies automatically. ## Database & Infrastructure - OAuth tables (`oauth_clients`, `oauth_codes`, `refresh_tokens`) have been removed. Sanctum uses the stock `personal_access_tokens` migration. - No background jobs are required for PAT issuance or rotation. - Audit requirements should log `personal_access_tokens` changes via database auditing (e.g. telescope/horizon logs) if needed. ## Configuration & Environment Set the following environment variables to support the hybrid flow: - `SANCTUM_STATEFUL_DOMAINS` – comma-separated list of domains running the Tenant Admin PWA (TWA, Capacitor, staging). Ensures cookies are considered first-party for the exchange endpoint. - `SANCTUM_TOKEN_PREFIX` – optional prefix to enable secret-scanning detection. - Remove the deprecated OAuth variables: - `VITE_OAUTH_CLIENT_ID` - `OAUTH_JWT_KID` - `OAUTH_KEY_STORE` - `OAUTH_REFRESH_*` Google OAuth credentials (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`) remain for Socialite but only power the browser session login; token exchange happens through Sanctum. ## Testing - Feature tests cover `/api/v1/tenant-auth/login`, `/exchange`, and `/logout` (`tests/Feature/Auth/TenantProfileApiTest.php`, `tests/Feature/TenantCreditsTest.php`). - Frontend Playwright fixtures (`tests/e2e/utils/test-fixtures.ts`) provide helpers to obtain PATs when seeding test users. - When adding new tenant API endpoints ensure coverage under Sanctum by using `->actingAs($user, 'sanctum')` in PHPUnit tests and by asserting abilities with `Sanctum::actingAs()` helpers where required. This Sanctum-based approach keeps login logic inside the Laravel application, avoids custom OAuth infrastructure, and works uniformly across web, PWA, and the planned React Native wrapper.