How to configure, manage and validate access tokens

This guide explains how to configure the properties of access tokens issued by the Connect2id server to suit the needs of applications, how to manage their lifecycle and how resource servers can validate them.

1. Access token properties

This table outlines the supported properties of Connect2id server issued access tokens.

Property Variants / values
Lifetime 1 — ∞ seconds
Type Bearer
Bearer with client certificate binding (mTLS)
DPoP
Encoding

Self-contained (JWT)

  • Cryptographic security:
    • Signed (JWS)
    • Signed (JWS) and encrypted (JWE)
  • Profile:
    • c2id-1.1
    • c2id-1.0
    • oauth-1.0
    • custom

Identifier based (opaque)

1.1 Lifetime

The duration of access token validity. The default lifetime is configured in authzStore.accessToken.defaultLifetime and is set to 600 seconds (10 minutes) out of the box:

authzStore.accessToken.defaultLifetime=600

The default lifetime can be overridden during login by setting the optional access_token.lifetime parameter in the consent object. When set like this it will apply only to the access token(s) that are issued for the current end-user and OAuth 2.0 client. Use this approach to apply an end-user and / or client specific token policy.

Example consent where the access token lifetime is set to 300 seconds (5 minutes):

{
  "scope"        : [ "openid", "email" ],
  "claims"       : [ "email", "email_verified" ],
  "access_token" : { "lifetime" : 300 }
}

For other OAuth 2.0 grants, such as the client credentials grant, the default configured access token lifetime can be overridden via their plugin interface. See the client credentials SPI and the other SPIs to find out exactly how.

1.2 Type

The type is a crucial security property of the access token.

Bearer

This type is the default and has minimal security. It is widely supported by common OAuth 2.0 client and resource server software.

In order to access a protected resource with a token of type Bearer the client must only present the token and nothing else. This makes for a very simple protocol, but requires the token to be stored and submitted securely (over TLS). A stolen or accidentally leaked bearer token can be used by an attacker to impersonate the end-user.

Bearer token usage is specified in RFC 6750.

Bearer with client certificate binding (mTLS)

Requires the client to submit a client X.509 certificate in its HTTPS request to the token endpoint in order to receive its token(s). The client must present the same certificate in the HTTPS request when accessing a protected resource with the token.

A stolen or leaked token cannot be used by an attacker unless they also have access to the private key associated with the certificate. The client application can store the private key in a HSM or another secure store that prevents extraction of the private key material.

To instruct the Connect2id server to issue certificate bound tokens for a given client it must be registered with the tls_client_certificate_bound_access_tokens parameter set. If the client is non-public it is recommended to use the mutual TLS (mTLS) with the client certificate to also authenticate the client.

The mTLS certificate binding is specified in RFC 8705.

DPoP

The DPoP access token type is intended for browser based clients (SPAs) which require security on a par with mTLS, to address the lack of a standard JavaScript API in browsers for making HTTPS calls with a client certificate for mutual TLS.

An OAuth client can request a DPoP access token by including a proof-of-possession (POP) JWT in a DPoP HTTP header for the token request. You can find more information in our blog post that announced support for DPoP in Connect2id server 12.2.

DPoP is specified in a draft that keeps evolving but is already mature enough for production use.

1.3 Encoding

OAuth 2.0 doesn't specify the content of the access token. This is left to be determined by the authorisation server and the resource / API servers in its jurisdiction.

An access token can be encoded in two ways:

  • Self-contained -- The authorisation is encoded into the token itself. The resource server validates the token by checking its digital signature using keys published by the Connect2id server.

  • Identifier-based -- The granted authorisation is stored in the Connect2id server and the token represents a long random key used to access it at the introspection endpoint.

The Connect2id server issues self-contained access tokens by default.

During login the optional access_token.encoding parameter in the consent object can be set to IDENTIFIER to switch the encoding. Use this method to switch the encoding to identifier-based for all clients or only for ones that require it.

Example consent where the access token encoding is switched to identifier-based:

{
  "scope"        : [ "openid", "email" ],
  "claims"       : [ "email", "email_verified" ],
  "access_token" : { "encoding" : "IDENTIFIER" }
}

The self-contained access tokens are encoded as a signed and optionally encrypted JSON Web Token (JWT).

The signing algorithm is configured by authzStore.accessToken.jwsAlgorithm and is set to RS256 out of the box:

authzStore.accessToken.jwsAlgorithm=RS256

To make the JWT claims (payload) confidential, i.e. protected from inspection by the client and the end-user, encrypt them after the signing. To do this set access_token.encrypt in the consent object. Example:

{
  "scope"        : [ "openid", "email" ],
  "claims"       : [ "email", "email_verified" ],
  "access_token" : { "encrypt" : true }
}

The key management and content encryption algorithms are configured by authzStore.accessToken.jweAlgorithm, respectively authzStore.accessToken.jweMethod, and have these values out of the box:

authzStore.accessToken.jweAlgorithm=dir
authzStore.accessToken.jweMethod=A128GCM

Note, identifier-based access tokens are an alternative to signed-then-encrypted JWTs for keeping the underlying token authorisation confidential from clients and end-users.

The structure of the claims within the JWT is determined by the profile configured in authzStore.accessToken.codec.jwt.profile, set to c2id-1.1 out of the box.

authzStore.accessToken.codec.jwt.profile=c2id-1.1

If the available profiles don't suit you use a self-contained access token codec plugin to define your own structure for the JWT-encoded access tokens.

2. Access token lifecycle

2.1 Expiration and refresh

The Connect2id server indicates the lifetime of an access token issued to a client in the token response expires_in parameter.

