Fixing Auth Cookie Scope and Event Slug Convention Mismatches in the Crew Portal

During a development session investigating the shipcaptaincrew project, we uncovered two interconnected authentication and data validation bugs that were degrading the user experience when navigating between charter dispatch and crew detail pages. This post details the root causes, the fixes applied, and the infrastructure decisions that prevent similar issues in the future.

The Problem: Auth Loss Between Routes

A user authenticated successfully on the dispatch page (/) but when clicking a charter link to the crew detail page (/charter/2026-05-23), the browser was forcing re-authentication. The token, stored in browser memory/cookies, was not persisting across route transitions.

Additionally, the waiver page (/g/2026-05-23/waiver) was failing with "Could not load event" errors because the charter slug format didn't match the expected convention documented in project_jada_guest_page_convention.md.

Root Cause Analysis

Bug #1: Cookie Path Scoping in Lambda@Edge

The authentication flow in the dispatch page stores the token via a Lambda function that sets HTTP-only cookies. However, the Set-Cookie header in the Lambda response was not specifying a Path attribute, causing the browser to scope the cookie to only the route that set it.

We traced this through:

  • Dispatch page auth fetch logic in /frontend/dispatch.js
  • Token storage key inspection (localStorage key pattern)
  • Lambda cookie handling in the authentication middleware
  • CloudFront behavior rules for / and /charter prefixes

The Lambda function responsible for auth (deployed to CloudFront as an origin request/response handler) was issuing cookies like:

Set-Cookie: auth_token=xyz; HttpOnly; Secure; SameSite=Strict

Instead of:

Set-Cookie: auth_token=xyz; HttpOnly; Secure; SameSite=Strict; Path=/

Bug #2: Event Slug Format Inconsistency

The Ash Scattering charter row in the dispatch table used a bare-date slug (2026-05-23) instead of the documented convention: YYYY-MM-DD-firstname-[morning|afternoon|sunset].

When the crew detail page (/charter/:slug) tried to load event metadata, it called the /events Lambda handler with the slug. The handler performs a DynamoDB lookup using the full convention format. The bare-date slug failed the lookup, and downstream calls to fetch waiver data from the waiver route handler (registered in serverless.yml) also failed.

The "Could not load event" error originated from /frontend/charter.js in the loadCharter() function after the /events handler returned a 404.

Fixes Applied

Fix #1: Update Lambda Cookie Headers

We modified the Lambda@Edge function responsible for authentication responses. The function is deployed as part of the CloudFront distribution for tech.queenofsandiego.com (distribution ID and function name tracked in infrastructure docs).

The authentication middleware now explicitly sets Path=/ on all session cookies:

// In Lambda@Edge auth handler
const setCookieHeader = `auth_token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600`;
response.headers['set-cookie'] = [{ key: 'Set-Cookie', value: setCookieHeader }];

This ensures cookies are available to all routes under the domain root, not just the issuing path.

Fix #2: Standardize Event Slug Generation in Dispatch Page

We updated the dispatch page data source to generate slugs matching the documented convention. The charter list is populated from an S3 bucket (location: s3://queenofsandiego-charters/) with DynamoDB metadata.

In the charter seeding/admin tooling, we enforced:

const eventSlug = `${date}-${captainFirstName.toLowerCase()}-${timeSlot}`;
// Example: "2026-05-23-alice-afternoon"

Existing bare-date slugs in the dispatch table were migrated. For any charter rows already persisted, we ran a data migration script that:

  • Scanned the dispatch S3 object for all charter entries
  • Identified entries with bare-date slugs (regex: ^\d{4}-\d{2}-\d{2}$)
  • Looked up the captain name and time slot from the metadata
  • Regenerated the slug and updated the S3 object

This ensures the crew detail page and waiver page both receive properly formatted slugs that DynamoDB lookups can resolve.

Infrastructure Changes

CloudFront Distribution: No new distributions were created, but we updated the Lambda@Edge function version deployed to the origin-response behavior. The function is checked into version control and deployed via CI/CD (specifics in the project's deployment pipeline).

DynamoDB Tables: No schema changes were required. The existing events table uses slug as the partition key; we simply ensured all writes conform to the documented format.

S3 Buckets: The dispatch data in s3://queenofsandiego-charters/dispatch.json was updated with corrected slug values. We versioned this change and kept a backup of the pre-migration state.

Route53: No DNS changes were necessary; all routes operate under the existing tech.queenofsandiego.com hosted zone.

Testing & Validation

We validated the fixes by:

  1. Authenticating on the dispatch page
  2. Inspecting the Set-Cookie headers in the browser DevTools Network tab to confirm Path=/ was present
  3. Navigating to a crew detail page and confirming the auth token was included in subsequent requests (no re-auth prompt)
  4. Clicking the waiver link and confirming the event loaded successfully
  5. Verifying the waiver page URL matched the expected format: /g/{properly-formatted-slug}/waiver

Key Decisions

Why Path=/ instead of Path=/charter? Setting Path=/ ensures cookies are available across all routes (dispatch, charter, guest pages, admin areas). This follows the principle of least surprise and simplifies future route additions.

Why enforce slug convention at write time? Validating slug format during charter creation (not lookup) catches mistakes early and reduces debugging complexity. DynamoDB queries fail explicitly rather than silently returning no results.

What's Next

With these two bugs resolved, the deposit-intake system design is next in the queue. The improved auth flow and validated event data structures provide a stable foundation for that feature.

We're also documenting a pre-deployment checklist to catch similar issues: cookie headers must specify Path, and slug generation must be validated against the convention before any D