JWT Security and OAuth 2.0/OIDC Requirements — ASVS 5.0 V9 & V10
    Authentication

    JWT Security and OAuth 2.0/OIDC Requirements — ASVS 5.0 V9 & V10

    Algorithm confusion, missing claim validation, PKCE, refresh token rotation — what OWASP ASVS 5.0 requires for self-contained tokens and OAuth/OIDC implementations.

    Reverse PolarityApril 11, 202612 min read

    OWASP ASVS 5.0, released in May 2025, splits token security across two chapters. V9 covers self-contained tokens — JWTs being the dominant case — regardless of whether they are issued inside an OAuth flow. V10 covers OAuth 2.0 and OpenID Connect as complete protocol stacks. The split matters: a JWT can exist with no OAuth in sight (inter-service authorization, signed session data, email verification links), and V9 requirements apply to all of those cases. V10 then extends those baseline requirements with the additional constraints that OAuth and OIDC roles impose on each other.

    This article walks through both chapters as a coherent unit, with the implementation-level detail that matters if you're building an authorization server, validating tokens in a resource server, or auditing a client integration.

    Diagram — ASVS 5.0 — JWT Validation Checklist & OAuth/OIDC Trust Controls
    Diagram

    V9: Self-Contained Tokens

    Token Source and Integrity (V9.1)

    The three Level 1 requirements in V9.1 address the most exploited class of JWT bugs: cases where a receiving service accepts a token whose signature it never actually verified, or where an attacker controls the key material used for verification.

    Requirement 9.1.1 states that tokens must be validated using their digital signature or MAC before any content is trusted. This sounds obvious, but the failure mode is not always a deliberate skip — it often appears as a misconfigured library where the verify flag defaults to false, or where signature validation occurs after the claims have already been read and acted upon.

    Requirement 9.1.2 mandates an allowlist of permitted algorithms. The "alg": "none" attack is the canonical example: an attacker strips the signature, sets the algorithm header to none, and sends the token to a library that accepts it. The requirement also addresses the RS256/HS256 key confusion attack. When a library supports both symmetric (HS256) and asymmetric (RS256) algorithms, an attacker can take an RS256-signed token, change the algorithm header to HS256, and sign it using the server's public RSA key — which the library then interprets as the HS256 symmetric secret. A compliant implementation maintains separate algorithm allowlists per context, ideally restricting a given verification context to either symmetric or asymmetric algorithms, not both simultaneously.

    {
      "alg": "RS256",
      "typ": "JWT",
      "kid": "2026-06-01-signing-key"
    }
    

    The kid (key ID) header is the right mechanism for key rotation: the verifier looks up the key in its pre-configured key store using the kid value. What it must not do is treat the kid as a database query, an OS path, or any other injectable parameter.

    Requirement 9.1.3 addresses the related class of header injection attacks. JWTs and other JWS structures can include headers that reference external key material: jku (JWK Set URL), x5u (X.509 certificate URL), and jwk (inline public key). If a verifier resolves these values at runtime from the token itself, an attacker who can craft a token can point the verifier at an attacker-controlled JWKS endpoint containing their own public key, then sign the token with the corresponding private key. ASVS requires that key material come exclusively from pre-configured, trusted sources for the token's issuer. Any jku, x5u, or jwk value in a token header must be validated against an allowlist before being used — not resolved dynamically.

    For JWKS-based verification (the standard pattern for public-key JWT issuers), the correct model is:

    1. At startup, load the issuer's JWKS endpoint URL from trusted configuration.
    2. Periodically refresh the key set from that URL (with appropriate caching and TTL behavior).
    3. On token validation, match the kid header against keys already loaded into the local key set.
    4. Never follow jku or jwk values from an incoming token header.

    Token Content Validation (V9.2)

    V9.2 requirements operate after the signature is verified. They ensure the token is valid for the specific service receiving it at the specific time it is received.

    Requirement 9.2.1 requires enforcement of nbf (not before) and exp (expiry) claims when present. A token with an exp claim must be rejected if the current time is after the expiry. A token with an nbf claim must be rejected if the current time is before that value. The edge case to handle explicitly is clock skew between the issuer and verifier — a small tolerance (typically ≤ 60 seconds) is acceptable and expected, but the tolerance should be bounded and configurable.

    {
      "iss": "https://auth.example.com",
      "sub": "user_a8f3b2",
      "aud": "https://api.example.com/v2",
      "iat": 1748908800,
      "nbf": 1748908800,
      "exp": 1748912400,
      "scope": "read:orders write:orders"
    }
    

    Requirement 9.2.2 requires type validation: a service must verify that the token it receives is the correct token type for the operation being performed. Access tokens authorize API calls; ID tokens prove authentication. Using an ID token for an API authorization decision, or accepting an access token as proof of user identity in an OIDC context, violates this requirement. In practice, this is enforced by checking the typ header claim or by including a custom claim that identifies the token's purpose, and validating it before processing.

    Requirements 9.2.3 and 9.2.4 address audience validation. The aud claim identifies the intended recipients of the token. The verifying service must compare its own identifier against the aud claim and reject the token if its identifier is not present. This prevents cross-service token replay: a token issued for https://api.example.com/v2 cannot be replayed against https://admin.example.com if both services enforce audience validation. Requirement 9.2.4 extends this: if a single issuer uses one private key for multiple audiences, each token must include a restrictive aud claim that uniquely identifies the intended audience. Without this, a token legitimately obtained for one service becomes valid at all services signed by the same key.


    Key Management Considerations

    ASVS V9 does not prescribe specific key rotation schedules, but the requirements collectively imply the infrastructure needed to support them. Key rotation requires:

    • A kid-based lookup mechanism so verifiers can match tokens to the correct key across a rotation boundary
    • A JWKS endpoint (for public-key issuers) that serves current and recently rotated public keys, enabling verifiers to validate tokens signed before the rotation while also accepting tokens signed after
    • A pre-configured allowlist of trusted JWKS URLs per issuer, so the rotation process does not require per-service configuration changes beyond the issuer's published endpoint

    The jku-injection risk means that JWKS URL resolution must be centralized and controlled. A verifier that blindly follows the jku header cannot safely perform key rotation at all — it cannot distinguish between a legitimate new key URL and an attacker-injected one.

    For symmetric tokens (HS256/HS384/HS512), rotation requires coordinating the secret across all issuers and verifiers simultaneously, which is why asymmetric algorithms with JWKS-based distribution are the standard for any multi-service deployment.


    V10: OAuth 2.0 and OIDC

    OAuth defines four roles (client, resource server, authorization server, resource owner) and OIDC adds two (relying party, OpenID Provider). ASVS V10 is organized by role, which makes it practical to use as a checklist during implementation or review: take the section that matches the component under test, verify each requirement independently.

    Generic OAuth/OIDC Security (V10.1)

    Two Level 2 requirements apply to all OAuth/OIDC implementations regardless of role.

    Requirement 10.1.1 mandates token minimization in BFF (Backend-For-Frontend) architectures. When a browser-based SPA uses a server-side backend to handle OAuth flows, access tokens and refresh tokens must remain on the server side. They must not be surfaced to the JavaScript frontend. The browser holds a session cookie bound to the BFF; the BFF holds the tokens. This eliminates the XSS-to-token-theft attack surface that exists in any SPA that stores tokens in localStorage or sessionStorage.

    Requirement 10.1.2 requires that clients bind authorization responses to the initiating session. The code_verifier (PKCE), state, and OIDC nonce values must all be: (a) cryptographically random, (b) specific to the transaction, and (c) securely bound to the user-agent session in which the authorization flow started. This prevents authorization code injection — where an attacker intercepts or replaces the authorization code in the callback — and CSRF attacks that attempt to trigger a token exchange on behalf of another user.

    OAuth Client (V10.2)

    Requirement 10.2.1 requires CSRF protection for the authorization code flow. Either PKCE or state parameter validation satisfies this at Level 2. In practice, PKCE is the better choice: it also protects against authorization code interception by an attacker who can observe the redirect URI but cannot access the PKCE code_verifier.

    Requirement 10.2.2 is relevant when a client interacts with more than one authorization server. Mix-up attacks exploit the fact that a client cannot always determine which authorization server responded to a given request. The defense is to require the authorization server to return its iss parameter in the authorization response and the token response, and for the client to validate that it matches the expected issuer. RFC 9207 standardizes this behavior.

    Requirement 10.2.3 (Level 3) requires that clients request only the minimum scopes necessary. Over-permissioned tokens increase the impact of any token compromise or misuse. This is an implementation discipline requirement, not just a configuration one: the requested scopes must be reviewed and reduced to match the actual operations the client performs.

    OAuth Resource Server (V10.3)

    The resource server receives access tokens from clients and must validate them before making authorization decisions. ASVS maps the full validation chain across four Level 2 requirements.

    Requirement 10.3.1 mirrors V9.2.3 in the OAuth context: the resource server must validate that the access token's aud claim includes its own identifier. A token issued for a different API must be rejected, even if the signature is valid and the claims are otherwise well-formed.

    Requirement 10.3.2 requires authorization decisions to incorporate the delegated authorization claims in the token. The scope claim defines what operations the client is permitted to perform. The sub claim identifies the user on whose behalf the request is made. The authorization_details claim (RFC 9396, Rich Authorization Requests) carries fine-grained authorization information when scopes are insufficient. All present claims must be part of the authorization decision — validating the signature and ignoring the scope claim is not compliant.

    Requirement 10.3.3 specifies how to identify a user from a token: use the combination of iss + sub, not sub alone. The sub claim is only unique within the scope of a specific issuer. A user at https://auth.provider-a.com with sub: "12345" is a different entity than a user at https://auth.provider-b.com with sub: "12345". If a resource server accepts tokens from multiple issuers, sub alone is not a stable user identifier.

    Requirement 10.3.5 (Level 3) requires sender-constrained access tokens to prevent stolen token replay. Two mechanisms satisfy this: Mutual TLS for OAuth 2 (RFC 8705) and DPoP — Demonstration of Proof of Possession (RFC 9449). With DPoP, the client generates an ephemeral key pair per request and includes a signed proof in the DPoP header. The resource server validates that the proof matches the key bound to the token, making the token useless to an attacker who obtained it without the corresponding private key.

    OAuth Authorization Server (V10.4)

    The authorization server section contains the most requirements in V10 and includes several with direct impact on common implementation mistakes.

    Requirement 10.4.1 requires exact-string matching for redirect URI validation. Pattern matching and prefix matching are insufficient — they create open redirect vulnerabilities that allow authorization codes to be sent to attacker-controlled URLs. Pre-register the exact URIs the client will use, including scheme, host, port, and path, and reject any redirect URI that does not exactly match a registered value.

    Requirements 10.4.2 and 10.4.3 address authorization code security. Codes must be single-use: if a code that has already been used to issue a token is presented again, the authorization server must reject the request and revoke all tokens previously issued for that code. This is the "authorization code replay" defense. The code lifetime is bounded at 10 minutes for Level 1 and Level 2, and 1 minute for Level 3. Short-lived codes reduce the window in which a stolen or leaked code is usable.

    Requirement 10.4.4 explicitly prohibits two legacy grant types: the Implicit flow (response_type=token) and the Resource Owner Password Credentials (ROPC) flow. Both are deprecated in OAuth 2.1. The Implicit flow exposes access tokens in the URL fragment and provides no mechanism for sender-constraining. ROPC requires the client to handle user credentials directly, defeating the purpose of delegated authorization.

    Requirement 10.4.6 mandates PKCE for the authorization code grant at Level 2. The specific requirements are:

    • The authorization request must include a valid code_challenge parameter
    • The code_challenge_method value plain must not be accepted — only S256 is permitted
    • The token request must include the code_verifier parameter, and the authorization server must verify that BASE64URL(SHA256(code_verifier)) matches the code_challenge stored with the authorization code

    The plain method provides no security benefit over transmitting the verifier directly — it only adds obfuscation. S256 ensures that even if the code_challenge is observed during the authorization request (e.g., in server logs), the code_verifier cannot be derived from it.

    code_verifier:  dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
    code_challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    method:         S256
    

    Requirements 10.4.5 and 10.4.8 cover refresh token security. For public clients (those that cannot maintain a client secret), sender-constrained refresh tokens via DPoP or mTLS are the preferred defense against refresh token replay at all levels. Where sender-constraining is not implemented, refresh token rotation is permitted for Level 1 and Level 2: each use of a refresh token must invalidate the token and issue a new one. Critically, if a used and invalidated refresh token is presented again, all refresh tokens for that authorization must be revoked — this detects a theft scenario where both the legitimate client and an attacker attempt to use the token. Requirement 10.4.8 adds that refresh tokens must have an absolute expiration, regardless of whether sliding expiration is also applied. An active session cannot extend a refresh token's lifetime indefinitely.

    Requirement 10.4.9 requires that users can revoke refresh tokens and reference access tokens through the authorization server's UI. This is a user-facing control: if a user suspects their session has been compromised, they must have a mechanism to revoke the credentials without needing to contact support.

    Requirement 10.4.10 requires that confidential clients authenticate themselves for all backchannel requests to the authorization server. Token requests, pushed authorization requests (PAR), and token revocation requests must all include client authentication. A confidential client that sends only a client_id without a client_secret or equivalent strong credential is not compliant.

    At Level 3, requirements 10.4.13 through 10.4.16 require PAR (Pushed Authorization Requests, RFC 9126) for all code grants, sender-constrained access tokens for all issued tokens, and strong client authentication using public-key cryptography (mutual TLS or private_key_jwt). PAR moves the authorization request parameters from the front-channel (browser redirect) to a direct HTTPS call from client to authorization server, preventing parameter tampering and enabling the server to validate request integrity before the user sees the consent screen.

    OIDC Client — Relying Party (V10.5)

    OIDC clients inherit all OAuth client requirements and add ID token-specific validations.

    Requirement 10.5.1 requires nonce validation to prevent ID token replay. The relying party generates a cryptographically random nonce value, includes it in the authentication request, and then verifies that the same nonce appears in the ID token's nonce claim. This binds the token to the specific authentication transaction and prevents an attacker from reusing a previously issued ID token in a different session.

    Requirement 10.5.2 requires using sub as the primary user identifier, noting that sub cannot be reassigned to other users within the scope of a given identity provider. The combination of iss + sub (following V9.2 and 10.3.3) is the stable, non-reassignable identifier across multi-IdP deployments.

    Requirement 10.5.3 protects against authorization server metadata impersonation. When an RP uses OIDC Discovery to retrieve authorization server metadata (the .well-known/openid-configuration endpoint), it must verify that the issuer field in the metadata exactly matches the pre-configured expected issuer URL. A malicious actor who can serve altered discovery metadata could substitute their own token endpoint or JWKS URL.

    Requirement 10.5.4 requires audience validation on ID tokens. The aud claim in an ID token must equal the client_id of the relying party. Unlike access tokens, which may have multiple audience entries, an ID token is issued specifically for the client that requested it and must not be accepted by any other client.

    Requirement 10.5.5 addresses OIDC back-channel logout, a mechanism where the OpenID Provider pushes a logout token to relying parties when a session ends centrally. The RP must validate that the logout token: has typ: "logout+jwt", contains the correct events claim with the back-channel logout event member name, and does not contain a nonce claim. The absence of nonce in logout tokens is normative — the spec explicitly prohibits it to prevent logout tokens from being confused with authentication tokens.

    OpenID Provider (V10.6)

    Requirement 10.6.1 restricts the permitted response_type values to code, ciba, id_token, and id_token code. The token response type (any variant of the Implicit flow) is prohibited. The code flow is preferred over id_token code (the Hybrid flow), which adds complexity without commensurate security benefit when the code flow with PKCE is implemented correctly.

    Requirement 10.6.2 requires the OpenID Provider to mitigate forced logout attacks. Front-channel logout can be abused by any third party that can embed an invisible iframe pointing at the RP's logout endpoint. Back-channel logout is more robust but requires the OP to track active RP sessions accurately. The requirement addresses this at the OP level: before processing an RP-initiated logout, the OP must obtain explicit user confirmation or validate the id_token_hint parameter in the logout request.

    Three Level 2 requirements govern how authorization servers handle user consent. The authorization server must ensure the user actively consents to each authorization request (10.7.1), must present clear information about what is being consented to including scope descriptions, the client's identity, and the lifetime of the authorization (10.7.2), and must provide users with the ability to review, modify, and revoke previously granted consents (10.7.3). These requirements exist partly to prevent social-engineering attacks where a malicious application uses consent screen confusion to obtain broader permissions than the user intends to grant.


    Overlap with V6 and V7

    V9 and V10 do not operate in isolation. Two other chapters carry directly relevant requirements.

    V6 (Authentication) section V6.8 covers authentication delegation: when an application validates identity through an external IdP, it must validate the provider's assertions including aud, iss, expiry, and — at Level 3 — acr (authentication context class reference) and amr (authentication methods reference). The acr and amr claims also appear in V10.3.4, where the resource server must verify that an access token satisfies required authentication strength constraints. An API that requires step-up authentication — re-authentication with a hardware key before a high-risk operation — enforces this through the acr claim in the access token rather than by managing its own authentication state.

    V7 (Session Management) explicitly addresses self-contained tokens in its session termination requirements (V7.4). When a stateless token is used as a session mechanism, logout cannot simply delete a server-side session record — the token remains valid until it expires. ASVS requires mitigating controls: a token blocklist, per-user revocation timestamps checked on each request, or per-user signing key rotation that invalidates all tokens signed with the previous key. The V7 requirement mirrors the revocation discussion in V10.4.9, but applies to any self-contained token used for session purposes, not just OAuth tokens. Any JWT used as a session carrier must have a revocation strategy — short expiry alone is not sufficient for a logout mechanism that users expect to be immediate.

    Token size is an indirect constraint woven through both chapters. JWTs carrying extensive claims can exceed HTTP header size limits when used as Bearer tokens, can be blocked by WAF rules, and can cause performance issues at high throughput. V4 (API and Web Service) flags rejection of overly long header fields as a DoS protection measure. The practical guidance is to keep access token payloads lean: iss, sub, aud, exp, iat, scope, and whatever authorization details are genuinely needed at the resource server. User profile data belongs in a userinfo endpoint response, not in the access token.


    Implementation Summary by Role

    If you are building an authorization server, the non-negotiable Level 1 and Level 2 requirements are: exact-match redirect URI validation, single-use authorization codes with automatic revocation on replay, PKCE with S256 only, prohibition of Implicit and ROPC flows, refresh token rotation for public clients with revocation-on-reuse, absolute refresh token expiration, and confidential client authentication on all backchannel requests. At Level 3, add PAR for all code grants, sender-constrained tokens (DPoP or mTLS), and strong client authentication via public-key methods.

    If you are building a resource server, validate every access token's signature, expiry, iss, and aud before making authorization decisions. Base authorization on the scope, sub, and authorization_details claims — not just on whether the token validates. Identify users by iss + sub, not sub alone. At Level 3, require DPoP or mTLS sender-constraining.

    If you are building an OAuth client, enforce PKCE on every authorization request. Validate the state or iss parameters in authorization responses when interacting with multiple authorization servers. In BFF architectures, keep tokens server-side. In OIDC flows, validate nonce, aud, iss, exp, and logout token structure.

    For JWT validation in any context — OAuth or otherwise — enforce the V9.1 requirements unconditionally: algorithm allowlist with no none, key material from pre-configured trusted sources only, no dynamic resolution of jku/x5u/jwk header values. Then enforce V9.2: nbf/exp enforcement, token type validation, and audience validation against a service-specific allowlist.

    More Articles