# Type of document: How-to guide Product: NGINX Gateway Fabric > How to configure JSON Web Token (JWT) claim validation in F5 NGINX Gateway Fabric using the `AuthenticationFilter` custom resource definition (CRD). --- This guide describes how to configure JWT claim validation in F5 NGINX Gateway Fabric using the AuthenticationFilter custom resource definition (CRD). JWT claim validation adds authorization after [JWT authentication](/ngf/traffic-security/jwt-authentication.md) and [OIDC authentication](/ngf/traffic-security/oidc-authentication.md). Authentication checks whether a token is valid and signed correctly. Claim validation checks claims in the token payload. You can require a specific issuer, audience, or custom claim before users access your application. By following these instructions, you will configure an AuthenticationFilter with claim validation rules and verify that only tokens containing the expected claims are allowed through. For demonstration purposes, in this document you will deploy and configure a `type: JWT` AuthenticationFilter using `mode: File`. **Note:** JWT claim validation requires NGINX Plus. ## Overview JWT claim validation is configured through the `authorization` field on the AuthenticationFilter spec. It uses a two-level `require` model that controls how rules and claims are evaluated. **Top-level require** The top-level `require` field (`authorization.require`) controls how **rules** relate to each other: - **Any** (default) — A request is authorized if **any one** of the rules are satisfied. - **All** — A request is authorized only if **every** rule is satisfied. **Per-rule require** Each rule has its own `require` field (`rules[].require`) that controls how the **claims within that rule** relate to each other: - **Any** (default) — The rule is satisfied if **any one** of its claims matches. - **All** — The rule is satisfied only if **every** claim in the rule matches. **How the two levels work together** Consider an AuthenticationFilter with `authorization.require: Any` and two rules, each with `require: All`: - Rule 0 requires **all** of: `iss=issuer-1` **and** `aud=api` - Rule 1 requires **all** of: `iss=issuer-2` **and** `aud=admin` Since the top-level require is `Any`, a request is authorized if the token satisfies **either** rule 0 **or** rule 1. A token that only partially matches both rules would be rejected as it does not fully satisfy either rule. ## Before you begin - [Install](/ngf/install/) NGINX Gateway Fabric with NGINX Plus. ## Set up In this part of the document, we will set up several resources in your cluster to demonstrate the authorization field of the AuthenticationFilter CRD. ### Deploy sample applications To deploy the `coffee` and `tea` applications, run the following YAML with `kubectl apply`: ```yaml kubectl apply -f - < GW_PORT= ``` ### Generate a JWKS and create a Secret For testing purposes, the following example shows a simple JWKS with a single RSA key. In production, use properly generated keys from your identity provider or key management system. Save the JWKS to a file called `auth` and create a Secret: ```shell cat < auth { "keys": [ { "kty": "RSA", "kid": "test-key", "use": "sig", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB" } ] } EOF kubectl create secret generic jwks-secret --from-file=auth ``` **Note:** This example JWKS is for demonstration only. In production, use keys from your identity provider or key management system. --- ## Configure JWT claim validation This example creates an AuthenticationFilter with two rules that enforce different issuer and audience combinations. The top-level `require` is set to `Any`, and each rule's `require` is set to `All`. This means a request is authorized if its JWT satisfies **all** claims in rule 0 **or** all claims in rule 1. ### Create the AuthenticationFilter Deploy the AuthenticationFilter with claim validation rules by running the following YAML with `kubectl apply`: ```yaml kubectl apply -f - < ``` ### Understanding the configuration - **`authorization.require: Any`** — The request passes if **any** rule is satisfied. - **Rule 0** (`require: All`) — The JWT must contain **both** `iss` equal to `https://issuer-1.example.com` **and** `aud` equal to `api`. - **Rule 1** (`require: All`) — The JWT must contain **both** `iss` equal to `https://issuer-2.example.com` **and** `aud` equal to `admin`. The following table summarizes which tokens are authorized: | Token claims | Rule 0 | Rule 1 | Result | |---|---|---|---| | `iss=issuer-1`, `aud=api` | ✅ All matched | ❌ | ✅ Authorized | | `iss=issuer-2`, `aud=admin` | ❌ | ✅ All matched | ✅ Authorized | | `iss=issuer-1`, `aud=admin` | ❌ Partial | ❌ Partial | ❌ Rejected | | `iss=issuer-2`, `aud=api` | ❌ Partial | ❌ Partial | ❌ Rejected | ### Deploy an HTTPRoute referencing the AuthenticationFilter Deploy an HTTPRoute that applies the AuthenticationFilter to the `/coffee` path. The `/tea` path has no authentication and responds normally. Run the following YAML with `kubectl apply`: ```yaml kubectl apply -f - < ``` ### Verify JWT claim validation **Note:** Your clients should be able to resolve "cafe.example.com" to the public IP of the NGINX Service. This guide simulates that using curl's `--resolve` option. To test claim validation, you need JWTs signed with the private key corresponding to the public key in your JWKS. You can use [jwt.io](https://jwt.io) or other JWT tools to generate tokens with different claim payloads. Store each token in a shell variable. **Token matching rule 0** (`iss=https://issuer-1.example.com`, `aud=api`): ```shell JWT_RULE0="" ``` **Token matching rule 1** (`iss=https://issuer-2.example.com`, `aud=admin`): ```shell JWT_RULE1="" ``` **Token matching neither rule** (`iss=https://issuer-1.example.com`, `aud=admin`): ```shell JWT_NEITHER="" ``` Access `/coffee` with a token matching rule 0 ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "Authorization: Bearer $JWT_RULE0" ``` ```text Server address: 192.0.2.7:8080 Server name: coffee-654ddf664b-nhhvr Date: 10/Mar/2026:15:20:15 +0000 URI: /coffee Request ID: 13a925b2514b62c45ea4a79800248d5c ``` The request succeeds as the token satisfies all claims in rule 0. Access `/coffee` with a token matching rule 1 ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "Authorization: Bearer $JWT_RULE1" ``` ```text Server address: 192.0.2.7:8080 Server name: coffee-654ddf664b-nhhvr Date: 02/Sep/2026:15:21:30 +0000 URI: /coffee Request ID: 7b2e4a1c9f0d3e5a8c6b4d2f1a0e9c8b ``` The request succeeds as the token satisfies all claims in rule 1. Access `/coffee` with a token matching neither rule ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "Authorization: Bearer $JWT_NEITHER" ``` ```text 401 Authorization Required

