fix: prevent cross-app auth session sharing on localhost

Root cause: browsers don't scope cookies by port. With SESSION_DOMAIN=
localhost, all three SPAs share cookies. The CookieBearerToken middleware
iterated all cookie names and picked the first match, so logging into
the organizer app (port 5174) also authenticated the portal (port 5175).

Fix: CookieBearerToken now resolves the correct cookie name from the
Origin header (same logic as SetAuthCookie trait). It only reads the
cookie matching the requesting app — portal origin reads only
crewli_portal_token, app origin reads only crewli_app_token, etc.

Falls back to first-available cookie when no Origin header is present
(server-to-server requests, tests without explicit Origin).

Added 3 cross-app isolation tests:
- app cookie does NOT authenticate portal requests
- portal cookie does NOT authenticate app requests
- correct cookie + matching origin = authenticated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 17:19:42 +02:00
parent 821bfc5bcf
commit a9ef384515
3 changed files with 97 additions and 7 deletions

View File

@@ -52,11 +52,14 @@ On successful login (`POST /auth/login`), the server:
### Validation
The `CookieBearerToken` middleware (registered before `auth:sanctum` in the API middleware stack):
1. Checks for any of the three cookie names in the request
2. If found, sets the `Authorization: Bearer` header on the request
3. Sanctum's existing token validation processes the header normally
1. Reads the `Origin` (or `Referer`) header to identify which app is making the request
2. Resolves the correct cookie name for that app (e.g. portal origin → `crewli_portal_token`)
3. Reads only that cookie and sets `Authorization: Bearer` on the request
4. Sanctum's existing token validation processes the header normally
If an `Authorization` header is already present (e.g. from the portal token flow), the middleware skips cookie injection.
**Cross-app isolation:** In local development, all three SPAs share `localhost` (different ports). Browsers do not scope cookies by port, so all three app cookies are sent with every API request. The middleware prevents cross-app authentication by only reading the cookie that matches the requesting app's Origin header. Without this, logging into one app would authenticate all apps.
If the `Origin` header is absent (e.g. server-to-server requests), the middleware falls back to the first available cookie. If an `Authorization` header is already present (e.g. from the portal token flow), the middleware skips cookie injection entirely.
### Rotation