OIDC Back-Channel Logout

Auth0 supports the OpenID Connect Back-Channel Logout 1.0 specification in all tenants with an Enterprise plan subscription.

This specification leverages the session ID (sid) included in ID tokens and the Logout Tokens to coordinate session termination via back-channel communication. Different session IDs represent individual sessions of a user agent or device in your tenant. Logout Tokens identify the end-user and session to logout.

Back-channel communications

To use Back-Channel Logout, an application must expose a Back-Channel Logout URI, reachable from the tenant server, where the application expects to receive the requests with the Logout Token. When an application receives this request, it is compelled to clear the local session state matching the claims in the token.

Tokens in OIDC Back-Channel Logout communications

Applications cannot rely on session cookies to determine which session to terminate when communications are performed via the back-channel. Rather, the service depends on a shared session identifier (sid) on ID and Logout Tokens.

When end-users successfully authenticate with Auth0 during login, the authorization server assigns an access, ID, and Logout Token. The ID and Logout Token contain the claims your application needs in the Back-Channel Logout workflow. To learn more about claims, read JSON Web Token Claims.

Workflow for back-channel logout
  1. Login - During user authentication, the Auth0 tenant adds the sid to the ID token.

  2. Login - The application stores the received session identifier in its own session store and associates it with the application-specific session.

  3. Logout - The IdP calls the pre-registered logout callback URL and posts the Logout Token to this endpoint. The token contains the user_id (sub) and the sid along with other parameters.

  4. Logout - The application’s backend needs to validate the Logout Token as per OIDC spec and extract the sid. Then the backend can use this token to find the session associated with the identifier and terminate it as necessary.

How it works

The sample use case demonstrates how Back-Channel Logout works with more than one application:

Back-channel logout multiple app use case
  1. During application configuration, Application A registers a Back-Channel Logout URI with Auth0.

  2. During application configuration, Application B registers a Back-Channel Logout URI with Auth0.

  3. During end-user login, a user authenticates with Auth0 to access Application A.

  4. Auth0 sends an ID token with sid to Application A. To learn more, read ID Token Structure.

  5. User authenticates with Auth0 to access Application B.

  6. Auth0 sends an ID token with the same sid to Application B. Your application should store the session information.

  7. During logout, Application A or other entities initiate logout on the front-channel.

  8. Auth0 terminates the Auth0 session layer via session cookie.

  9. Auth0 calls Application A’s Back-Channel Logout URI and posts the Logout Token.

  10. Application A validates the Logout Token and terminates the session.

  11. Auth0 calls Application B’s Back-Channel Logout URI and posts the Logout Token.

  12. Application B validates the Logout Token and terminates the session.

Sample token

Your application must be able to parse and validate JWTs to use as Logout Tokens with Auth0. To learn more, read Validate JSON Web Tokens

Once your application validates and decodes your token, the contents are similar to the example below:

{
  "iss": "https://artex-dev.eu.auth0.com/",
  "sub": "auth0|602e93db83fa6f00749a23e6",
  "aud": "TuhNLv7ulXD3RfyLlSMbOvszzwJJFPpO",
  "iat": 1698160928,
  "exp": 1698161048,
  "jti": "44a91215-dfb4-4dfe-a1eb-fcafa911deba",
  "events": {
    "http://schemas.openid.net/event/backchannel-logout": {}
  },
  "trace_id": "81b336a94a4a5707",
  "sid": "375UIp_ID5mCTClIeBEHpXfGwq51tF_L"
}

Was this helpful?

/

Auth0 SDKs

A full example and production code is already included under the Back-Channel Logout Example section of our express-openid-connect SDK.

Implementation examples

Session storage

The session storage example is built in Node (Express) and based on the Express OpenID Connect Web App Sample.

In your application sessions tab, expose the route you configured to receive the Logout Token. Validate the token and terminate the user session.

routes/index.js

const express = require('express');
const router = express.Router();
const { requiresAuth } = require('express-openid-connect');

// middleware to validate the logout token
const requiresValidLogoutToken = require('../middlewares/validateLogoutToken');

// helper function to delete user sessions
const deleteUserSessions = require('../utils/sessions');

