Summary
signOut() (dist/esm/auth.js) resolves the session id via withAuth() inside a try/catch, with a cookie-decode fallback in the catch. But withAuth() returns a quiet { user: null } (no throw) when getSessionFromHeader() finds the middleware marker header present but no session header — so sessionId stays undefined, the catch-side cookie fallback is unreachable, and the finally deletes the cookie and does the LOCAL redirect only. The end-session redirect through userManagement.getLogoutUrl never happens.
Observed impact (production-like deploy, authkit-nextjs ^4.x, Next 15.5, next start behind nginx)
- Network capture on logout click: zero requests to api.workos.com — the sessions/logout hop never occurs.
wos-session cookie is deleted, so the app looks signed out.
- The IdP session at the AuthKit hosted-UI domain (
*.authkit.app) survives; the next sign-in silently completes via the hosted domain's refresh-token endpoint with no credential prompt — the user cannot actually log out.
Context that may matter: middleware integrates via authkit(request) + handleAuthkitProxy() (the documented proxy pattern), and the sign-out call reaches the action through the client useAuth().signOut → handleSignOutAction. In this setup the action evidently sees the middleware marker but not the session header.
Suggested fix
Make the cookie fallback reachable on the quiet-null path, e.g.:
let sessionId;
try {
({ sessionId } = await withAuth());
} catch (error) { /* existing fallback */ }
if (!sessionId) {
const session = await getSessionFromCookie();
if (session?.accessToken) sessionId = decodeJwt(session.accessToken).sid;
}
i.e. treat "withAuth resolved but produced no sessionId" the same as the throw path — the cookie is right there and carries the sid.
Workaround we shipped
A route handler that unseals the cookie directly (iron-session), extracts sid, and redirects through userManagement.getLogoutUrl — happy to link it if useful. We'd much rather delete it when the fallback becomes reachable upstream.
Summary
signOut()(dist/esm/auth.js) resolves the session id viawithAuth()inside a try/catch, with a cookie-decode fallback in thecatch. ButwithAuth()returns a quiet{ user: null }(no throw) whengetSessionFromHeader()finds the middleware marker header present but no session header — sosessionIdstays undefined, the catch-side cookie fallback is unreachable, and thefinallydeletes the cookie and does the LOCAL redirect only. The end-session redirect throughuserManagement.getLogoutUrlnever happens.Observed impact (production-like deploy, authkit-nextjs ^4.x, Next 15.5, next start behind nginx)
wos-sessioncookie is deleted, so the app looks signed out.*.authkit.app) survives; the next sign-in silently completes via the hosted domain'srefresh-tokenendpoint with no credential prompt — the user cannot actually log out.Context that may matter: middleware integrates via
authkit(request)+handleAuthkitProxy()(the documented proxy pattern), and the sign-out call reaches the action through the clientuseAuth().signOut→handleSignOutAction. In this setup the action evidently sees the middleware marker but not the session header.Suggested fix
Make the cookie fallback reachable on the quiet-null path, e.g.:
i.e. treat "withAuth resolved but produced no sessionId" the same as the throw path — the cookie is right there and carries the sid.
Workaround we shipped
A route handler that unseals the cookie directly (iron-session), extracts
sid, and redirects throughuserManagement.getLogoutUrl— happy to link it if useful. We'd much rather delete it when the fallback becomes reachable upstream.