identity->identifier()) { return new FileResponse( Server::runtimeRootLocation() . '/public/private.html', Response::HTTP_OK, ['Content-Type' => 'text/html'] ); } // User is not authenticated - serve the public app // If there's an accessToken cookie present but invalid, clear it $response = new FileResponse( Server::runtimeRootLocation() . '/public/public.html', Response::HTTP_OK, ['Content-Type' => 'text/html'] ); // Clear any stale auth cookies since the user is not authenticated if ($request->cookies->has('accessToken')) { $response->headers->clearCookie('accessToken', '/'); } if ($request->cookies->has('refreshToken')) { $response->headers->clearCookie('refreshToken', '/security/refresh'); } return $response; } #[AnonymousRoute('/login', name: 'login', methods: ['GET'])] public function login(): Response { return new FileResponse( Server::runtimeRootLocation() . '/public/public.html', Response::HTTP_OK, ['Content-Type' => 'text/html'] ); } #[AnonymousRoute('/logout', name: 'logout_get', methods: ['GET'])] public function logoutGet(Request $request): Response { // Blacklist the current access token if present $accessToken = $request->cookies->get('accessToken'); if ($accessToken) { $claims = $this->securityService->extractTokenClaims($accessToken); if ($claims && isset($claims['jti'])) { $this->securityService->logout($claims['jti'], $claims['exp'] ?? null); } } $response = new RedirectResponse( '/login', Response::HTTP_SEE_OTHER ); // Clear both authentication cookies $response->headers->clearCookie('accessToken', '/'); $response->headers->clearCookie('refreshToken', '/security/refresh'); return $response; } #[AnonymousRoute('/logout', name: 'logout_post', methods: ['POST'])] public function logoutPost(Request $request): Response { // Blacklist the current access token if present $accessToken = $request->cookies->get('accessToken'); if ($accessToken) { $claims = $this->securityService->extractTokenClaims($accessToken); if ($claims && isset($claims['jti'])) { $this->securityService->logout($claims['jti'], $claims['exp'] ?? null); } } $response = new JsonResponse(['message' => 'Logged out successfully']); // Clear both authentication cookies $response->headers->clearCookie('accessToken', '/'); $response->headers->clearCookie('refreshToken', '/security/refresh'); return $response; } /** * Catch-all route for SPA routing. * Serves the appropriate HTML based on authentication status, * allowing client-side routing to handle the actual path. */ #[AnonymousRoute('/{path}', name: 'spa_catchall', methods: ['GET'])] public function catchAll(Request $request, string $path = ''): Response { // If an authenticated identity is available, serve the private app if ($this->identity->identifier()) { return new FileResponse( Server::runtimeRootLocation() . '/public/private.html', Response::HTTP_OK, ['Content-Type' => 'text/html'] ); } // User is not authenticated - serve the public app $response = new FileResponse( Server::runtimeRootLocation() . '/public/public.html', Response::HTTP_OK, ['Content-Type' => 'text/html'] ); // Clear any stale auth cookies since the user is not authenticated if ($request->cookies->has('accessToken')) { $response->headers->clearCookie('accessToken', '/'); } if ($request->cookies->has('refreshToken')) { $response->headers->clearCookie('refreshToken', '/security/refresh'); } return $response; } }