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
| Item | Value |
|---|---|
| URL | /api/logout |
| Method | POST (CSRF-protected); GET is a no-op returning { ok: true } |
| CSRF | Required via X-CSRF-Token header |
Implementation (this repo)
The route lives in src/app/api/logout/route.ts:
- Validate the CSRF token with
validateCsrfTokenFromRequest(request). On failure:403with{ error: "LOGIN-LOGOUT-INVALID-CSRF" }. - Clear auth cookies via
clearAuthCookiesApiResponse(response). - 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):
- Reads
redirectUrlfrom the query string and sanitizes it withsanitizeRedirectUrl()againstALLOWED_REDIRECT_HOSTS, falling back to the tenant’s dashboard URL if missing/unsafe. - Fetches a CSRF token via the
getCsrfToken()server action. - Sends
POST /api/logoutwithX-CSRF-Tokenand body{ redirectUrl, locale }. - On success:
window.location.href = safeRedirectUrl. - 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):
- Validates the CSRF token from the
x-csrf-tokenheader (same mechanism as the account/login apps — the calling app must send its own valid CSRF token, e.g. from itsCSRFContext). - Clears the tenant’s cached user info for that
id_token. - Revokes all refresh tokens:
DELETE {OAUTH_AUTHORIZE_SCHEME}{tenant}.{OAUTH_API_BASE_URL}/revoke/allwithAuthorization: Bearer {currentUser jwt}— this is a “sign out everywhere” call, not just a local cookie clear. - Deletes that app’s own auth cookies (
currentUser,access_expiration,refresh_token,refresh_expiration,id_token). - Returns
{ ok: true, redirectUrl }, whereredirectUrlis 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/logoutusing the shared bundle’slogout(tenant, request)rather than a custom cookie-clearing route. - Send a valid CSRF token (
x-csrf-tokenheader) with the logout request — the bundle rejects it otherwise (401 UTILS-CSRF-VERIFY-FAILED). - Set
LOGIN_BASE_URLandLOGIN_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.
Related
- 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