Access token
This guide explains how to configure the properties of access tokens issued by the Connect2id server, how to manage their lifecycle and how resource servers can validate them.
1. Access token properties
This table outlines the available 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)
|
Identifier based (opaque) |
|
Subject identifier | Public |
Pairwise (encrypted) | |
Custom claims | End-user claims |
Client data | |
Authorisation data |
1.1 Lifetime
The duration of access token validity. The default lifetime is configured in authzStore.accessToken.defaultLifetime and is originally set to 600 seconds (10 minutes):
authzStore.accessToken.defaultLifetime=600
The default lifetime can be overridden at login time by setting the optional
access_token.lifetime
parameter in the
consent object. The overridden lifetime
will apply only to the access token(s) that are issued for the current end-user
and 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.
When a refresh token is issued together with the access token, the Connect2id server will ensure the access token lifetime does not exceed the refresh token lifetime or maximum idle time, by trimming the access token lifetime when necessary to achieve parity.
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" : "app:read app:write"
}
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 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 RFC 9449.
1.3 Encoding
OAuth 2.0 doesn’t specify the content of the access token. This aspect is determined by the authorisation server, potentially in agreement with the resource 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 a cryptographic check of its digital signature using keys published by the Connect2id server.
-
Identifier-based – The authorisation is stored in the Connect2id server and the token represents a long random key used to access it at the introspection endpoint.
Self-contained is the default encoding of access tokens minted by the Connect2id server.
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 public 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 makes the local end-user identifier confidential, and if the Connect2id server is used to secure access to APIs belonging to multiple third party APIs, also technically impossible to correlate if the parties collude.
The setup and use of access tokens with pairwise subjects is described in a dedicated guide.
1.5 Custom claims
The Connect2id server supports three sources that can feed additional (custom) claims into the access tokens.
Feeding 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",
"client_id" : "ieJ0iefo",
"scope" : "https://api.example.com/read",
"email" : "alice@mail.example.com",
...
}
Feeding client custom data
The client registration “data” field, which offers a generic JSON object container for custom client-related parameters, 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",
"client_id" : "ieJ0iefo",
"scope" : "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.
Feeding authorisation custom data
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 avaiable 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",
"client_id" : "ieJ0iefo",
"scope" : "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",
"client_id" : "ieJ0iefo",
"scope" : "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. 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)
- 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 JWT “kid” header.
- 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”.
- 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 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)
- 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.