Back-channel logout
Revoke session-mode sessions when Logto ends an IdP session, using a verified OIDC Logout Token.
OIDC back-channel logout lets Logto notify your Convex deployment when its
identity-provider session ends. In session mode, the
component records logical revocation for every component Session mapped to the
Logout Token's sid, then removes those rows in bounded batches. The existing
sessionValid subscription pushes signed-out state to every live client. There
is no client configuration or new frontend code.
This is separate from webhook sync. Webhooks handle account-level deletion and suspension; back-channel logout handles an individual Logto sign-in session.
Register the Convex endpoint
Install the session component per the Quick start,
then add the route to the same convex/http.ts router as your other HTTP actions:
import { httpRouter } from "convex/server";
import { registerLogtoBackchannelLogout } from "convex-logto";
import { components } from "./_generated/api";
const http = httpRouter();
registerLogtoBackchannelLogout(http, {
sessions: components.logto,
}); // POST /logto/backchannel-logout
export default http;The route reads LOGTO_ENDPOINT and LOGTO_APP_ID, the same deployment values
as logtoSessionApi. You can override either in the options, or change the route
with path.
Configure Logto
In Logto Console, open Applications, select the Traditional web app that session mode uses, and find its Backchannel logout section. Set Backchannel logout URI to:
https://<your-deployment>.convex.site/logto/backchannel-logoutUse the deployment's .convex.site HTTP Actions URL, not its
.convex.cloud client URL. Production, staging, and development deployments
each need their own Logto app and URI.
Enable Is session required? for precise session-level logout. Logto then
includes the ID token's sid, and the component deletes only sessions created
from that Logto SSO session. One sid may map to multiple component Sessions.
If Logto sends a token with sub but no sid, the OIDC-defined fallback
logically revokes every component Session for that subject before bounded row
cleanup.
Existing component sessions created before this feature gain their sid on
their next ID-token refresh (or on the next sign-in). A valid logout for an
already-absent or not-yet-mapped sid still returns 200, as the OIDC
specification requires.
Validation and replay handling
The endpoint accepts only a form-encoded POST containing exactly one
logout_token. Before changing state it:
- verifies an
RS256orPS256signature with<LOGTO_ENDPOINT>/oidc/jwks(the handler caches the key set for five minutes, shares one refresh among concurrent misses, times fetches out after 10 seconds, and caps responses at 256 KiB and 32 keys); - requires the configured issuer and app audience, fresh
iat, unexpiredexp, and a non-emptyjti; - requires the OIDC back-channel logout event and either
sidorsub; - rejects
none, symmetric signing algorithms, malformed JWTs, and anynonceclaim; - streams the request body through a 1 MiB application limit.
The endpoint claims each verified jti for 24 hours in the component's existing
delivery-deduplication table. It answers a retry of a delivery that already
completed with the same 200 without re-running revocation. It redoes a claim
whose work never completed, because that work may never have committed;
revocation is idempotent. If the revocation mutation fails, the endpoint
releases the claim so Logto can retry the work.
Responses include Cache-Control: no-store. Success is always 200, including
when no session matched, so the endpoint never reveals session existence.
Invalid or failed logout requests return 400 with an OAuth-style JSON error;
oversized bodies return 413.
For a custom router, createLogtoBackchannelLogoutHandler(options) returns the
same Convex HTTP action without registering a path. The package also exports the
lower-level verifyLogtoLogoutToken(token, options?) verifier for integrations
that need the verification step alone.