OAuth 2.0 access token introspection

Protected resources, such as web APIs, need to validate the access token in each received request, before serving it.

The access token will typically be of type Bearer and included in a Authorization header like this:

Authorization: Bearer [token-value]

For example:

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer gai1iud5ohgh7aewaiV5riuzaiNgooWu

Access token types

There are two kinds of access token, depending on how their authorisation, such as scope, is encoded:

  • Identifier based -- The token represents a random, hard-to-guess identifier for the token authorisation in the authorisation server's database.

  • Self-contained -- The authorisation is encoded in the token itself and is cryptographically protected against tampering. JSON Web Token (JWT) is the common standard for that.

Access token introspection request

Identifier based access tokens are validated by making a network call to the authorisation server. There is a standard protocol for that, called OAuth 2.0 Token Introspection (RFC 7662).

The protected resource will POST the token to the authorisation server's introspection endpoint, and will get back a JSON object with the token's parameters.

Note that the introspection request cannot be made freely, it needs to be either

  • authenticated with credentials, or
  • authorised with an access token.

Thus, for this particular interaction the protected resource becomes an OAuth client, and the authorisation server a protected resource.

Example introspection request where the protected resource authenticates with a client_id and client_secret, which were obtained after registering as an OAuth client with the authorisation server.

POST /token/introspect HTTP/1.1
Host: demo.c2id.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

token=gai1iud5ohgh7aewaiV5riuzaiNgooWu

The Java code:

import java.net.URI;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.token.*;

// The introspection endpoint
URI introspectionEndpoint = new URI("https://demo.c2id.com/token/introspect");

// The registered client credentials of the protected resource
ClientID clientID = new ClientID("s6BhdRkqt3");
Secret clientSecret = new Secret("gX1fBat3bV");

// Token to validate
AccessToken inspectedToken = new BearerAccessToken("gai1iud5ohgh7aewaiV5riuzaiNgooWu");

// Compose the introspection call
HTTPRequest httpRequest = new TokenIntrospectionRequest(
    introspectionEndpoint,
    new ClientSecretBasic(clientID, clientSecret),
    inspectedToken)
    .toHTTPRequest();

// Make the introspection call
HTTPResponse httpResponse = httpRequest.send();

Example introspection request where the protected resource includes a bearer access token as authorisation. How this particular token is obtained is outside the scope of the introspection specification. The protected resource might have been obtained it by being registered as an OAuth 2.0 client with the authorisation server for the client credentials grant, which is intended for service-to-service access.

POST /token/introspect HTTP/1.1
Host: demo.c2id.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer phiize0aibie8TeekaWuvahw3Cohj6mo

token=gai1iud5ohgh7aewaiV5riuzaiNgooWu

The Java code:

import java.net.URI;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.token.*;

// The introspection endpoint
URI introspectionEndpoint = new URI("https://demo.c2id.com/token/introspect");

// The registered client credentials of the protected resource
ClientID clientID = new ClientID("s6BhdRkqt3");
Secret clientSecret = new Secret("gX1fBat3bV");

// Token to access the introspection endpoint, may need to be refreshed
AccessToken accessToken = new BearerAccessToken("phiize0aibie8TeekaWuvahw3Cohj6mo");

// Token to validate
AccessToken inspectedToken = new BearerAccessToken("gai1iud5ohgh7aewaiV5riuzaiNgooWu");

// Compose the introspection call
HTTPRequest httpRequest = new TokenIntrospectionRequest(
    introspectionEndpoint,
    accessToken,
    inspectedToken)
    .toHTTPRequest();

// Make the introspection call
HTTPResponse httpResponse = httpRequest.send();

If the protected resource is allowed to access the introspection endpoint it will receive a JSON object with details about the token.

The most important detail is the boolean active parameter. If true it means the token is valid and the JSON object will include other token details, such as its scope values. If false the token is either invalid or expired, hence the protected resource must return a 401 Unauthorized with invalid_token error code.

Example response for a valid token:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8

{
  "active"     : true,
  "scope"      : "https://example.com/accounts https://example.com/groups",
  "client_id"  : "izad7cqy34bg4",
  "token_type" : "Bearer",
  "exp"        : 1448367412,
  "iat"        : 1448366912,
  "sub"        : "izad7cqy34bg4",
  "iss"        : "https://demo.c2id.com",
  "jti"        : "thee5Quu"
}

Example response for an expired or invalid token:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8

{
  "active" : false
}

The Java code for the introspection response:

import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.http.*;

TokenIntrospectionResponse response = TokenIntrospectionResponse.parse(httpResponse);

if (! response.indicatesSuccess()) {
    // The introspection request failed
    System.err.println("HTTP " + httpResponse.getStatusCode());
    System.err.println("Error: " + response.toErrorResponse().getErrorObject());
    return;
}

TokenIntrospectionSuccessResponse tokenDetails = response.toSuccessResponse();

if (! tokenDetails.isActive()) {
    System.out.println("Invalid / expired access token");
    return;
}

// Get the token authorisation details which are needed by the
// protected resource
System.out.println("Scope: " + tokenDetails.getScope());
System.out.println("Subject: " + tokenDetails.getSubject());
System.out.println("Client ID: " + tokenDetails.getClientID());

// If the token is going to be cached to save future lookups
// we'll need its expiration time
tokenDetails.getExpirationTime();

JWT validation

Access tokens which are a JWT are validated locally by the protected resource, by checking their digital signature, issuer, expiration and if necessary other claims.

The JWT validation for access tokens is explained in a article in the Nimbus JOSE+JWT library documentation.

References