How to validate an OpenID Connect ID token

ID tokens are used in OpenID Connect to sign in users into client applications. But to how validate them?

Like identity cards, they contain a number of attributes, or claims. These are protected with a digital signature, or message authentication code (MAC), to ensure the token's integrity and authenticity.

ID tokens carry the following claims:

  • Subject (sub) -- identifier for the authenticated user
  • Issuer (iss) and audience (aud) -- specify the IdP that created the ID token and who it is intended for (the client_id)
  • Timestamps - issue (iat) and expiration (exp) times
  • Other attributes, such as authentication time, strength, a nonce and selected user details can also be included.

The ID token claims are encoded in a simple JSON object like this one:

{
  "sub"       : "alice",
  "iss"       : "https://openid.c2id.com",
  "aud"       : "client-12345",
  "iat"       : 1311280970,
  "exp"       : 1311281970,
}

ID token validation

There are basically three steps to it:

  1. Ensure the ID token JWS algorithm matches the one which the client has registered with the OpenID provider;
  2. Validate the ID token signature or HMAC;
  3. Validate the ID token claims:
    • issuer -- does the token originate from the IdP?
    • audience -- is the token intended for the client application?
    • timestamps -- is the token within its validity window?
    • nonce -- if set, does it match the nonce in the original request?

The complete validation process is specified in the OpenID Connect Core spec:

Prerequisites

The client must have the following four pieces of data to validate an ID token:

1. OP issuer

The issuer (iss) identifier for the OpenID Provider. This is typically an HTTPS URL, such as https://idp.c2id.com or https://accounts.google.com.

2. Client ID

The registered client_id with the OpenID Provider.

3. ID token JWS algorithm

The expected JWS algorithm for securing the ID tokens issued to the client.

The default JWS algorithm is RS256 which is a PKCS #1 signature using SHA-256. All OpenID Providers should support it. For some, such as Google Accounts, this is also the only supported one, as advertised in their IdP discovery JSON doc. The Connect2id server supports additional RSA signatures as well as HMAC'ed ID tokens protected with the client secret.

If an OpenID Provider supports multiple ID token crypto algorithms, the one which has been selected for the client at registration time must be used.

3. Key material

Finally, we'll need to have the right key material to verify the JWS signatures or HMAC.

  • For ID tokens secured with the RSA or EC signature (e.g. RS256), we need the IdP's public JSON Web Key (JWK) set. It is published as a simple JSON document at an URL which is also advertised in the OpenID Provider's metadata. You can check out Google's to see what such as JWK set looks like.

  • For ID tokens secured with an HMAC (e.g. HS256) we need the client_secret to perform the validation.

To set up ID token validation using the Connect2id OAuth 2.0 / OpenID Connect SDK for Java:

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

// The required parameters
Issuer iss = new Issuer("https://idp.c2id.com");
ClientID clientID = new ClientID("123");
JWSAlgorithm jwsAlg = JWSAlgorithm.RS256;
URL jwkSetURL = new URL("https://idp.c2id.com/jwks.json");

// Create validator for signed ID tokens
IDTokenValidator validator = new IDTokenValidator(iss, clientID, jwsAlg, jwkSetURL);

This ID token validator will automatically download the JWK set from the IdP and cache the keys to speed up processing. OpenID Providers may rotate keys (Google does it once per day), which will be detected by the validator, so you don't have to worry about this.

If you expect ID tokens with HMAC using the client_secret as a key use the alternative constructor:

new IDTokenValidator(iss, clientID, jwsAlg, clientSecret);

The ID token validator also supports ID tokens encrypted for confidentiality. Check the IDTokenValidator JavaDocs to see what other constructors are available.

Performing the actual ID token validation

Use of the setup validator is simple:

// Parse the ID token
JWT idToken = JWTParser.parse(idTokenString);

// Set the expected nonce, leave null if none
Nonce expectedNonce = new Nonce("xyz..."); // or null

IDTokenClaimsSet claims;

try {
    claims = validator.validate(idToken, expectedNonce);
} catch (BadJOSEException e) {
    // Invalid signature or claims (iss, aud, exp...)
} catch (JOSEException e) {
    // Internal processing exception
}

System.out.println("Logged in user " + claims.getSubject());

The validator performs the following operations internally:

  • Checks if the ID token JWS algorithm matches the expected one.
  • Checks the ID token signature (or HMAC) using the provided key material (from the JWK set URL or the client secret).
  • Checks if the ID token issuer (iss) and audience (aud) match the expected IdP and client_id.
  • Checks if the ID token is within the specified validity window (between the given issue time and expiration time, given a 1 minute leeway to accommodate clock skew).
  • Check the nonce value if one is expected.

If all checks pass the ID token claims set is returned.

Optional features

The validator provided by the Connect2id SDK has a number of features that you may not find elsewhere:

  • Support for ID tokens encrypted with JWE.
  • Caching and automatic update of the downloaded JWK set.
  • Set HTTP connection and read timeouts as well as size limits for the JWK set download.
  • Define alternative key material sources using the JWSKeySelector and JWEKeySelector interface.
  • Support for explicitly typed ID tokens, a special optional Connect2id server feature to guard against JWT mix up accidents and attacks.

Pac4j

If you're looking for a great library that integrates all of the above for your favourite Java framework - take a look at Pac4j and its OpenID Connect guide.

Pac4j has got support for J2EE, Spring, Apache Shiro, Play, Vertx and a whole lot of other frameworks.