Trip Access Authentication
Passwordless authentication for passengers to access their trip details via personalized magic links. Allows travel agents to share trip information directly with passengers, independent of the client authentication system.
Architecture
Section titled “Architecture”+-------------------+ +-------------------+ +-------------------+| Filament Admin | | Laravel Backend | | Astro Frontend || (Backoffice) | | | | (Trip Page) |+-------------------+ +-------------------+ +-------------------+ | | | | "Share Trip with Pax" | | |----------------------------->| | | | | | | Magic Link Email | | |----------------------------->| | | | | | Verify Token | | |<-----------------------------| | | | | | Session + Redirect | | |----------------------------->| | | |Difference from Client Authentication
Section titled “Difference from Client Authentication”| Aspect | Client Auth | Trip Access |
|---|---|---|
| Target user | Clients (booking owners) | Passengers (travelers) |
| Scope | All client bookings | Single booking only |
| Token storage | clients + Sanctum | Sanctum personal_access_tokens (polymorphic) |
| Token owner | Client model | Passenger model |
| Duration | 7 days | 90 days (default) |
| Use case | Client portal | Trip itinerary page |
Magic Link Flow
Section titled “Magic Link Flow”1. Agent clicks "Share Trip with Pax" in Filament booking view | v2. Agent selects passengers (checkbox list) - Only passengers with email addresses shown - All selected by default - Lead passenger marked with badge | v3. For each selected passenger: - TripAccessTokenService creates Sanctum token - Previous tokens for same booking revoked - TripAccessNotification queued - Email sent with HMAC-signed magic link | v4. Passenger clicks email link -> /auth/trip?token={encoded} | v5. POST /api/trip/verify - Decodes base64 payload (token, booking_reference, passenger_id, signature) - Validates HMAC signature (prevents tampering) - Finds valid Sanctum token via TripAccessTokenService - Verifies booking_reference and passenger_id match token abilities - Marks token as used - Returns booking details + access_token | v6. Astro stores in server session: - bookingReference: string - accessToken: string (Sanctum token for subsequent calls) - expiresAt: timestamp - passengerName: string (for personalized greeting) | v7. Redirect to /{market}/trip/{reference} - Page shows personalized greeting - Trip details rendered from sessionToken Security
Section titled “Token Security”HMAC Signature
Section titled “HMAC Signature”The magic link payload includes an HMAC-SHA256 signature to prevent tampering:
$payload = [ 'token' => $plainTextToken, 'booking_reference' => $booking->booking_reference, 'passenger_id' => $passenger->id,];
$signature = hash_hmac('sha256', json_encode($payload), config('app.key'));The signature is verified before any database lookup, preventing timing attacks.
Sanctum Token Properties
Section titled “Sanctum Token Properties”| Property | Value | Purpose |
|---|---|---|
| Storage | personal_access_tokens table | Standard Sanctum polymorphic storage |
| Tokenable | Passenger model | Token owner is the passenger |
| Name | trip-access:{booking_reference} | Identifies token purpose and booking |
| Abilities | trip-access, booking:{id}, passenger:{id} | Scoped permissions |
| Expiry | 90 days default | Configurable via DEFAULT_VALIDITY_DAYS |
| Single-use | No | Token can be reused until expiry |
Token Abilities
Section titled “Token Abilities”Each token is created with abilities that encode the access context:
$abilities = [ 'trip-access', // Identifies as trip access token 'booking:' . $bookingId, // Links to specific booking 'passenger:' . $passengerId, // Links to specific passenger];Database Schema
Section titled “Database Schema”Trip access tokens use Laravel Sanctum’s personal_access_tokens table (polymorphic):
-- Sanctum's personal_access_tokens tableCREATE TABLE personal_access_tokens ( id BIGINT PRIMARY KEY, tokenable_type VARCHAR(255), -- 'App\Models\Passenger' for trip access tokenable_id BIGINT, -- Passenger ID name VARCHAR(255), -- 'trip-access:{booking_reference}' token VARCHAR(64) UNIQUE, -- Hashed token abilities JSON, -- ['trip-access', 'booking:123', 'passenger:456'] last_used_at TIMESTAMP NULL, expires_at TIMESTAMP NULL, created_at TIMESTAMP, updated_at TIMESTAMP,
INDEX(tokenable_type, tokenable_id));TripAccessTokenService
Section titled “TripAccessTokenService”The service wraps Sanctum token operations with booking context:
use App\Services\TripAccessTokenService;
$tokenService = app(TripAccessTokenService::class);
// Create token for passenger$tokenData = $tokenService->createForPassenger($booking, $passenger);// Returns: ['token' => NewAccessToken, 'plain_text' => 'id|token']
// Find valid token$token = $tokenService->findValidToken($plainTextToken);// Returns: PersonalAccessToken or null
// Get booking ID from token abilities$bookingId = $tokenService->getBookingIdFromToken($token);
// Get passenger from token$passenger = $tokenService->getPassengerFromToken($token);
// Revoke tokens for specific booking$tokenService->revokeTokensForBooking($passenger, $booking);
// Revoke all trip tokens for passenger$tokenService->revokeAllTokens($passenger);API Endpoints
Section titled “API Endpoints”Verify Trip Access Token
Section titled “Verify Trip Access Token”POST /api/trip/verifyRate Limit: 10 requests/minute
Request:
{ "token": "base64_encoded_payload"}Response (success):
{ "success": true, "data": { "booking": { /* BookingDetailsResource */ }, "passenger": { "id": 4, "name": "John Doe" }, "access_token": "1|abc123def456...", "expires_at": "2026-03-29T15:41:27.000000Z" }}Error codes: invalid_token, expired_token
Source: backend/app/Http/Controllers/Api/Trip/AccessController.php:26
Get Trip Details
Section titled “Get Trip Details”GET /api/trip/show?token={access_token}Rate Limit: 60 requests/minute
Request: Sanctum token as query parameter.
Response: Fresh booking details for the authenticated passenger.
Source: backend/app/Http/Controllers/Api/Trip/AccessController.php:110
Filament Integration
Section titled “Filament Integration”Share Trip with Pax Action
Section titled “Share Trip with Pax Action”Located in ViewBooking.php, the action:
- Shows checkbox list of passengers with email addresses
- Displays passenger name, lead badge, and email
- Pre-selects all passengers
- Allows bulk select/deselect
- Sends emails to selected passengers only
Action::make('share_trip_with_pax') ->label('Share Trip with Pax') ->icon(Heroicon::OutlinedEnvelope) ->color('gray') ->form(fn (): array => $this->getShareTripForm()) ->action(fn (array $data) => $this->shareWithSelectedPassengers($data['passengers'] ?? []))Source: backend/app/Filament/Resources/Bookings/Pages/ViewBooking.php:28
Dual Access Methods
Section titled “Dual Access Methods”The trip page supports two access methods:
| Method | URL Pattern | Use Case |
|---|---|---|
| Admin Preview | /{market}/trip/{ref}?preview={signed_token} | Short-lived (60 min) signed URL for backoffice preview |
| Passenger Session | /{market}/trip/{ref} | Long-lived session from magic link verification |
// Trip page access checkif (previewToken) { // Admin preview via signed URL booking = await getBookingByPreviewToken(previewToken, clientIp);} else if (tripAccess && tripAccess.bookingReference === reference) { // Passenger session access const result = await getTripDetails(tripAccess.accessToken); booking = result.data?.booking;}Source: frontend/src/pages/[market]/trip/[reference].astro:46
Email Template
Section titled “Email Template”The TripAccessNotification sends a branded email with:
- Personalized greeting (passenger name)
- Trip summary (destination, dates)
- CTA button linking to
/auth/trip?token={encoded} - Expiry information
Source: backend/app/Notifications/TripAccessNotification.php
Rate Limiting
Section titled “Rate Limiting”| Endpoint | Limit | Purpose |
|---|---|---|
POST /api/trip/verify | 10/min | Prevent brute force |
GET /api/trip/show | 60/min | Normal usage |
| File | Purpose |
|---|---|
backend/app/Services/TripAccessTokenService.php | Token management service |
backend/app/Models/Passenger.php | Uses HasApiTokens trait for Sanctum |
backend/app/Http/Controllers/Api/Trip/AccessController.php | API endpoints |
backend/app/Notifications/TripAccessNotification.php | Email notification |
backend/app/Filament/Resources/Bookings/Pages/ViewBooking.php | Admin action |
frontend/src/pages/auth/trip.astro | Magic link verification page |
frontend/src/pages/[market]/trip/[reference].astro | Trip itinerary page |
frontend/src/features/trip-access/services/tripAccessApi.ts | Frontend API client |
backend/routes/api.php | Route definitions (trip prefix) |
Security Considerations
Section titled “Security Considerations”Token Security Measures
Section titled “Token Security Measures”- HMAC verification: Payload signed with
app.key, prevents tampering - Sanctum validation: Token must exist and not be expired
- Abilities verification: Token abilities must contain correct booking/passenger IDs
- Activity logging: Token usage tracked via
last_used_at - Token rotation: Re-sharing revokes previous tokens for same booking/passenger
Access Scope
Section titled “Access Scope”- Single booking: Each token grants access to one booking only
- No modification: Read-only access to trip details
- No cascading: Compromised token doesn’t expose other bookings
- Isolated tokens: Tokens for different bookings are independent
URL Patterns
Section titled “URL Patterns”- Trip page at
/{market}/trip/{reference}is not publicly accessible - Requires either valid preview token OR active session from magic link
- Session tied to specific booking reference