401 Authorization Required


nginx
``` The request is rejected. Although the token has valid claims, it only partially matches each rule (`iss` from rule 0 and `aud` from rule 1). Since each rule requires **all** claims to match and the token does not fully satisfy either rule, authorization fails. Access `/coffee` without a token ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee ``` ```text 401 Authorization Required

401 Authorization Required


nginx
``` Without a JWT, the request fails authentication before claim validation is evaluated. Access `/tea` without authentication ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/tea ``` ```text Server address: 10.244.0.10:8080 Server name: tea-75bc9f4b6d-ms2n8 Date: 03/Sep/2026:15:36:26 +0000 URI: /tea Request ID: c7eb0509303de1c160cb7e7d2ac1d99f ``` The `/tea` path has no AuthenticationFilter attached and responds normally. ## Additional configuration ### Nested claims JWT claims can be nested at multiple levels. Use the slash (`/`) separator to identify the required claim value. In this example, we set up the rule to access the values of the `roles` claim, which is nested under `realm_access` Example JSON payload ```json { "realm_access": { "roles": ["reader", "admin"] } } ``` ```yaml authorization: require: Any rules: - require: All claims: - name: "realm_access/roles" values: - "reader" - "admin" ``` ### Claim match type By default, claim values are evaluated against their exact value. This can also be set to `Regex` allowing for a more complex and expressive matching configuration. This example allows an `email` claim that contains `@example.com`. This would match on `foo@example.com`, `user@example.com` etc... ```yaml authorization: require: Any rules: - require: Any claims: - name: email match: Regex values: - ".*@example\\.com" ``` ### Header forwarding By defining the `proxySetHeader` for a specific claim, you can forward the value of a matched claim as a request header to the upstream application. This example shows defining a header called `X-Aud` on the `aud` claim. ```yaml authorization: require: Any rules: - require: All claims: - name: "aud" proxySetHeader: X-Aud values: - "api" ``` --- ## Troubleshooting - Ensure NGINX Gateway Fabric is deployed with NGINX Plus. JWT claim validation is not supported in the open source version. - Ensure the AuthenticationFilter is accepted by checking its status with `kubectl describe`. - Ensure the HTTPRoute references the correct AuthenticationFilter name and group. - Confirm the Secret key is named `auth` and contains valid JWKS JSON. The Secret must be in the same namespace as the AuthenticationFilter. - Verify your JWT includes the `kid` (key ID) claim that matches one of the keys in your JWKS. - Check that the JWT is not expired by verifying the `exp` claim. - Ensure the JWT signature algorithm (typically RS256) matches the key type in your JWKS. - If claim validation rejects a token you expect to pass, decode the token at [jwt.io](https://jwt.io) and verify that the claim names and values exactly match what is configured in the AuthenticationFilter. ## Further reading - [AuthenticationFilter API reference](/ngf/reference/api.md#gateway.nginx.org/v1alpha1.AuthenticationFilter) - [Configure JWT authentication](/ngf/traffic-security/jwt-authentication.md) - [Configure OIDC authentication](/ngf/traffic-security/oidc-authentication.md) - [NGINX JWT Authentication Module](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) - [NGINX auth_jwt_require directive](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_require) - [RFC 7519 - JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)