Example token response indicating an access token lifetime of 300 seconds (5 minutes):

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token"  : "eyJraWQiOiJDWHVwIiwidHlwIjoiYXQrand0IiwiYWxnIjoiUlMyN...",
  "token_type"    : "Bearer",
  "expires_in"    : 300,
  "scope"         : "https://scopes.example.com/track-item",
  "refresh_token" : "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..yi2k..."
}

After the access token expires the client will no longer be able to use it. Trying to use an invalid or expired access token will normally result in a 401 Unauthorized error at the protected resource.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token" error_description="Invalid / expired access token"

The Connect2id server will issue a refresh token in the browser based OAuth flows (code and implicit), to let the client obtain a new access token before (or after) the current one expires.

To override this behaviour and issue only an access token to the client, set the optional refresh_token.issue parameter in the consent object to false. This can be useful in authorisations for one-time access or a transaction. Example:

{
  "scope"         : [ "openid", "email" ],
  "claims"        : [ "email", "email_verified" ],
  "refresh_token" : { issue : false }
}

Example token response with an access token only:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token" : "eyJraWQiOiJDWHVwIiwidHlwIjoiYXQrand0IiwiYWxnIjoiUlMyN...",
  "token_type"   : "Bearer",
  "expires_in"   : 300,
  "scope"        : "https://scopes.example.com/track-item"
}

In the absence of a refresh token the client can obtain a new access token only by making a new, repeated authorisation request to the Connect2id server. The end-user will be (re)authenticated (if they don't have an active user session with the server). If the previous consent wasn't persisted (by setting its long_lived parameter to false) the end-user will also be asked again for their consent to the requested scope (and any OpenID claims).

2.2 Revocation

The issued access and refresh tokens can be revoked at any one time. After a revocation event the OAuth client must repeat the authorisation request and receive the user's consent anew to continue accessing the protected resource.

Triggered by the client

An OAuth 2.0 client can ask the Connect2id server to revoke an obtained access or refresh token if it's no longer needed. All other tokens (access and refresh) issued to the client for the same end-user will also be revoked. If the token is linked to a persisted authorisation record it will be deleted as well.

Example revocation request by a client:

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

token=eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..kC0eQSyYpkdZ1a2Yr5iPUg

Triggered by the end-user or an IdP policy

The tokens and any underlying persisted authorisation record can also be invalidated in response to the end-user choosing to withdraw access to the client application or in response to some policy action, such as the termination of a user account.

The Connect2id server provides a special revocation API, to facilitate the implementation of a access management UI for users and administrators, or to respond to policy actions and events.

The API supports revocation by multiple criteria, such as revoking all client tokens and persisted authorisations for a user. Example:

POST /authz-store/rest/v3/revocation HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/x-www-form-urlencoded

subject=alice

3. Access token validation

Resource servers, such as API gateways, must validate the received access tokens according to the agreed encoding.

3.1 Self-contained (JWT)

  1. Parse the access token as a signed JWT.
  2. Ensure the JWT "typ" (type) header matches the expected for the access token profile, e.g. at+jwt.
  3. Ensure the JWT "alg" (algorithm) matches the expected, e.g. RS256.
  4. Verify the JWT signature with the matching public key, identified by the JWT "kid" (key ID) header and made available by the Connect2id server at its public JWK set endpoint. The resource server can cache the server JWK set, and refresh it when it sees a new JWT "kid" header.
  5. Ensure the JWT claims set contains all claims that must be present according to the profile, e.g. "iss", "sub", "cid", "scp", "iat", "exp" and "jti".
  6. Ensure the "iss" (issuer) claim matches the Connect2id server issuer URL, e.g. "https://c2id.com".
  7. Ensure the "exp" (expiration time) is in the future.
  8. Ensure the "aud" (audience), if set, contains the resource server.
  9. If the token must be client certificate bound, or of type DPoP, check the binding by examining the "cnf" (confirmation) claim.
  10. Finally, get the token scope and any other necessary parameters to process the request.

If any one of the checks fails the token must be rejected and the resource server must return an HTTP 401 error with an appropriate code to the client.

Example error for an invalid Bearer token:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token" error_description="Invalid / expired access token"

If the token scope doesn't match the scope of the HTTP request (as an API call or otherwise) return aninsufficient_scope error code to the client.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="insufficient_scope" error_description="Scope not sufficient for the requested operation"

If the token is signed and then encrypted with a symmetric Connect2id server key, it must be first parsed as JWE and decrypted. After that the resource server can process the JWE payload as a signed JWT.

If the web server or API gateway has a built-in facility for validating access tokens or JWTs, use that instead of trying to roll your own validation code. The Nimbus JOSE+JWT library also has a facility for validating JWT-encoded access tokens.

3.2 Identifier-based (opaque)

  1. Inspect the access token at the token introspection endpoint. Note that in order to inspect a token at the Connect2id server endpoint the resource server must authenticate itself, or include a valid token authorisation.
  2. Ensure the "aud" (audience), if set, contains the resource server.
  3. If the token must be client certificate bound, or of type DPoP, check the binding by examining the "cnf" (confirmation) claim.
  4. Finally, get the token scope and any other necessary parameters to process the request.

Example introspection request:

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

token=giuLtTTnya5XpHVKNopT9w.gepM14CKpHcWloJ3XqMtvA

If required by the application the resource server can enforce single use of the access token (for an identifier-based access token) by including the optional revoke=true form parameter. The Connect2id server will invalidate the access token after completing its introspection.

If the token is invalid or has an insufficient scope, return an error as explained above in the section for self-contained tokens.

The token introspection response can be customised with a plugin.