RFC 9068: JWT Profile for OAuth 2.0 Access Tokens

RFC 9068: JWT Profile for OAuth 2.0 Access Tokens

Learn how RFC 9068 standardizes JSON Web Token profiles for OAuth 2.0 access tokens to ensure seamless, secure cross-platform interoperability.

For years, OAuth 2.0 access tokens were the “Wild West” of identity, with every provider essentially making up their own proprietary format for resource servers to decode.

RFC 9068 finally brings order to the chaos by standardizing the JSON Web Token (JWT) profile, ensuring your tokens speak a universal language across different systems.

This guide breaks down how to implement this standard to boost your API’s interoperability and security without the usual integration headaches.

1. The Core Objective

The core objective of the RFC can be defined as:

To ensure interoperability between Authorisation Servers (AS) and Resource Servers (RS). It prevents “Token Confusion” attacks where an ID Token might be mistakenly accepted as an Access Token.

  • Standardization of Token Format: While [RFC6749] does not mandate a specific format, this profile standardizes JWT [RFC7519] as the interoperable format for access tokens.
  • Decoupled Validation: Enables Resource Servers to validate tokens directly without constant round-trips to the Authorization Server, which is essential when entities are not co-located.
  • Interoperability : Replaces fragmented, vendor-specific claim names and layouts with a common set of mandatory/optional claims and syntax.

2. Mandatory Header Claims

The header is critical for security and explicit typing.

  • typ: MUST be at+jwt (or application/at+jwt). This is the “explicit typing” that prevents ID Token substitution.
  • alg: MUST NOT be none. RS256 is the recommended baseline.
  • kid: Recommended to help the RS find the correct public key for validation.Mandatory Header Claims

3. Mandatory Payload Claims

An RFC 9068 compliant token must contain:

ClaimRequiredDescription
issYesIssuer URL of the AS.
expYesExpiration time (Unix timestamp).
audYesThe Resource Server(s) the token is intended for.
subYesThe subject (User ID or Client ID for client credentials).
client_idYesThe identifier of the client that requested the token.
iatYesIssued-at time.
jtiYesUnique identifier for the token (prevents replay).

4. Optional & Authorization Claims

  • scope: Space-separated string of scopes (from RFC 6749).
  • groups / roles / entitlements: Standardized claims for fine-grained authorization (derived from the SCIM/RFC 7643 schema).
  • auth_time / acr / amr: Authentication context (useful if the RS needs to know how the user authenticated).

5. Requesting a JWT Access Token

You must ensure that the client properly identifies the target resource so the Authorization Server (AS) can populate the aud (audience) claim correctly. This is often done using Resource Indicators (RFC 8707).

The client sends a request to the Token Endpoint. Note the resource parameter which dictates the aud in the resulting JWT.

POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=n0O6n6W9t7&
redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb&
client_id=s6BhdRkqt3&
resource=https%3A%2F%2Fapi.example.com%2Fpayments

Once the AS validates the request, it issues a JWT. Below is the decoded structure of an RFC 9068 compliant Access Token.

Header The typ header is the most critical security feature of this RFC.

{
  "typ": "at+jwt",
  "alg": "RS256",
  "kid": "j13-77"
}

Payload (Claim Set)

This example includes the mandatory claims, authorization scopes, and identity attributes for a delegated user session.

{
  "iss": "https://as.example.com/",
  "sub": "5445453435",
  "aud": "https://api.example.com/payments",
  "client_id": "s6BhdRkqt3",
  "iat": 1614612000,
  "exp": 1614615600,
  "jti": "b77c7263-ad62-4299-b3a1-2d7c50a4d04d",
  "scope": "openid profile read:payments",
  "auth_time": 1614611980,
  "acr": "https://www.rfc-editor.org/rfc/rfc6711#section-2.2",
  "groups": ["finance-admins", "internal-users"]
}

6. Validation Logic (The Security Architect’s Checklist)

When a Resource Server (RS) receives this token, it MUST perform these steps in order:

  1. Check Type: Verify typ is at+jwt. Reject anything else.
  2. Verify Signature: Ensure the token was signed by the trusted iss.
  3. Check Issuer: iss must match the expected AS.
  4. Check Audience: The aud claim must contain the RS’s own identifier.
  5. Check Expiration: exp > Current Time (with a small leeway for clock skew).
  6. Check Scope: Ensure the scope claims grant enough permission for the requested operation.

7. Critical Security Considerations

  • Resource Indicators (RFC 8707): If a client requests a token for a specific resource, the AS should use that value for the aud claim.
  • Privacy: Use “Pairwise” Subject Identifiers (PPI) if you want to prevent different Resource Servers from correlating the same user across different APIs.
  • Cross-JWT Confusion: Without the at+jwt type, an attacker could potentially use an ID Token (intended for the Client) as an Access Token (intended for the API). RFC 9068 closes this hole.

8. Quick Comparison: RFC 9068 vs. RFC 7523

  • RFC 7523: Using a JWT to request a token (JWT as a Grant).
  • RFC 9068: The format of the resulting Access Token itself.

9. Summary

RFC 9068 standardizes the JSON Web Token (JWT) format for OAuth 2.0 access tokens to resolve the historical lack of interoperability between different vendors.

By mandating explicit header typing and specific payload claims, the profile effectively prevents security risks like token substitution and cross-JWT confusion.

This standard enables resource servers to perform secure, self-contained validation of access tokens without requiring constant coordination with the authorization server.