Logout tokens
The logout token is a back-channel mechanism for notifying subscribed relying parties that an end-user has been logged out of the OpenID provider. The logout event can be the result of direct end-user action, such as the click of a “Logout” button in a app, or result from the expiration of the end-user session with the IdP.
Version 9.35 of the SDK introduced support for explicitly typed logout tokens, a simple measure to prevent mix-up of logout token JWTs with other types of JWT, which measure doesn’t doesn’t rely on examining the JWT claims structure.
Registering an OpenID relying party to receive logout tokens
To register an OpenID relying party to receive back-channel logout notifications use the following optional parameters:
-
backchannel_logout_uri – the URI where the OpenID provider is to deliver the logout tokens;
-
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 multiple user sessions 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 relying party 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
.