OpenID Connect logout tokens

Support for logout tokens appears in version 5.25 of the OAuth 2.0 / OpenID Connect SDK. Version 9.35 introduced support for explicitly typed logout tokens, a simple measure to prevent mix-up of logout token JWTs with other types of JWT without having to examine the JWT claims structure.

Logout tokens are a back-channel mechanism for notifying subscribed relying parties that an end-user has been logged out of the OpenID Connect provider. The logout itself can be explicit, or result from the expiration of the end-user session with the IdP.

Registering a client to receive logout tokens

A client states its wish to receive logout back-channel notifications when it registers with the OpenID provider, using the following optional parameters:

  • backchannel_logout_uri -- the URI where logout tokens are to be delivered;

  • backchannel_logout_session_required -- if set to true, the ID token and the logout token will include a session ID (sid) to enable the client to differentiate between sessions from multiple devices / browsers with the OpenID provider.

Example registration request specifying a logout notification URI:

POST /c2id/clients HTTP/1.1
Host: demo.c2id.com
Content-Type: application/json
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

{
  "redirect_uris"          : [ "https://example.org/oidc-callback" ],
  "backchannel_logout_uri" : "https://example.org/oidc-logout"
}

Using the SDK (see client registration for more examples):

import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.client.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.rp.*;

// The client registration endpoint
URI clientsEndpoint = new URI("https://demo.c2id.com/c2id/clients");

// Master API token for the clients endpoint
BearerAccessToken masterToken = new BearerAccessToken("ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6");

// We want to register a client for the code grant
OIDCClientMetadata clientMetadata = new OIDCClientMetadata();
clientMetadata.setRedirectionURI(URI.create("https://example.org/oidc-callback"));
clientMetadata.setBackChannelLogoutURI(URI.create("https://example.org/oidc-logout"));

OIDCClientRegistrationRequest regRequest = new OIDCClientRegistrationRequest(
    clientsEndpoint,
    clientMetadata,
    masterToken
);

HTTPResponse httpResponse = regRequest.toHTTPRequest().send();

ClientRegistrationResponse regResponse = OIDCClientRegistrationResponseParser.parse(httpResponse);

if (! regResponse.indicatesSuccess()) {
    // We have an error
    ClientRegistrationErrorResponse errorResponse = (ClientRegistrationErrorResponse)regResponse;
    System.err.println(errorResponse.getErrorObject());
    return;
}

// Successful registration
OIDCClientInformationResponse successResponse = (OIDCClientInformationResponse)regResponse;
OIDCClientInformation clientInfo = successResponse.getOIDCClientInformation();

// The client credentials - store them:
// The client_id
System.out.println("Client ID: " + clientInfo.getID());
// The client_secret
System.out.println("Client secret: " + clientInfo.getSecret().getValue());
// The client's registration resource
System.out.println("Client registration URI: " + clientInfo.getRegistrationURI());
// The token for accessing the client's registration (for update, etc)
System.out.println("Client reg access token: " + clientInfo.getRegistrationAccessToken());

// Print the remaining client metadata
System.out.println("Client metadata: " + clientInfo.getMetadata().toJSONObject());

Logout token delivery

The logout token is delivered with an HTTP POST to the registered notification URI.

POST /oidc-logout HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded

logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ...

How to validate logout tokens

An application receiving a logout token at the notification URI must validate its type, signature and claims to ensure the token originates from the OpenID provider and is not a forgery.

The token is secured with the same JWS / JWE algorithms as those of the ID tokens minted to the relying party.

Example logout token header, with explicit logout+jwt typing:

{
   "alg" : "RS256",
   "typ" : "logout+jwt"
}

Example logout token claims:

{
   "iss"    : "https://demo.c2id.com",
   "sub"    : "alice",
   "aud"    : "s6BhdRkqt3",
   "iat"    : 1471566154,
   "jti"    : "bWJq",
   "sid"    : "08a5019c-17e1-4977-8f42-65a12843ea02",
   "events" : { "http://schemas.openid.net/event/backchannel-logout": {} }
}

Java code to validate the logout token and get the ID of the logged out end-user.

import java.net.*;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.proc.*;
import com.nimbusds.jwt.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.openid.connect.sdk.claims.*;
import com.nimbusds.openid.connect.sdk.validators.*;

// The OpenID Provider URL 
Issuer expectedIssuer = new Issuer(URI.create("https://demo.c2id.com"));

// The registered client_id
ClientID clientID = new ClientID("s6BhdRkqt3");

// The expected registered JWS algorithm for securing 
// the ID and logout tokens issued to the client
JWSAlgorithm expectedAlg = JWSAlgorithm.RS256;

// The OpenID Provider JWK set URL
URL idpJWKSetURL = new URL("https://demo.c2id.com/jwks.json");

// Create a new logout token validator. The class is thread-safe
// so it can be used concurrently across multiple threads. Note
// that this constructor will not require logout tokens to be
// explicitly typed, but if the JWT type is set it will check it
// for matching `logout+jwt`.
LogoutTokenValidator validator = new LogoutTokenValidator(
    expectedIssuer,
    clientID,
    expectedAlg,
    idpJWKSetURL);

// Validate a logout token that has been received
JWT logoutToken = JWTParser.parse(jwtString);

LogoutTokenClaimsSet validatedClaims;

try {
    validatedClaims = validator.validate(logoutToken);
} catch (BadJOSEException e) {
    System.err.println("The logout token is invalid: " + e.getMessage());
    return;
} catch (JOSEException e) {
    System.err.println("Internal error: " + e.getMessage());
    return;
}

// Print the logged out user
System.out.println("Logged out user: " + validatedClaims.getSubject());

The LogoutTokenValidator has other useful constructors, for example to create a validator directly from OpenID provider and client metadata.

To create a validator that requires explicitly typed logout tokens use this constructor, with the requireTypedToken argument set to true.