Skip to content

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.

+-------------------+ +-------------------+ +-------------------+
| Astro Frontend | | Laravel Backend | | Client |
| (SSR Server) | | | | (Browser) |
+-------------------+ +-------------------+ +-------------------+
| | |
| INTERNAL_API_TOKEN | |
| (server-to-server) | |
|----------------------------->| |
| | |
| | Sanctum Token |
| | (client session) |
| |<-----------------------------|
| | |
SystemToken TypeUse CaseStored In
Internal APISanctum (internal:read)Astro SSR fetching protected dataFrontend .env
Client PortalSanctum (client:read)Client viewing their bookingsAstro session cookie

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

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

1. Client enters email on /login
|
v
2. 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)
|
v
3. Client clicks email link -> /auth/verify?token={encoded}
|
v
4. 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
|
v
5. Astro stores in server session:
- client: { id, name, email }
- clientToken: Sanctum token
- clientTokenExpiresAt: expiry timestamp
|
v
6. Subsequent requests: Astro session cookie identifies client
- Protected routes check session validity
- API calls use stored Sanctum token

No additional .env configuration needed. Magic link uses Laravel cache and Sanctum defaults.

Required seeder (run once):

Terminal window
./vendor/bin/sail artisan db:seed --class=ServiceUserSeeder

This creates the service user (frontend-service@volare.internal) used for Internal API tokens.

Generate and configure the Internal API token:

Terminal window
# In backend directory
./vendor/bin/sail artisan internal:generate-token

Add output to frontend/.env:

Terminal window
INTERNAL_API_TOKEN=1|abc123...xyz789

The 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,
}),
},
},
EndpointLimitPurpose
POST /api/client/auth/magic-link5/minPrevent email spam
POST /api/client/auth/verify10/minAllow retries
Client protected routes60/minNormal API usage
Internal API routes120/minSSR rendering
POST /api/client/auth/magic-link

Request: 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

POST /api/client/auth/verify

Request: 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

GET /api/client/bookings
Authorization: Bearer {client_token}

Response: Paginated list of client’s bookings.

Source: backend/app/Http/Controllers/Api/Client/BookingsController.php:20

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

POST /api/client/auth/logout
Authorization: Bearer {client_token}

Revokes the current Sanctum token.

Source: backend/app/Http/Controllers/Api/Client/AuthController.php:136

  • 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
  • Internal API token: Never exposed to browser (server-side only)
  • Client tokens: Stored in httpOnly session cookie
  • Token abilities: Scoped (internal:read vs client:read)
  • 7-day expiry: Matches Astro session cookie maxAge

Protected routes and auth redirects in frontend/src/middleware.ts:

const PROTECTED_ROUTES = ["/my-account"];
const AUTH_ROUTES = ["/login"];
  • Unauthenticated users redirected from /my-account to /login
  • Authenticated users redirected from /login to /my-account
  • Session validated on every request (checks token expiry)
Terminal window
./vendor/bin/sail artisan internal:generate-token
# Revoke existing tokens first
./vendor/bin/sail artisan internal:generate-token --revoke

Source: backend/app/Console/Commands/GenerateInternalApiToken.php

FilePurpose
backend/app/Http/Controllers/Api/Client/AuthController.phpMagic link and session management
backend/app/Http/Controllers/Api/Client/BookingsController.phpClient booking endpoints
backend/app/Notifications/ClientMagicLinkNotification.phpMagic link email template
backend/app/Console/Commands/GenerateInternalApiToken.phpToken generation command
backend/database/seeders/ServiceUserSeeder.phpCreates frontend service user
frontend/src/shared/config/internalApiClient.tsInternal API client (SSR)
frontend/src/features/client-auth/services/clientAuthApi.tsClient auth API calls
frontend/src/middleware.tsRoute protection and redirects
backend/routes/api.phpRoute definitions (lines 108-148)