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:
@@ -192,6 +192,45 @@ final class HttpOnlyCookieAuthTest extends TestCase
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
// --- Cross-App Isolation Tests ---
|
||||
|
||||
public function test_app_cookie_does_not_authenticate_portal_requests(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$token = $user->createToken('auth-token')->plainTextToken;
|
||||
|
||||
// App cookie is set, but request comes from portal origin —
|
||||
// middleware should only read crewli_portal_token, not crewli_app_token
|
||||
$response = $this->withUnencryptedCookie('crewli_app_token', $token)
|
||||
->getJson('/api/v1/auth/me', ['Origin' => 'http://localhost:5175']);
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
public function test_portal_cookie_does_not_authenticate_app_requests(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$token = $user->createToken('auth-token')->plainTextToken;
|
||||
|
||||
$response = $this->withUnencryptedCookie('crewli_portal_token', $token)
|
||||
->getJson('/api/v1/auth/me', ['Origin' => 'http://localhost:5174']);
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
public function test_correct_cookie_authenticates_with_matching_origin(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$token = $user->createToken('auth-token')->plainTextToken;
|
||||
|
||||
// Portal cookie + portal origin = authenticated
|
||||
$response = $this->withUnencryptedCookie('crewli_portal_token', $token)
|
||||
->getJson('/api/v1/auth/me', ['Origin' => 'http://localhost:5175']);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.id', $user->id);
|
||||
}
|
||||
|
||||
// --- Helper ---
|
||||
|
||||
private function findCookie($response, string $name): ?\Symfony\Component\HttpFoundation\Cookie
|
||||
|
||||
Reference in New Issue
Block a user