// new route to receive backchannel logout tokens
// must be configured in the Application -> Sessions tab 
// in the Auth0 Management Dashboard
router.post(
  '/backchannel-logout',
  requiresValidLogoutToken,
  function (req, res, next) {
    // at this point the logout token is valid, checked by requiresValidLogoutToken middleware
    // you can access it from the request object: req.logoutToken

    // delete user session so the user gets logged out
    deleteUserSessions(
      req.app.locals.sessionStore,
      req.logoutToken.sub,
      req.logoutToken.sid
    );

    res.sendStatus(200);
  }
);

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'Auth0 Webapp sample Nodejs',
    isAuthenticated: req.oidc.isAuthenticated(),
    headline: process.env.APP_NAME,
    backgroundColor: process.env.BACKGROUND_COLOR,
    baseURL: process.env.BASE_URL,
  });
});

router.get('/profile', requiresAuth(), function (req, res, next) {
  res.render('profile', {
    userProfile: JSON.stringify(req.oidc.user, null, 2),
    title: 'Profile page',
    headline: process.env.APP_NAME,
    backgroundColor: process.env.BACKGROUND_COLOR,
    baseURL: process.env.BASE_URL,
  });
});

module.exports = router;

Was this helpful?

/

middlewares/validateLogoutToken.js

// This middleware validates the logout token as defined here:
// https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation

const jose = require('jose');

async function requiresValidLogoutToken(req, res, next) {

  // get remote key set for token verification
  const JWKS = jose.createRemoteJWKSet(
    new URL(process.env.ISSUER_BASE_URL + '/.well-known/jwks.json')
  );

  const logoutToken = req.body.logout_token;

  if (!logoutToken) {
    res.status(400).send('Need logout token');
  }

  try {
    const { payload, protectedHeader } = await jose.jwtVerify(
      logoutToken,
      JWKS,
      {
        issuer: process.env.ISSUER_BASE_URL + '/',
        audience: process.env.CLIENT_ID,
        typ: 'JWT',
        maxTokenAge: '2 minutes',
      }
    );

    // Verify that the Logout token contains a sub claim, a sid claim, or both
    if (!payload.sub && !payload.sid) {
      res
        .status(400)
        .send(
          'Error: Logout token must contain either sub claim or sid claim, or both'
        );
    }

    // Verify that the logout token contains an events claim
    // whose value is a JSON object containing the member name http://schemas.openid.net/event/backchannel-logout
    if (!payload.events['http://schemas.openid.net/event/backchannel-logout']) {
      res
        .status(400)
        .send(
          'Error: Logout token must contain events claim with correct schema'
        );
    }

    // Verify that the Logout token does not contain a nonce claim.
    if (payload.nonce) {
      res
        .status(400)
        .send('Error: Logout token must not contain a nonce claim');
    }

    // attach valid logout token to request object
    req.logoutToken = payload;

    // token is valid, call next middleware
    next();
  } catch (error) {
    res.status(400).send(`Error:  ${error.message}`);
  }
}

module.exports = requiresValidLogoutToken;

Was this helpful?

/

Logout token store

A common approach for token storage is to define a logout store as an alternative to the session store model. Your application(s) keeps a collection of Logout Tokens in the persistence level. 

Wherever the application wants to check its authentication status, it queries the Logout Token store to find if its session is still active. The logout store flushes obsolete information regularly to keep only the necessary information.

Logout Token Store

Security considerations

Back-Channel Logout Tokens are delivered over the internet, therefore the callback endpoints receiving them must follow the best practices to ensure reliable and secure operation. The recommendations list below is not exhaustive and you must always consider the specific deployment and operational situations to adapt accordingly. In the list below, any apps that handle the Back-Channel Logout Tokens are referenced as “apps”.

  • Apps must have the ability to store the session ID (sid claim) received during user login in order to retrieve them later when receiving a Back-Channel Logout token.

  • Apps must verify any received tokens as per JWT validation best practices.

  • Apps must accept tokens issued only by trusted tenants. A malicious actor can attempt to send tokens issued by other Auth0 tenants - such attempts must be rejected.

  • Apps must accept tokens only when they contain a sid value (session ID) that the app recognizes. Tokens containing an invalid session ID (being it expired or unrecognized) must be rejected.

  • Apps must expose the callback endpoints only via TLS. Unencrypted communication channels are not recommended.

  • Apps are recommended to accept requests only from the published list of outbound IP addresses.

  • Apps are recommended to follow the general best practices in terms of monitoring, logging and rate limiting - however details of these are outside of scope of this document.

  • Apps are recommended to regularly clean stale or expired sessions.

  • Any changes in the endpoint address must be synchronised with the tenant configuration in order to ensure the Logout Tokens are always delivered to the correct Back-Channel Logout Callback URL.

Learn more