Client Authentication
Passwordless authentication for clients to access their bookings via the client portal. Uses separate token systems for server-to-server (Internal API) and client session (Sanctum) authentication.
Architecture
Section titled “Architecture”+-------------------+ +-------------------+ +-------------------+| Astro Frontend | | Laravel Backend | | Client || (SSR Server) | | | | (Browser) |+-------------------+ +-------------------+ +-------------------+ | | | | INTERNAL_API_TOKEN | | | (server-to-server) | | |----------------------------->| | | | | | | Sanctum Token | | | (client session) | | |<-----------------------------| | | |Two Authentication Systems
Section titled “Two Authentication Systems”| System | Token Type | Use Case | Stored In |
|---|---|---|---|
| Internal API | Sanctum (internal:read) | Astro SSR fetching protected data | Frontend .env |
| Client Portal | Sanctum (client:read) | Client viewing their bookings | Astro session cookie |
Internal API Token
Section titled “Internal API Token”Server-to-server authentication between Astro SSR and Laravel. A single token shared by all Astro instances.
When to use: SSR pages that need backend data not exposed publicly (e.g., booking details page rendering on server).
Source: frontend/src/shared/config/internalApiClient.ts
Client Sanctum Token
Section titled “Client Sanctum Token”Per-client session token issued after magic link verification. Stored in Astro server-side session, never exposed to browser.
When to use: Client-specific API calls (list bookings, view booking details).
Source: frontend/src/features/client-auth/services/clientAuthApi.ts
Magic Link Flow
Section titled “Magic Link Flow”1. Client enters email on /login | v2. POST /api/client/auth/magic-link - Validates email exists, client active, has bookings - Generates 64-char random token - Stores in cache: client_magic_link:{client_id}:{token} (30 min TTL) - Sends email with encoded link - Always returns success (prevents enumeration) | v3. Client clicks email link -> /auth/verify?token={encoded} | v4. POST /api/client/auth/verify - Decodes base64 payload (client_id, token, expires_at) - Validates cache key exists (single-use) - Creates Sanctum token with 'client:read' ability (7 days) - Deletes cache key (invalidates magic link) - Returns token + client data | v5. Astro stores in server session: - client: { id, name, email } - clientToken: Sanctum token - clientTokenExpiresAt: expiry timestamp | v6. Subsequent requests: Astro session cookie identifies client - Protected routes check session validity - API calls use stored Sanctum tokenConfiguration
Section titled “Configuration”Backend Setup
Section titled “Backend Setup”No additional .env configuration needed. Magic link uses Laravel cache and Sanctum defaults.
Required seeder (run once):
./vendor/bin/sail artisan db:seed --class=ServiceUserSeederThis creates the service user (frontend-service@volare.internal) used for Internal API tokens.
Frontend Setup
Section titled “Frontend Setup”Generate and configure the Internal API token:
# In backend directory./vendor/bin/sail artisan internal:generate-tokenAdd output to frontend/.env:
INTERNAL_API_TOKEN=1|abc123...xyz789The token is defined in frontend/astro.config.mjs as a server-side secret:
env: { schema: { INTERNAL_API_TOKEN: envField.string({ context: "server", access: "secret", optional: true, }), },},Rate Limiting
Section titled “Rate Limiting”| Endpoint | Limit | Purpose |
|---|---|---|
POST /api/client/auth/magic-link | 5/min | Prevent email spam |
POST /api/client/auth/verify | 10/min | Allow retries |
| Client protected routes | 60/min | Normal API usage |
| Internal API routes | 120/min | SSR rendering |
API Endpoints
Section titled “API Endpoints”Request Magic Link
Section titled “Request Magic Link”POST /api/client/auth/magic-linkRequest: See MagicLinkRequest for validation.
Response (always 200):
{ "success": true, "message": "If an account exists with this email and has bookings, you will receive a login link shortly."}Security: Always returns success to prevent email enumeration.
Source: backend/app/Http/Controllers/Api/Client/AuthController.php:29
Verify Magic Link
Section titled “Verify Magic Link”POST /api/client/auth/verifyRequest: See VerifyMagicLinkRequest for validation.
Response (success):
{ "success": true, "data": { "client": { "id": 1, "name": "John", "email": "john@example.com" }, "token": "1|abc...", "expires_at": "2024-01-15T12:00:00.000Z" }}Error codes: invalid_token, expired_token, client_not_found
Source: backend/app/Http/Controllers/Api/Client/AuthController.php:65
List Client Bookings
Section titled “List Client Bookings”GET /api/client/bookingsAuthorization: Bearer {client_token}Response: Paginated list of client’s bookings.
Source: backend/app/Http/Controllers/Api/Client/BookingsController.php:20
Get Booking Details
Section titled “Get Booking Details”GET /api/client/bookings/{reference}Authorization: Bearer {client_token}Response: Full booking details for the authenticated client.
Source: backend/app/Http/Controllers/Api/Client/BookingsController.php:50
Logout
Section titled “Logout”POST /api/client/auth/logoutAuthorization: Bearer {client_token}Revokes the current Sanctum token.
Source: backend/app/Http/Controllers/Api/Client/AuthController.php:136
Security Considerations
Section titled “Security Considerations”Magic Link Security
Section titled “Magic Link Security”- Single-use tokens: Cache key deleted after verification
- 30-minute expiry: Embedded in payload and cache TTL
- No email enumeration: Identical response for valid/invalid emails
- Client validation: Must be active with at least one booking
Token Security
Section titled “Token Security”- Internal API token: Never exposed to browser (server-side only)
- Client tokens: Stored in httpOnly session cookie
- Token abilities: Scoped (
internal:readvsclient:read) - 7-day expiry: Matches Astro session cookie maxAge
Frontend Middleware
Section titled “Frontend Middleware”Protected routes and auth redirects in frontend/src/middleware.ts:
const PROTECTED_ROUTES = ["/my-account"];const AUTH_ROUTES = ["/login"];- Unauthenticated users redirected from
/my-accountto/login - Authenticated users redirected from
/loginto/my-account - Session validated on every request (checks token expiry)
Token Generation Commands
Section titled “Token Generation Commands”Generate Internal API Token
Section titled “Generate Internal API Token”./vendor/bin/sail artisan internal:generate-token
# Revoke existing tokens first./vendor/bin/sail artisan internal:generate-token --revokeSource: backend/app/Console/Commands/GenerateInternalApiToken.php
| File | Purpose |
|---|---|
backend/app/Http/Controllers/Api/Client/AuthController.php | Magic link and session management |
backend/app/Http/Controllers/Api/Client/BookingsController.php | Client booking endpoints |
backend/app/Notifications/ClientMagicLinkNotification.php | Magic link email template |
backend/app/Console/Commands/GenerateInternalApiToken.php | Token generation command |
backend/database/seeders/ServiceUserSeeder.php | Creates frontend service user |
frontend/src/shared/config/internalApiClient.ts | Internal API client (SSR) |
frontend/src/features/client-auth/services/clientAuthApi.ts | Client auth API calls |
frontend/src/middleware.ts | Route protection and redirects |
backend/routes/api.php | Route definitions (lines 108-148) |