OpenID Connect logout tokens

Support for logout tokens appears in version 5.25 of the OAuth 2.0 / OpenID Connect SDK.

Logout tokens serve as a back-channel mechanism for notifying subscribed clients (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 end-user session with the IdP.

Registering a client app to receive logout tokens

A clients 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 by a simple 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

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

The token is protected with the same JWS / JWE algorithms used to secure the ID tokens for the client.

Example logout token content:

{
   "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
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.