For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Access token validation
Validate an access token that an API client already holds, and reject requests without a valid one.
Protect a route by validating an access token that the client already holds. Kgateway checks the token signature against Okta’s signing keys. It rejects requests without a valid token instead of redirecting them to a login page. Use this flow for API clients that cannot follow browser redirects.
Before you begin
Complete the Okta setup page. This flow needs the Okta application and test user, the access policy and rule on the default authorization server, and the Backend and BackendConfigPolicy created during setup.
This flow does not need the client secret in a Kubernetes Secret. The gateway does not exchange an authorization code. The flow also works over HTTP because it does not use cookies.
Note
This guide validates tokens issued by the custom authorization server named default, at /oauth2/default. Do not validate tokens from the Org authorization server. Okta says that those tokens “aren’t intended for validation or use by your own apps or resource servers.” Their contents are also “subject to change at any time without notice.” If your tokens do not carry the expected iss and aud claims, confirm that your application requests them from the default authorization server.
Configure access token validation
Create a GatewayExtension to configure token validation. Then create a TrafficPolicy to enforce validation on a route.
-
Create a GatewayExtension for JWT validation. The
issuermust match the token’sissclaim from Okta. Theaudienceslist must include the token’saudclaim.kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: GatewayExtension metadata: name: okta-jwt namespace: kgateway-system spec: jwt: validationMode: Strict providers: - name: okta issuer: https://YOUR_OKTA_DOMAIN/oauth2/default jwks: remote: backendRef: group: gateway.kgateway.dev kind: Backend name: okta namespace: kgateway-system url: https://YOUR_OKTA_DOMAIN/oauth2/default/v1/keys audiences: - api://default EOFField Description nameA required, unique name for the provider. The resource is rejected without it. issuerMust match the issclaim in your tokens exactly. For the default authorization server, Okta useshttps://YOUR_OKTA_DOMAIN/oauth2/default. Decode a real token and check itsissclaim.jwks.remote.backendRefThe Backendthat the gateway uses to fetch the signing keys. The JWKS endpoint does not need to be reachable from outside the cluster.jwks.remote.urlThe JWKS URL. For the default authorization server, use /oauth2/default/v1/keys. Kgateway connects throughbackendRefand uses this URL for the request path andHostheader.audiencesAccepted values for the audclaim. The gateway rejects a token if none of its audiences match. The default authorization server usesapi://default. You can use the Identifier from your own authorization server instead. Decode a token and check itsaudclaim at jwt.io.Note
The
validationModefield is optional and defaults toStrict. This mode requires a valid JWT on every request. The guide sets it explicitly to make the intent clear. Set it toAllowMissingonly if you want requests without a token to pass through. Pair that mode with an authorization policy that restricts those requests. -
Create a TrafficPolicy that references the JWT GatewayExtension. Make sure that the TrafficPolicy is in the same namespace as the HTTPRoute that it targets.
kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: TrafficPolicy metadata: name: okta-jwt-policy namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin jwtAuth: extensionRef: name: okta-jwt namespace: kgateway-system EOF -
Confirm that the TrafficPolicy is attached.
kubectl get TrafficPolicy okta-jwt-policy -n httpbin -o yamlIn the
status.ancestorssection, confirm that both theAcceptedandAttachedconditions areTrue. An empty status means that the policy did not attach to anything.- message: Policy accepted reason: Valid status: "True" type: Accepted - message: Attached to all targets reason: Attached status: "True" type: AttachedImportant
If the authorization code flow also targets this route, the OAuth2 policy runs first. API requests without a session cookie are redirected to Okta before JWT validation runs. To test JWT validation alone, temporarily remove the OAuth2 policy or target a different route with the JWT policy.
Verify
Verify the access token validation flow with these steps.
-
Get the JWKS URI from Okta:
- The JWKS endpoint for the default authorization server is
https://YOUR_OKTA_DOMAIN/oauth2/default/v1/keys. - Confirm that the value matches the
jwks.remote.urlfield that you set on theGatewayExtension.
- The JWKS endpoint for the default authorization server is
-
Verify that a request without a token is rejected.
curl -vi "http://$INGRESS_GW_ADDRESS:8080/headers" -H "host: www.example.com"Example output:
< HTTP/1.1 401 UnauthorizedIf you get
200 OK, the JWT policy is not attached to the route. Check the policy attachment step and confirm that the HTTPRoute name matches. -
Obtain an access token from the
defaultauthorization server.The JWT policy validates the signature, issuer, and audience of the token you present. It does not depend on which grant produced the token. Use an option supported by your Okta org, and request the token from the same Okta address that you set as the
issueron theGatewayExtension.- Authorization code flow: Complete the authorization code flow in a browser. Then copy the value of the
AccessTokencookie set by the gateway.
export TOKEN=<access-token-cookie-value>- Client credentials grant: Request the token from
/oauth2/default/v1/token. This grant has no user context, so Okta does not accept reserved OIDC scopes such asopenid,email, andprofile. Add a custom scope to thedefaultauthorization server, then request that scope.
export TOKEN=$(curl -s -X POST "https://YOUR_OKTA_DOMAIN/oauth2/default/v1/token" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "grant_type=client_credentials" \ -d "scope=YOUR_CUSTOM_SCOPE" \ | jq -r .access_token)Note
The client credentials grant requires API Access Management. Not every Okta org has this feature. If yours does not, use the authorization code flow to obtain a token.
- Authorization code flow: Complete the authorization code flow in a browser. Then copy the value of the
-
Confirm that the token’s
issandaudclaims match yourGatewayExtension. Decode the payload.echo $TOKEN | jq -rR 'split(".")[1] | @base64d' | jq '{iss, aud}'Example output. If
auddoes not include the expected audience, update theaudienceslist in yourGatewayExtension.{ "iss": "https://YOUR_OKTA_DOMAIN/oauth2/default", "aud": ["api://default"] } -
Send a request with the token in the
Authorizationheader.curl -vi "http://$INGRESS_GW_ADDRESS:8080/headers" \ -H "host: www.example.com" \ -H "Authorization: Bearer $TOKEN"A successful response shows the headers from the httpbin app.
< HTTP/1.1 200 OKIf the request is rejected, check the response body for the reason:
Message Meaning Jwt is missingNo Authorization: Bearerheader was sent.Jwt is not in the form of Header.Payload.Signature with two dots and 3 sectionsThe Authorizationheader value is not a JWT. Check that the token came from the/oauth2/defaultauthorization server. Also check that it was not truncated when copied.Jwt verification failsThe signature does not match a JWKS key, or the issuer or audience does not match the JWT policy.
Cleanup
You can remove the resources that you created in this guide.kubectl delete TrafficPolicy okta-jwt-policy -n httpbin
kubectl delete GatewayExtension okta-jwt -n kgateway-systemTo remove Okta and the shared resources, see the Cleanup section of the Okta setup page.