Back-Channel Logout

Back-Channel Logout

Auth0’s back-channel Logout terminates a session for end-users with a logout mechanism, such as a browser, as defined by the OpenID Connect Back-Channel Logout specification. In front-channel logout, assigned cookies terminate the session from the browser. In back-channel logout, logout tokens sent directly to your application contain claims to determine which session to end.

To use back-channel logout, your application must be able to store and track the session information, including the session ID (sid) claim obtained during user authentication so that its local session can be terminated later during back-channel logout flow via a logout token.

Back-channel communications

Back-channel communications between applications and identity providers (IdP) use the publish–subscribe pattern. Applications register HTTP(s) webhooks to receive logout tokens when the identity provider requests session logout. Back-channel communications do not involve the end-user’s browser, but use a backend to receive and synchronize the application’s session status.

Token-based synchronization

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) claim 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 you need 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 IdP (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 callback URL with Auth0.

  2. During application configuration, Application B register a callback URL 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 callback URL and posts the logout token.

  10. Application A validates the logout token and terminates the session.

  11. Auth0 calls Application B’s callback URL 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://{yourTenant}auth0.sus.auth0.com/",
  "sub": "google-oauth2|113812989461878752311",
  "aud": "45DXmtNEeUdMqz4tpVAvYPlQEbnMdZlE",
  "iat": 1663931440,
  "jti": "951de5a3-bdc7-4210-a34c-8ef2495f59e0",
  "events": {
    "http://schemas.openid.net/event/backchannel-logout": {}
  },
  "trace_id": "74f2d5cb9b71d0d9",
  "sid": "xNvDYnS6tzcP6ZUn9wjPfY1IhfCIamgY"
}

Was this helpful?

/

Early access limitations

Back-channel logout in early access currently has the following limitations:

  • ID tokens returned during refresh token exchange do not contain a sid claim. We recommend storing the sid value obtained during initial authentication.

  • The express-openid-connect SDK support for OIDC Back-Channel Logout is experimental. It should not be used in production. Function signatures may change when the feature is released for General Availability.

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 storage

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

You can find more details in the Express SDK example.

Was this article helpful?