convex-logto

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:

convex/http.ts
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-logout

Use 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 RS256 or PS256 signature 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, unexpired exp, and a non-empty jti;
  • requires the OIDC back-channel logout event and either sid or sub;
  • rejects none, symmetric signing algorithms, malformed JWTs, and any nonce claim;
  • 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.

On this page