Skip to Content
FrontendAppsLoginImplementing the New Logout Process

Implementing the New Logout Process

This document describes how the logout flow works in this app and how other PHPReaction apps should trigger it.

Overview

This app’s canonical logout entry point is a page: /[locale]/logout. That page fetches a fresh CSRF token, then POSTs to its own /api/logout API route, clears auth cookies, and redirects the user to a safe redirectUrl. Other PHPReaction apps don’t call this page directly — they call their own /api/logout route (backed by the shared auth bundle), which revokes tokens, clears that app’s cookies, and hands back the URL of this page to finish the job.

Backend: API Route

ItemValue
URL/api/logout
MethodPOST (CSRF-protected); GET is a no-op returning { ok: true }
CSRFRequired via X-CSRF-Token header

Implementation (this repo)

The route lives in src/app/api/logout/route.ts:

  1. Validate the CSRF token with validateCsrfTokenFromRequest(request). On failure: 403 with { error: "LOGIN-LOGOUT-INVALID-CSRF" }.
  2. Clear auth cookies via clearAuthCookiesApiResponse(response).
  3. Return { ok: true } (200), or { error: "LOGIN-LOGOUT-CLEAR-COOKIES-FAILED" } (500) if clearing fails.

The route itself does not redirect — redirection is handled client-side by the /[locale]/logout page after a successful response.

Frontend: The /[locale]/logout Page

src/containers/Auth/LogoutContainer.tsx (rendered by src/app/[locale]/logout/page.tsx):

  1. Reads redirectUrl from the query string and sanitizes it with sanitizeRedirectUrl() against ALLOWED_REDIRECT_HOSTS, falling back to the tenant’s dashboard URL if missing/unsafe.
  2. Fetches a CSRF token via the getCsrfToken() server action.
  3. Sends POST /api/logout with X-CSRF-Token and body { redirectUrl, locale }.
  4. On success: window.location.href = safeRedirectUrl.
  5. On failure: shows a “Logout failed” message with a Force clear session button that calls clearAuthCookiesServerAction() directly and sends the user back to the login page.

Frontend: Triggering Logout from Your App

Other PHPReaction apps (e.g. punch, email, print) implement their own /api/logout route that delegates to the shared bundle:

// e.g. src/app/api/logout/route.ts in another app import { logout } from "@phpcreation/frontend-auth-authorization-flow-react-nextjs-bundle/api-functions"; import { getAllConfigs } from "@phpcreation/frontend-config-react-nextjs-bundle/functions"; export async function GET(request: Request) { const configs = await getAllConfigs(request); return logout(configs?.tenant.name, request); }

The bundle’s logout(tenant, request) (@phpcreation/frontend-auth-authorization-flow-react-nextjs-bundle/src/api-functions/logout.ts):

  1. Validates the CSRF token from the x-csrf-token header (same mechanism as the account/login apps — the calling app must send its own valid CSRF token, e.g. from its CSRFContext).
  2. Clears the tenant’s cached user info for that id_token.
  3. Revokes all refresh tokens: DELETE {OAUTH_AUTHORIZE_SCHEME}{tenant}.{OAUTH_API_BASE_URL}/revoke/all with Authorization: Bearer {currentUser jwt} — this is a “sign out everywhere” call, not just a local cookie clear.
  4. Deletes that app’s own auth cookies (currentUser, access_expiration, refresh_token, refresh_expiration, id_token).
  5. Returns { ok: true, redirectUrl }, where redirectUrl is built from this login app’s own env vars — {LOGIN_AUTHORIZE_SCHEME}{tenant}.{LOGIN_BASE_URL}/{locale}/logout?redirectUrl=<calling_app_origin>.

The calling app’s frontend must then navigate the browser to the returned redirectUrl:

async function handleLogout() { const res = await fetch("/api/logout", { headers: { "x-csrf-token": csrfJwt }, // from the app's own CSRFContext }); const data = await res.json(); if (data?.redirectUrl) { window.location.href = data.redirectUrl; // lands on this app's /[locale]/logout } }

This is a two-hop flow: the calling app’s /api/logout clears its own cookies and revokes tokens, then hands off to this app’s /[locale]/logout page, which clears the login app’s own cookies and finally redirects back to the calling app’s origin. Skipping the hand-off (e.g. only clearing the calling app’s cookies) leaves the user still signed in at {tenant}.login.phpr.link.

Checklist for New Apps

  • Implement /api/logout using the shared bundle’s logout(tenant, request) rather than a custom cookie-clearing route.
  • Send a valid CSRF token (x-csrf-token header) with the logout request — the bundle rejects it otherwise (401 UTILS-CSRF-VERIFY-FAILED).
  • Set LOGIN_BASE_URL and LOGIN_AUTHORIZE_SCHEME (server-side env vars) so the bundle can build the correct redirect back to this app.
  • After a successful response, navigate the browser to the returned redirectUrl — don’t stop at clearing local cookies.
  • Respect rate limiting: avoid repeated logout calls in short succession.
  • Route implementation (this app): src/app/api/logout/route.ts
  • Page implementation (this app): src/app/[locale]/logout/page.tsx, src/containers/Auth/LogoutContainer.tsx
  • Shared logout function: @phpcreation/frontend-auth-authorization-flow-react-nextjs-bundle/api-functions/logout.ts
  • General API overview: API Calls
Last updated on