Skip to content

Include acr and amr claims in stateless JWT access tokens#1033

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue/acr-amr-1
Jun 3, 2026
Merged

Include acr and amr claims in stateless JWT access tokens#1033
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue/acr-amr-1

Conversation

@vharseko
Copy link
Copy Markdown
Member

@vharseko vharseko commented Jun 3, 2026

Summary

This PR adds the authentication context class reference (acr) and the
authentication modules (authModules, i.e. the amr value) to the stateless
JWT access token
.

Until now these claims were only available in the id_token, the
/oauth2/userinfo endpoint and the /oauth2/tokeninfo endpoint. The stateless
access token was assembled in StatelessTokenStore.createAccessToken(...) from
a hard-coded claim set with no extension point, so neither the OIDC Claims
Script nor a custom Scope Implementation Class could influence it. As a result,
PEPs / API gateways that need acr/amr were forced to make an extra
/oauth2/tokeninfo round-trip per request.

With this change the claims are written directly into the access token payload,
exactly the same way they are already written into the refresh token in
createRefreshToken(...).

Motivation

  • Reported in OpenAM Discussion #1027.
  • The stateless access token already carried acr/amr for the refresh
    token
    but not for the access token — an inconsistency inherited from the
    original ForgeRock code.
  • Avoids an extra network round-trip to /oauth2/tokeninfo for downstream
    services that authorize on acr/amr.

Changes

OAuth2 (server)

  • StatelessTokenStore.createAccessToken(...):
    • After building the base JwtClaimsSetBuilder, extract acr
      (getAuthenticationContextClassReference()) and authModules
      (getAuthModules()) from:
      • the AuthorizationCode present in the request (authorization_code grant), and
      • the previous RefreshToken present in the request (refresh_token grant),
        which overrides the authorization-code values when present.
    • The acr and authModules claims are added only when a value is
      available
      , keeping the change additive and backward-compatible.
    • Reuses the existing authCode lookup (already read for the
      realm_access.roles logic); no new imports required — ACR and
      AUTH_MODULES constants were already imported.

Documentation

  • admin-guide/chap-openid-connect.adoc: the decoded stateless access token
    example now shows the acr and authModules claims, with a NOTE explaining
    that they appear only when the originating authorization code / refresh token
    carries them (e.g. omitted for the client_credentials grant).

Tests

  • StatelessTokenStoreTest (new tests):
    • whenAuthorizationCodePresentAcrAndAmrGetAddedToAccessToken
    • whenRefreshTokenPresentAcrAndAmrGetAddedToAccessToken
    • whenRefreshTokenPresentItOverridesAuthorizationCodeAcrAndAmr
    • whenNoAcrOrAmrAvailableTheyAreNotAddedToAccessToken
    • Shared mock setup extracted into givenBaseProviderSettings().

Behavioral / compatibility notes

  • Additive only. When no authorization code or refresh token with acr /
    authModules is available (for example, the client_credentials grant), the
    claims are simply omitted — existing tokens are unaffected.
  • Consistent with refresh tokens. The logic and claim names (acr,
    authModules) match createRefreshToken(...).
  • No configuration changes, no new scripts, no new provider settings.

How to test

# Unit tests
mvn -pl openam-oauth2 test -Dtest=StatelessTokenStoreTest

Result: Tests run: 6, Failures: 0, Errors: 0, Skipped: 0.

Manual:

  1. Configure an OAuth2/OIDC provider with stateless OAuth2 tokens enabled.
  2. Obtain an access token via the authorization_code grant after
    authenticating through a chain that yields an acr / auth modules.
  3. Decode the JWT access token (or call /oauth2/tokeninfo) and confirm the
    acr and authModules claims are present in the payload.
  4. Refresh the token and confirm acr / authModules are preserved from the
    refresh token.

Related

Propagate the authentication context class reference (`acr`) and the
authentication modules (`authModules`, i.e. `amr`) into the stateless JWT
access token, mirroring the behaviour already present in
`StatelessTokenStore.createRefreshToken`.

Previously these claims were only emitted into the `id_token`, the
`/oauth2/userinfo` and `/oauth2/tokeninfo` responses. The stateless access
token was built from a hard-coded claim set with no extension point, so PEPs /
API gateways had to make an extra `/oauth2/tokeninfo` round-trip to read
`acr`/`amr`. They can now be read directly from the access token payload.

The values are sourced from:
- the `AuthorizationCode` (authorization_code grant), and
- the previous `RefreshToken` (refresh_token grant), which overrides the
  authorization-code values when present.

Both claims are added only when a value is available, so the change is
additive and backward-compatible (e.g. client_credentials grant emits neither).

Changes:
- openam-oauth2: StatelessTokenStore.createAccessToken now extracts and adds
  the `acr` and `authModules` claims.
- openam-oauth2: StatelessTokenStoreTest adds 4 tests covering
  authorization-code source, refresh-token source, refresh-token precedence,
  and the no-source case.
- docs: chap-openid-connect.adoc updates the decoded stateless access token
  example and adds a note explaining when `acr`/`authModules` appear.

Refs: OpenAM Discussion OpenIdentityPlatform#1027
@vharseko vharseko requested a review from maximthomas June 3, 2026 15:39
@vharseko vharseko changed the title Include acr and amr claims in stateless JWT access tokens (Discussion #1027) Include acr and amr claims in stateless JWT access tokens Jun 3, 2026
@vharseko vharseko merged commit a43aba8 into OpenIdentityPlatform:master Jun 3, 2026
16 checks passed
@vharseko vharseko deleted the issue/acr-amr-1 branch June 3, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants