---
title: Back-channel logout
description: How the webshop receives Auth0 back-channel logout and ends its own sessions.
sidebar:
  order: 6
---

Auth0 is pointed at the webshop through the Terraform variable `website_backchannel_logout_url`, which must match
`^https://[^/?#]+/api/auth0/backchannel-logout$`. The API has no receiver.

The Auth0 SDK cookie is the shop's own session, so the shop is the component that must learn when Auth0 ends one.
Auth0 posts a signed logout token to `POST /api/auth0/backchannel-logout` (`src/routes/api.auth0.backchannel-logout.ts`,
`lib/identity/backchannel-logout.ts`). The webshop records the revocation in the `WEB_SESSION_REVOCATIONS` Workers KV
namespace, then calls the Auth0 proxy to deny and disconnect grants carrying the verified Auth0 session association. The Shop API has no logout receiver. Independent grants without that association stay connected.

```mermaid
sequenceDiagram
  autonumber
  participant A0 as Auth0
  participant W as Webshop
  participant JWKS as Auth0 JWKS
  participant KV as WEB_SESSION_REVOCATIONS
  participant G as Proxy Auth0
  participant B as Browser
  A0->>W: POST /api/auth0/backchannel-logout  logout_token
  W->>JWKS: fetch keys (redirect manual, 3 s timeout)
  W->>W: verify the logout token
  W->>KV: record sid, or every session of sub up to the notification second
  W->>G: deny linked Auth0 sid or subject-cutoff grants, bounded cleanup
  G-->>W: completed, or retryable failure
  W->>KV: remember notification only after both effects
  W-->>A0: 200 when complete, 503 on retryable failure
  B->>W: next request with the old cookie
  W->>KV: is this sid or sign-in time revoked?
  W-->>B: signed out
```

## Verifying the token

The receiver accepts one form-encoded token of at most 16 KiB and checks all of the following:

- RS256 signature against the tenant JWKS, fetched with `redirect: manual` and a 3 second timeout;
- issuer `https://<AUTH0_DOMAIN>/` and audience equal to the website client;
- `iat`, `exp`, `jti` and `events` present, and the token no older than two minutes;
- the back-channel logout event, a `sub` or a `sid`, and no `nonce`.

A key outage, failed KV write, unresolved legacy sid-only ownership, or incomplete Auth0 proxy cleanup answers 503 so Auth0 retries. Every other failure answers 400. A `jti` is remembered
for 120 seconds once its browser cutoff and Auth0 proxy effects are stored, so a retry of a notification that took effect answers 200 without any
further effect, and a retry of one whose write failed applies it.

## What is recorded

| Token carries | Recorded in KV                                                                          |
| ------------- | --------------------------------------------------------------------------------------- |
| `sid`         | That session id                                                                         |
| `sub` only    | An account cutoff: every session of the subject signed in up to the notification second |

Entries expire after 30 days, the longest session. The cookie records `fh_signed_in_at` (Unix milliseconds) at the
callback, which is what the cutoff compares against. On the next request, `resolve()` in `lib/identity/web.ts` refuses a
cookie that matches either record.

## Linked Poppy grants

The Auth0 proxy durably denies an exact sid before associated grant cleanup. A subject-only notification records a cutoff
for linked grants created through that notification second; independent grants without a verified sid stay connected.
Verified subject-bearing notifications persist the sid ownership binding. Owned listing also repairs legacy bindings
from retained verified grant records before applying display admission, including grants whose sessions are signed out.
A sid-only notification without a known subject remains denied and retryable until that trusted recovery resolves it.
Bounded cleanup keeps a durable continuation cursor across deliveries; replay completion is never recorded after a failure.

## Limits

- KV is eventually consistent, about 60 seconds across locations, and an isolate caches answers for 5 seconds. A write in
  the same isolate is visible immediately.
- An access token already issued to the shop stays valid at the API until it expires. See
  [Customer authority](/architecture/callers-and-tokens#customer-authority).
- Poppy writes keep live grant/session checks, reads keep their at-most-five-second authorization cache, and conversations receive sign-out notifications and periodic rechecks. Previously authorized work may finish.
- A copied cookie is validated only by this revocation list, not by the API.

Sign out everywhere uses the same cutoff; see [Account API](/architecture/request-flows/account-api#sign-out-everywhere).
