---
title: "Back-Channel Logout with TanStack Start"
description: "Learn how to secure your distributed session states by implementing Auth0 Back-Channel Logout using TanStack Start server routes"
authors:
  - name: "Rudy Dahbura"
    url: "https://auth0.com/blog/authors/rudy-dahbura/"
date: "Jun 2, 2026"
category: "Developers"
tags: ["tanstack", "back-channel logout", "typescript"]
url: "https://auth0.com/blog/back-channel-logout-tanstack-start/"
---

# Back-Channel Logout with TanStack Start

Managing session state across a distributed system with multiple relying parties presents a unique set of challenges, particularly when it comes to terminating those sessions reliably. This is where Back-Channel Logout becomes essential. 

Unlike traditional front-channel logout, which relies on browser redirects or iframes to clear local cookies, Back-Channel Logout operates entirely server to server. When a user logs out from a central Auth0 tenant, Auth0 transmits a secure logout token directly to the backend of your applications. This approach removes the dependency on the user's browser, providing a significantly more robust and secure mechanism to ensure that a user's session is definitively terminated across all connected services.

In this post, we will explore how to implement Auth0 Back-Channel Logout within a [TanStack Start](https://tanstack.com/start/latest) application. As a modern, type-safe full-stack framework built on [TanStack Router](https://tanstack.com/router/latest), TanStack Start provides an excellent architecture for handling both the frontend experience and the necessary server-side logic. 

We will walk through the process of configuring the required server-side API route to receive Auth0 Logout Tokens, securely validate those tokens, and effectively destroy the local user session. We will use [TanStack Start Server Routes](https://tanstack.com/start/latest/docs/framework/react/guide/server-routes) for the implementation. Read on as we detail the steps required to enhance the security and reliability of your application's authentication flow.

## Auth0 Back-Channel Logout Flow

To understand how this works in practice, let's outline the core Auth0 Back-Channel Logout flow. The process begins when a logout event occurs at the authorization server—whether initiated by the user or via an administrative action. Auth0 then identifies all active applications associated with that user's session. For every application configured to support this feature, Auth0 generates a specialized, signed JSON Web Token (JWT) known as a Logout Token. This token is dispatched via an HTTP POST request directly to the application's designated Back-Channel Logout URI. The receiving application must then validate the token's cryptographic signature and specific claims (such as the subject, `sub` or session ID, `sid`) before definitively destroying the local session.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/4rrCj2n2lJNLpHsNyTQ8a8/c66cbc019cc4628b50ced4bc5e4e7c08/Auth0BackchannelLogoutFlow.png" alt="Auth0 Back-Channel Logout Flow" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

## Configuring Your Auth0 Application

Before diving into the code, we first need to ensure your Auth0 application is properly configured to support this feature. Here are the essential steps to get your application ready for the Back-Channel Logout flow:

1. **Navigate to Applications:** Go to the Auth0 Dashboard and navigate to the **Applications** section.

2. **Select Your App:** Choose the specific application you want to register for Back-Channel Logout.

3. **Open Settings:** Select the **Settings** tab for your chosen application.

4. **Configure the URI:** Scroll to find **OpenID Connect Back-Channel Logout**, and in the **Back-Channel Logout URI** field, add the application logout URI that will receive the `logout_token`s (for example, [https://your-domain.com/api/auth/backchannel-logout](https://your-domain.com/api/backchannel-logout)).

5. **Select Logout Initiators:** Logout initiators are the specific events that trigger the Back-Channel Logout process. These can include a user initiating a logout from another application, an administrator manually revoking a session from the dashboard, or a password change event. It is important to select the specific initiators that make the most sense for your application's security requirements. For the purposes of this tutorial, we will simply choose them all to ensure our application responds to every possible logout event.

6. **Save:** Once complete, click **Save Changes** to apply the configuration.

## Endpoint Validation Requirements

Before we look at the code for the TanStack Start endpoint, let's review what this endpoint actually needs to do. When Auth0 sends a request to your Back-Channel Logout URI, it includes a `logout_token` that you must rigorously validate to ensure the request is legitimate. 

According to the OpenID Connect Back-Channel Logout specification, your application must perform the following checks:

* **Accept HTTP POST:** The endpoint must strictly process incoming HTTP POST requests.

* **JWT Signature Validation:** Extract the `logout_token` parameter and validate its cryptographic signature as a standard JWT using your Auth0 tenant's public keys.

* **Verify the Events Claim:** Check that the token contains an `events` claim (a JSON object) that includes a specific member named http://schemas.openid.net/event/backchannel-logout.

* **Check for Session Identifiers:** Ensure the token contains a `sid` (Session ID) and/or `sub` (Subject/User ID) claim. You will use these claims to identify which local session to terminate.

* **Reject Nonce Claims:** Verify that the token does *not* contain a `nonce` claim. This is a crucial security measure required to distinguish a logout token from a standard ID token and prevent potential abuse.

Once these validation steps are successfully completed, your backend can safely destroy the local session and return a standard `200 OK` response.

## Implementing the TanStack Start Endpoint

Now that our Auth0 application is configured to fire off logout tokens and we know exactly what we need to validate, let's shift focus to the application side. We will not dive into the nuts and bolts of TanStack Start itself; instead, we're going to look at configuring an endpoint via a TanStack Start server route. This ensures that when a POST request is received from Auth0, our application can securely destroy the requisite session.

To make this work, we'll create a route equipped with a POST handler. Keep in mind that the code below references a few small helper functions (`buildSessionId`, and `destroySession`). We will not show the implementation for these as they are standard boilerplate tailored to your specific session store.

In TanStack Start, server route handlers receive a `request` object that provides the necessary context to parse and process the incoming data. This is a standard Web API [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object, meaning you can leverage familiar methods—as extensively documented in the MDN Web Docs—to access headers, form data, and other critical request properties.

```typescript
import { createFileRoute } from '@tanstack/react-router';

import * as jose from 'jose';

import * as z from 'zod';

import { clientEnv } from '~/lib/env';
import {
  buildSessionId,
  destroySession,
} from '~/lib/session/session.functions';

type BackchannelLogoutEvents = {
  'http://schemas.openid.net/event/backchannel-logout': Record<string, unknown>;
};

type LogoutTokenClaims = jose.JWTPayload & {
  events?: BackchannelLogoutEvents;
  sid?: string;
  nonce?: string;
};

const JwksCacheMaxAgeMs = 10 * 60 * 1000;

const issuerJwks = new URL(
  `https://${clientEnv.VITE_APP_AUTH0_DOMAIN}/.well-known/jwks.json`,
);

const getJwksKey = jose.createRemoteJWKSet(issuerJwks, {
  cacheMaxAge: JwksCacheMaxAgeMs,
});

const BodySchema = z.object({
  logout_token: z.string().min(1),
});

async function verifyLogoutToken(token: string) {
  const { payload } = await jose.jwtVerify(token, getJwksKey, {
    issuer: `https://${clientEnv.VITE_APP_AUTH0_DOMAIN}/`,
    audience: clientEnv.VITE_APP_AUTH0_CLIENT_ID,
  });
  return payload as LogoutTokenClaims;
}

export const Route = createFileRoute('/_public/api/auth/backchannel')({
  server: {
    handlers: {
      ANY: () => {
        return new Response(null, {
          status: 405,
          headers: { Allow: 'POST' },
        });
      },
      POST: async ({ request }) => {
        const contentType = request.headers.get('content-type') ?? '';

        if (!contentType.includes('application/x-www-form-urlencoded')) {
          console.warn('Backchannel logout: unsupported content type');
          return Response.json({ error: 'Bad Request' }, { status: 400 });
        }

        const parsed = BodySchema.safeParse(
          Object.fromEntries(await request.formData()),
        );

        if (!parsed.success) {
          console.warn('Backchannel logout: logout_token missing or invalid');
          return Response.json({ error: 'Bad Request' }, { status: 400 });
        }

        let claims: LogoutTokenClaims;

        try {
          claims = await verifyLogoutToken(parsed.data.logout_token);
        } catch {
          console.warn('Backchannel logout: token verification failed');
          return Response.json({ error: 'Bad Request' }, { status: 400 });
        }

        const backchannelEvent =
          claims.events?.['http://schemas.openid.net/event/backchannel-logout'];

        if (!backchannelEvent) {
          console.warn('Backchannel logout: missing backchannel-logout event');
          return Response.json({ error: 'Bad Request' }, { status: 400 });
        }

        if (!claims.sid || !claims.sub) {
          console.warn('Backchannel logout: missing sid or sub claim');
          return Response.json({ error: 'Bad Request' }, { status: 400 });
        }

        if (claims.nonce) {
          console.warn('Backchannel logout: nonce claim present');
          return Response.json({ error: 'Bad Request' }, { status: 400 });
        }

        const sessionId = buildSessionId(claims.sid, claims.sub);
        await destroySession(sessionId);

        return new Response(null, {
          status: 200,
        });
      },
    },
  },
});

```

Notice the strict validation checks occurring within the POST handler. Per the OIDC Back-Channel Logout specification, we ensure the token contains the specific Back-Channel Logout event claim, verifies the presence of both `sid` and `sub` claims to identify the session, and explicitly rejects any token that contains a `nonce`. Once validated, our boilerplate session helpers take over to purge the user's session from the system, returning a standard `200 OK` response to let Auth0 know the logout was successful.

## Testing Your Implementation

With your endpoint in place and your Auth0 application properly configured, it is time to test the flow. A straightforward way to verify everything is working as expected is to start by logging a user into your TanStack application to establish an active session. Once the user is logged in, head over to the **User Management** section of your Auth0 Dashboard. Find the user you just logged in with and change their password.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/snyOYPZGPhJFLQHLZToGx/f9f92fedd0d9184f762118f988500641/Auth0DashboardUserManagementBackChannelLogoutURI.png" alt="Auth0 Dashboard User Management Back-Channel Logout URI" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Because we selected all logout initiators earlier (which includes password changes), Auth0 will immediately dispatch a `logout_token` to your configured Back-Channel Logout URI. You can monitor your application's server logs to watch the payload come in, verify the token passes all validation steps, and confirm that the local session is successfully destroyed behind the scenes.

## Wrapping Up

Implementing Back-Channel Logout provides significant advantages over traditional front-channel methods. By shifting the logout process entirely server-side, you eliminate the reliance on fragile browser-based redirects and third-party cookies. This guarantees a much more robust and secure architecture, ensuring that when a user logs out, their sessions are definitively terminated across all connected relying parties. For a deeper dive into the specific mechanics and advanced configurations of this feature, be sure to check out the [official Auth0 Back-Channel Logout documentation](https://auth0.com/docs/authenticate/login/logout/back-channel-logout).

## Resources

To continue your deep dive into the underlying specifications and the frameworks used for this solution, please consult the official documentation below.

* **Auth0 Documentation**  
  * [OpenID Connect Back-Channel Logout](https://auth0.com/docs/authenticate/login/logout/back-channel-logout): For a complete understanding of the protocol specifications and requirements.  
* **TanStack Documentation**  
  * [TanStack Start Documentation](https://tanstack.com/start/latest): The official entry point for the full-stack framework used in this implementation.  
  * [TanStack Start Server Routes Guide](https://tanstack.com/start/latest/docs/framework/react/guide/server-routes): Detailed information on configuring the server-side endpoints.