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
.