130 lines
7.7 KiB
Markdown
130 lines
7.7 KiB
Markdown
# 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.
|