Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article shows you how to use the Application Gateway for Containers AuthPolicy custom resource to require a valid bearer JSON Web Token (JWT) for an HTTPRoute.
Important
JWT validation and the AuthPolicy API are currently in preview.
See the Supplemental Terms of Use for Microsoft Azure Previews for legal terms that apply to Azure features that are in beta, preview, or otherwise not yet released into general availability.
For an overview of JWT validation, policy inheritance, and OAuth discovery, see JWT validation for Application Gateway for Containers.
Prerequisites
Before you begin, ensure you have:
If you're using the BYO deployment strategy, set up your Application Gateway for Containers resources and ALB Controller by using the Add-on or Helm.
If you're following the ALB managed deployment strategy, ensure provisioning of the ALB Controller (Add-on or Helm) the Application Gateway for Containers resources via the ApplicationLoadBalancer custom resource.
Deploy the sample HTTP application. Apply the following
deployment.yamlfile on your cluster to create a backend for testing JWT validation.kubectl apply -f https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/refs/heads/main/articles/application-gateway/for-containers/examples/traffic-split-scenario/deployment.yamlThis command creates the following resources on your cluster:
- a namespace called
test-infra - two services called
backend-v1andbackend-v2in thetest-infranamespace - two deployments called
backend-v1andbackend-v2in thetest-infranamespace
- a namespace called
A Microsoft Entra tenant in which you have permission to create app registrations and grant admin consent. This walkthrough uses Microsoft Entra ID to create a test API and client. You can instead use another OAuth 2.0 or OpenID Connect identity provider that issues JWT access tokens and exposes an HTTPS JWKS endpoint, however additional identity provider configurations are outside the scope of this article.
A Bash environment with
curlandjq.
Deploy the required Gateway API resources
Create a gateway:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-01
namespace: test-infra
annotations:
alb.networking.azure.io/alb-namespace: alb-test-infra
alb.networking.azure.io/alb-name: alb-test
spec:
gatewayClassName: azure-alb-external
listeners:
- name: http-listener
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
EOF
Note
When the ALB Controller creates the Application Gateway for Containers resources in Azure Resource Manager, it uses the following naming convention for a frontend resource: fe-<eight randomly generated characters>.
If you want to change the name of the frontend resource created in Azure, consider following the bring-your-own deployment strategy.
After you create the gateway resource, check that the status is valid, the listener is Programmed, and the gateway has an assigned address.
kubectl get gateway gateway-01 -n test-infra -o yaml
Example output of successful gateway creation.
status:
addresses:
- type: IPAddress
value: xxxx.yyyy.alb.azure.com
conditions:
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Valid Gateway
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
listeners:
- attachedRoutes: 0
conditions:
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: ""
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Listener is accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T21:04:55Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
name: https-listener
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
After you create the gateway, create an HTTPRoute that routes requests for api.contoso.com to backend-v1.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: auth-route
namespace: test-infra
spec:
parentRefs:
- name: gateway-01
hostnames:
- api.contoso.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: backend-v1
port: 8080
EOF
After you create the HTTPRoute resource, check that the route is Accepted and the Application Gateway for Containers resource is Programmed.
kubectl get httproute auth-route -n test-infra -o yaml
Verify that the status of the Application Gateway for Containers resource is successfully updated.
status:
parents:
- conditions:
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: ""
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: Route is Accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-06-19T22:18:23Z"
message: Application Gateway For Containers resource has been successfully updated.
observedGeneration: 1
reason: Programmed
status: "True"
type: Programmed
controllerName: alb.networking.azure.io/alb-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-01
namespace: test-infra
Before you attach an AuthPolicy, confirm that a request can reach the backend. Replace the hostname if needed:
GATEWAY_ADDRESS=$(kubectl get gateway gateway-01 \
-n test-infra \
-o jsonpath='{.status.addresses[0].value}')
curl --connect-to "api.contoso.com:443:${GATEWAY_ADDRESS}:443" \
https://api.contoso.com/
The --connect-to option preserves api.contoso.com as the TLS server name and HTTP host while connecting to the Gateway address. You can omit it after your DNS record points api.contoso.com to the Gateway.
Configure Microsoft Entra ID
This walkthrough uses the OAuth 2.0 client credentials flow to get a test token without an interactive user. Create separate app registrations for the protected API and the client that calls it.
Register the API
Sign in to the Microsoft Entra admin center.
Go to Entra ID > App registrations, and then select New registration.
Enter
AGC authentication demo APIfor the name.For Supported account types, select Accounts in this organizational directory only, don't enter a Redirect URI, and then select Register.
On the app registration's Overview page, record the Application (client) ID and Directory (tenant) ID. These values are the API application ID and tenant ID used later in this article.
Select Manifest, set
api.requestedAccessTokenVersionto2, and then save the manifest. This setting makes Microsoft Entra ID issue v2.0 access tokens whoseaudclaim is the API application ID.Select Expose an API, select Add next to Application ID URI, accept
api://<api-application-id>, and then select Save.Select App roles, and then select Create app role. Use the following values:
Setting Value Display name Access APIAllowed member types ApplicationsValue Api.AccessDescription Allows an application to access the JWT validation demo API.Enable this app role Selected
For more information, see Configure an application to expose a web API.
Register the client
- Return to App registrations, and then select New registration.
- Enter
AGC authentication demo clientfor the name. - For Supported account types, select Accounts in this organizational directory only, don't enter a Redirect URI, and then select Register.
- On the app registration's Overview page, record the Application (client) ID. This value is the client application ID used later in this article.
- Select API permissions > Add a permission > My APIs, and then select AGC authentication demo API.
- Select Application permissions, select Api.Access, and then select Add permissions.
- Select Grant admin consent for <tenant-name>, and then confirm the operation.
- Select Certificates & secrets > Client secrets > New client secret. Add a description and expiration, select Add, and immediately record the secret Value.
Caution
Use the client secret only for this test and delete it when you finish. For production applications, use a certificate or federated credential instead of a client secret.
Set the values that you recorded as environment variables:
TENANT_ID='<directory-tenant-id>'
API_APP_ID='<api-application-client-id>'
CLIENT_APP_ID='<client-application-client-id>'
CLIENT_SECRET='<client-secret-value>'
ISSUER="https://login.microsoftonline.com/${TENANT_ID}/v2.0"
AUDIENCE="${API_APP_ID}"
JWKS_URI="https://login.microsoftonline.com/${TENANT_ID}/discovery/v2.0/keys"
TOKEN_SCOPE="api://${API_APP_ID}/.default"
The AUDIENCE value is the API application's client ID because Microsoft Entra v2.0 access tokens use that GUID for the aud claim. The TOKEN_SCOPE value uses the API's Application ID URI with the /.default suffix to request the application permission granted to the client.
Create an AuthPolicy
Create an AuthPolicy that targets the route:
kubectl apply -f - <<EOF
apiVersion: alb.networking.azure.io/v1alpha1
kind: AuthPolicy
metadata:
name: auth-route-policy
namespace: test-infra
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: auth-route
jwt:
issuer: "${ISSUER}"
audiences:
- "${AUDIENCE}"
jwks:
remote:
uri: "${JWKS_URI}"
EOF
The AuthPolicy and target must be in the same namespace. A policy can target an entire ApplicationLoadBalancer, Gateway, HTTPRoute, GRPCRoute, or MCPRoute, but it can't target an individual Gateway listener or route rule.
Verify the policy status
Wait for the Accepted condition to become True:
kubectl wait authpolicy/auth-route-policy \
-n test-infra \
--for=condition=Accepted \
--timeout=5m
Inspect the full policy status:
kubectl get authpolicy auth-route-policy -n test-infra -o yaml
A valid policy has a condition similar to the following example:
status:
conditions:
- message: Valid AuthPolicy
reason: Accepted
status: "True"
type: Accepted
If Accepted is False, use the condition's reason and message to correct the target reference, issuer, or JWKS configuration. Requests to a target with an invalid attached policy are denied rather than forwarded without authentication.
Test authentication
Send a request without a token:
curl -i \
--connect-to "api.contoso.com:443:${GATEWAY_ADDRESS}:443" \
https://api.contoso.com/
The gateway returns:
HTTP/2 401
Request a Microsoft Entra access token by using the client credentials flow:
TOKEN_RESPONSE=$(curl --silent --show-error --fail \
-X POST "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=${CLIENT_APP_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode "scope=${TOKEN_SCOPE}" \
--data-urlencode "grant_type=client_credentials")
ACCESS_TOKEN=$(printf '%s' "${TOKEN_RESPONSE}" | jq -r '.access_token')
For more information about this token request, see Microsoft identity platform and the OAuth 2.0 client credentials flow.
Send the token in the authorization header:
curl -i \
--connect-to "api.contoso.com:443:${GATEWAY_ADDRESS}:443" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
https://api.contoso.com/
The request succeeds and is forwarded to the backend. A token with an invalid signature, expired lifetime, or incorrect issuer is rejected. A token whose audience isn't in spec.jwt.audiences is rejected with 403 Forbidden.
Apply authentication to a Gateway
To protect every route attached to a Gateway, change the target reference:
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-01
Every child route inherits this policy unless it has a route-level AuthPolicy. If the Gateway has multiple listeners, the policy applies to routes on all listeners because sectionNames isn't supported for AuthPolicy.
To protect all Gateways and routes associated with an ALB managed Application Gateway for Containers resource, target its ApplicationLoadBalancer:
spec:
targetRef:
group: alb.networking.azure.io
kind: ApplicationLoadBalancer
name: alb-test
Override an inherited policy
Attach a policy directly to a route to replace an inherited Gateway or ApplicationLoadBalancer policy. The route-level JWT configuration replaces the inherited configuration; the values aren't merged.
For example, the following policy requires a different audience for one route:
apiVersion: alb.networking.azure.io/v1alpha1
kind: AuthPolicy
metadata:
name: auth-route-override
namespace: test-infra
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: auth-route
jwt:
issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
audiences:
- api://<different-application-id>
jwks:
remote:
uri: https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
Only one AuthPolicy can target a resource. Delete the existing route-level policy before applying a replacement policy to the same route.
Make an inherited route public
To disable inherited JWT validation for a specific route, attach a route-level policy with an empty jwt block:
apiVersion: alb.networking.azure.io/v1alpha1
kind: AuthPolicy
metadata:
name: public-route
namespace: test-infra
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: auth-route
jwt: {}
Use jwt: {} only when the route is intentionally public. A partially configured JWT block is invalid and causes the affected route to fail closed.
Publish OAuth protected resource metadata
To help OAuth-aware clients discover how to obtain a token, add oauth.protectedResourceMetadata to the policy:
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: auth-route
jwt:
issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
audiences:
- <api-application-client-id>
jwks:
remote:
uri: https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
oauth:
protectedResourceMetadata:
resource: https://api.contoso.com/
authorizationServers:
- https://login.microsoftonline.com/<tenant-id>/v2.0
scopesSupported:
- api.read
- api.write
Retrieve the generated metadata document without a token:
curl -i \
--connect-to "api.contoso.com:443:${GATEWAY_ADDRESS}:443" \
https://api.contoso.com/.well-known/oauth-protected-resource
The gateway returns a document similar to the following example:
{
"resource": "https://api.contoso.com/",
"authorization_servers": [
"https://login.microsoftonline.com/<tenant-id>/v2.0"
],
"scopes_supported": [
"api.read",
"api.write"
],
"bearer_methods_supported": [
"header"
]
}
Unauthenticated responses also include a WWW-Authenticate header that points to this well-known endpoint. The gateway publishes the configured scopes but doesn't enforce them; your backend remains responsible for authorization.
Congratulations, you configured Application Gateway for Containers to validate JWT access tokens for an HTTPRoute by using an AuthPolicy.
Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
AuthPolicy remains Accepted=False |
The target doesn't exist, is in another namespace, or uses an unsupported kind | Correct spec.targetRef and keep the policy in the target's namespace |
Accepted=False mentions jwt |
The JWT block has an issuer without JWKS, or JWKS without an issuer | Configure both issuer and jwks, or use jwt: {} for an intentional opt-out |
Accepted=False mentions the JWKS URI |
The URI isn't an absolute HTTPS URL with a valid host and port | Correct spec.jwt.jwks.remote.uri |
Requests return 401 with otherwise valid tokens |
The token is expired, the issuer doesn't match exactly, the signature key isn't in the JWKS document, or the gateway can't retrieve the JWKS document | Inspect token claims and confirm JWKS DNS, network connectivity, TLS trust, and key rotation |
Requests return 403 |
The token audience isn't allowed | Add the intended audience to spec.jwt.audiences or request a token for the configured audience |
| A second policy is rejected as conflicted | More than one AuthPolicy targets the same resource |
Keep one policy per target and use hierarchy-based inheritance |
| Browser requests fail before authentication | A cross-origin request that sends Authorization isn't allowed by CORS |
Configure CORS to allow the client origin and the Authorization header |
Clean up
Delete the policy:
kubectl delete authpolicy auth-route-policy -n test-infra
When you delete a route-level policy, you remove its authentication requirement unless the route inherits another policy from its Gateway or ApplicationLoadBalancer.
If you created the example route, delete it:
kubectl delete httproute auth-route -n test-infra
If you created the Microsoft Entra app registrations for this walkthrough, delete them:
az ad app delete --id "${CLIENT_APP_ID}"
az ad app delete --id "${API_APP_ID}"