How to configure, manage and validate access tokens
This guide explains how to configure access tokens issued by the Connect2id server, how to manage their lifecycle, and how resource servers validate them.
1. Access token properties
The following table summarises the key properties of access tokens issued by the Connect2id server.
| Property | Variants / values |
|---|---|
| Lifetime | 1 — ∞ seconds |
| Type | |
| Encoding |
Self-contained (JWT)
Identifier-based (opaque) |
| Subject identifier |
|
| Additional claims |
1.1 Lifetime
The duration for which the access token remains valid. The default lifetime is configured in authzStore.accessToken.defaultLifetime and set to 600 seconds (10 minutes) out of the box:
authzStore.accessToken.defaultLifetime=600
This lifetime can be overridden for specific clients, end-users and policies by
setting the optional access_token.lifetime parameter in the
consent object.
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 OAuth 2.0 grants that don’t involve the browser, 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 grant handler SPIs for how to do that.
When a refresh token is issued together with the access token, the Connect2id server ensures the access token lifetime does not exceed the refresh token lifetime or its maximum idle time. If necessary, the access token lifetime is reduced accordingly.
A client can find out the access token lifetime from the expires_in parameter
of the token response:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token" : "vJkbPNUFaK4kVIMGQlEmyA.-MAquq_5yQqtae62b8i7aw",
"token_type" : "Bearer",
"expires_in" : 600,
"scope" : "openid email"
}
1.2 Type
The type is a crucial security property of the access token.
Bearer
This is the simplest token type. It offers minimal security and is commonly supported by OAuth 2.0 client and resource server software.
To access a protected resource the client only has to present the token. This makes bearer tokens easy to use. Their security relies on secure storage of the token and transmission over a secure channel (typically TLS). If a bearer token is leaked or stolen it can be used to impersonate the user.
Bearer token usage is specified in RFC 6750.
Bearer with client certificate binding (mTLS)
Requires the client to use an X.509 certificate when making HTTPS requests to the token endpoint. The issued access tokens are then cryptographically bound to that certificate. The client must then use the same certificate in all HTTPS requests when accessing a protected resource with the token.
A leaked or stolen token cannot be used by an attacker unless they also possess the private key associated with the client certificate. This makes the token “sender constrained”. The client can store the private key in an HSM or another secure store that prevents key extraction.
For a client to be issued with certificate bound access tokens, it must be
registered with the
tls_client_certificate_bound_access_tokens metadata parameter. Note that
certificate binding is supported for both confidential and
public OAuth 2.0 clients. A confidential client
that uses a certificate to receive bound access tokens can naturally also use
it to authenticate with the
tls_client_auth or
self_signed_tls_client_auth methods (but it can
also use any other method, as the client authentication and the token binding
are independent from one another).
The mTLS certificate binding is specified in RFC 8705.
DPoP
The DPoP access token type was originally intended for browser-based clients (SPAs) which require sender-constrained tokens, because browsers do not provide a standard JavaScript API for making HTTPS requests with client certificates, mTLS cannot be used.
A client can request a DPoP access token by including a proof-of-possession
(POP) JWT in a DPoP HTTP header of the token
request. You can find more in the blog
post that announced support for DPoP in
Connect2id server 12.2.
To require a client to use DPoP proofs, it must be
registered with the
dpop_bound_access_tokens metadata parameter.
DPoP proofs must be used once only and their iat (issued-at time) must be
recent. The Connect2id server allows for a configurable clock skew between
client and server. If the iat falls outside the allowed time window the proof
is rejected. The dPoP.iatOffset, dPoP.iatTooOld and dPoP.iatInFuture
metrics provide insight into
the received iat timestamps. If needed, the time window can be tuned, by
changing the op.dpop.proofMaxClockSkew
and op.dpop.proofMaxAge configuration
properties.
DPoP is specified in RFC 9449.
1.3 Encoding
OAuth 2.0 does not prescribe a format for access tokens and generally treats them as opaque strings. This aspect is decided by the authorisation server and may be agreed with the resource servers in its domain.
An access token can be encoded in two ways:
-
Self-contained – The authorisation data is encoded directly in the token. Resource servers validate the token locally using its digital signature and the server’s published keys.
-
Identifier-based – The token is a reference to authorisation data stored in the Connect2id server. Resource servers must call the introspection endpoint to retrieve the token data.
Self-contained encoding is the default.
During authorisation 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 token
encoding 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
To reduce the overall size of the JWT the supplied token profiles will compress the standard names of granted OpenID claims for release at the UserInfo endpoint. This compression can be configured here.
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.
1.4 Subject identifier
Access tokens have an associated subject, which for OAuth grants authorised by a person (all standard OAuth 2.0 grants save for the client credentials grant intended for services acting on their own behalf) designates an identifier for the end-user.
-
Public subject identifier – The default behaviour of the Connect2id server is to simply set the access token subject to the local identifier of the end-user, thus making it visible to all resource servers. If the token is encoded as a JWT that is signed only (and not additionally encrypted), the subject identifier will also be visible to the OAuth client. If the token is a signed-and-encrypted JWT, or an opaque identifier, the token data which includes the subject will not be visible to the client.
-
Pairwise subject identifier – The access token subject is encrypted deterministically for the token audience, so that for a given end-user and resource server the identifier will be stable across all token issues. The encryption ensures that the local end-user identifier remains confidential and prevents correlation across different resource servers, even if they collude.
The setup and use of access tokens with pairwise subjects is described in a dedicated guide.
1.5 Additional claims
The Connect2id server supports three sources that can feed additional claims into access tokens.
Additional end-user claims
The Connect2id server claims source that feeds consented end-user claims into UserInfo responses and ID tokens can also be used with access tokens.
For tokens issued in the browser-based flows (code, implicit and hybrid) this is done in the consent, by specifying the name of the claim to include with an appropriate prefix:
-
The
access_token:prefix will make the claim appear as top-level claim in the access token, e.g.access_token:email. -
The
access_token:uip:prefix will make the claim appear as member within the optionaluip(preset userinfo) claim, e.g.access_token:uip:email. -
The
access_token:dat:prefix will make the claim appear as member within the optionaldat(data) claim, e.g.access_token:dat:email.
Use this source when you need to include user-specific claims (attributes) into the tokens.
Example access token claims composed with access_token:email:
{
"sub" : "449d693f-c0b8-4088-8ed6-6607d3c95853",
"cid" : "ieJ0iefo",
"scp" : [ "https://api.example.com/read" ],
"email" : "alice@mail.example.com",
...
}
Additional session claims
To include the acr and auth_time claims in access tokens, for example to
comply with the OAuth 2.0 extension that enables resource servers to request
user re-authentication (RFC 9470),
the session-based claims source can be used
as follows:
- Configure the server to source
claims from the
claimsobject of subject sessions and makeacrandauth_timeavailable:op.idToken.includeSubjectSessionClaims= op.sessionClaimsSource.enable=true op.sessionClaimsSource.supportedClaims=acr,auth_time - When the user is
authenticated, store
the
acrandauth_timevalues in the sessionclaimsobject, for example:{ "sub" : "449d693f-c0b8-4088-8ed6-6607d3c95853", "acr" : "https://loa.c2id.com/high", "claims" : { "auth_time" : "https://loa.c2id.com/high", "acr" : "1774860118" } } - In the submitted consent, include
the desired access token claims, for example:
{ "scope" : [ "https://api.example.com/read" ], "claims" : [ "access_token:acr", "access_token:auth_time" ] }
As long as the subject session remains active, the Connect2id server will these claims in the issued access tokens for the user.
Additional client data claims
The client registration data
field, which provides a generic JSON object for custom client metadata, can
also act as source for access token claims.
Example client registration with custom data:
{
"client_id" : "ieJ0iefo",
"grant_types" : [ "client_credentials" ],
"data" : {
"org_id" : "org-14738",
"org_name" : "Acme Inc",
"org_contact" : "admin@example.com"
},
...
}
To feed selected members from the client data field into the access tokens
issued to the client use the following configuration:
authzStore.accessToken.codec.jwt.copyClientData
Example configuration to feed the data.org_id member:
authzStore.accessToken.codec.jwt.copyClientData=org_id
Example resulting access token claims:
{
"sub" : "449d693f-c0b8-4088-8ed6-6607d3c95853",
"cid" : "ieJ0iefo",
"scp" : [ "https://api.example.com/read" ],
"org_id" : "org-14783",
...
}
This configuration works with all OAuth grants, but only for access tokens that are self-contained (JWT-encoded). It has no effect on identifier-based access tokens.
Additional authorisation data claims
Whenever a client is granted access to a resource or user identity the Connect2id server creates an internal authorisation object to represent the grant and the associated token properties.
Similar to client registrations, this object supports an optional dat (data)
member, a generic JSON object container intended for storing custom
authorisation parameters. This data object is automatically copied into the
minted access tokens, making it available to resource servers.
The custom authorisation data can be set for all OAuth grants. For the browser-based via consent object, for the rest via the grant handler’s plugin SPI.
Example access token claims with a custom dat claim:
{
"sub" : "449d693f-c0b8-4088-8ed6-6607d3c95853",
"cid" : "ieJ0iefo",
"scp" : [ "https://api.example.com/read" ],
"dat" : {
"enforce_single_use" : true,
"app_ctx" : "ext"
}
...
}
If required by the access token profile, selected “dat” members can be moved to become top-level claims with this configuration property:
authzStore.accessToken.codec.jwt.moveAuthzData
To make enforce_single_use a top-level access token claim:
authzStore.accessToken.codec.jwt.moveAuthzData=enforce_single_use
The resulting access token claims:
{
"sub" : "449d693f-c0b8-4088-8ed6-6607d3c95853",
"cid" : "ieJ0iefo",
"scp" : [ "https://api.example.com/read" ],
"enforce_single_use" : true,
"dat" : {
"app_ctx" : "ext"
}
...
}
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. Using an invalid or expired access token typically results in a 401 Unauthorized response from the resource server.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token" error_description="Invalid / expired access token"
The Connect2id server can issue a refresh token in the browser-based flows (typically the authorisation code flow), 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 subject 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
token=Ohw8choo.wii3ohCh.Eesh1AeDGong3eir
&token_type_hint=refresh_token
&client_id=xa9xesah
Optionally, a client can revoke a single refresh token without affecting other
tokens for the same end-user, by using the token_only=true parameter (if
enabled).
POST /token/revoke HTTP/1.1
Host: c2id.com
Content-Type: application/x-www-form-urlencoded
token=Ohw8choo.wii3ohCh.Eesh1AeDGong3eir
&token_type_hint=refresh_token
&token_only=true
&client_id=xa9xesah
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 must validate access tokens according to their encoding and binding method.
3.1 Self-contained (JWT)
- Parse the access token as a signed JWT.
- Ensure the JWT
typ(type) header matches the expected for the access token profile, e.g.at+jwt. - Ensure the JWT
alg(algorithm) matches the expected, e.g.RS256. - 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 JWTkidheader. - Ensure all required claims for the selected profile are present, e.g.
iss,sub,cid,scp,iat,expandjti. - Ensure the “iss” (issuer) claim matches the Connect2id server issuer
URL, e.g.
https://c2id.com. - Ensure the
exp(expiration time) is in the future. - Ensure the
aud(audience), if set, contains the resource server. - If the token must be client certificate bound, or of type DPoP, check the
binding by examining the
cnf(confirmation) claim. - Finally, get the token scope and any other necessary parameters to process the request.
If any check 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)
- 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.
- Ensure the
aud(audience), if set, contains the resource server. - If the token must be client certificate bound, or of type DPoP, check the binding by examining the “cnf” (confirmation) claim.
- 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.