More JWT examples
Review JWT policy configuration examples for other use cases.
For more information about JWTs, see the JWT overview and API docs.
If you import or export resources across workspaces, your policies might not apply. For more information, see Import and export policies.
Before you begin
This guide assumes that you use the same names for components like clusters, workspaces, and namespaces as in the getting started. If you have different names, make sure to update the sample configuration files in this guide.
- Set up Gloo Mesh Gateway in a single cluster.
- Install Bookinfo and other sample apps.
Configure an HTTP listener on your gateway and set up basic routing for the sample apps.
Clear route cache
The Envoy proxy in your gateway instance keeps a route cache in memory of precomputed routing decisions to help speed up performance. When the gateway proxy gets a request, it can check the route cache to decide where to send the request. When the route cache is cleared, Gloo recomputes the routing rules. This way, any old and potentially conflicting data from the initial request is cleared. Then, your traffic rules apply to the fresh route that might have new information, such as for claim-based routing with a JWT.
By default, the route cache is cleared in Gloo Gateway when the auth response is successful or if the JWT policy adds a claim in the claimsToHeader
field. To change this behavior, you can use the clearRouteCache
setting to explicitly set when to clear the route cache on a JWT policy. For more information about how route caching works with the JWT policy, see the API docs.
The following example shows how to use the clearRouteCache
setting to keep the route cache. This way, when a request is sent with a header that you want to extract from the JWT instead, the request is denied with a direct response. This approach can help prevent spoofing, saves route processing time by not proxying the request upstream, and provides an informative error message to the client.
Add another route for the httpbin app in the sample route table. This new route returns a direct response when the
X-email
header is in the request. The direct response tells the client not to include anX-email
header because you want to extract the email address from the JWT. The direct response route is configured before other httpbin routes so that it is matched first.kubectl apply -f- <<EOF apiVersion: networking.gloo.solo.io/v2 kind: RouteTable metadata: name: www-example-com namespace: bookinfo spec: hosts: - www.example.com # Selects the virtual gateway you previously created virtualGateways: - name: istio-ingressgateway namespace: bookinfo http: # Route for the main productpage app - name: productpage matchers: - uri: prefix: /productpage forwardTo: destinations: - ref: name: productpage namespace: bookinfo cluster: $CLUSTER_NAME port: number: 9080 # Routes all /reviews requests to the reviews-v1 or reviews-v2 apps - name: reviews labels: route: reviews matchers: - uri: prefix: /reviews forwardTo: destinations: - ref: name: reviews namespace: bookinfo cluster: $CLUSTER_NAME port: number: 9080 # Routes all /ratings requests to the ratings-v1 app - name: ratings-ingress labels: route: ratings matchers: - uri: prefix: /ratings forwardTo: destinations: - ref: name: ratings namespace: bookinfo cluster: $CLUSTER_NAME port: number: 9080 # Direct response route for httpbin requests with X-email header - name: httpbin-direct-response labels: route: httpbin matchers: - headers: - name: X-email directResponse: body: '{"message": "Based on your routing configuration, Gloo expects to get the X-email header from the JWT, not from the request. Remove the X-email header from the request and try again."}' status: 510 # Main route for requests to the httpbin app - name: httpbin-ingress labels: route: httpbin matchers: - headers: - name: X-httpbin forwardTo: destinations: - ref: name: httpbin namespace: httpbin cluster: $CLUSTER_NAME port: number: 8000 EOF
Send the following requests to test the httpbin routing behavior.
Create a JWT policy without the
clearRouteCache
setting. By default, the route cache is cleared if you add claims in theclaimsToHeaders
section.kubectl apply -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: JWTPolicy metadata: annotations: cluster.solo.io/cluster: "" name: jwt-policy namespace: httpbin spec: applyToRoutes: - route: labels: route: httpbin config: phase: preAuthz: {} providers: provider1: claimsToHeaders: - append: true claim: org header: x-org - append: true claim: email header: x-email issuer: https://localhost local: inline: |- -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO 39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/ NwIDAQAB -----END PUBLIC KEY----- tokenSource: headers: - name: X-Auth prefix: 'Bearer ' queryParams: - auth_token EOF
Repeat the request with the
X-email
header. This time, the request is denied, but not with the direct response that you configured in the routing rules. Instead, a401 Unauthorized
response is returned because you did not include the JWT in the header. The default behavior is to clear the routing cache when you add a claim in the JWT policy, as you did with thex-email
header in theclaimsToHeader
section.curl -vik http://www.example.com:80/status/200 -H "X-email: user2@solo.io" -H "X-httpbin: true" --resolve www.example.com:80:$INGRESS_GW_IP
Example response:
HTTP/1.1 401 Unauthorized ... Jwt is missing
Update the JWT policy to keep the route cache by setting
clearRouteCache
to false.kubectl apply -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: JWTPolicy metadata: annotations: cluster.solo.io/cluster: "" name: jwt-policy namespace: httpbin spec: applyToRoutes: - route: labels: route: httpbin config: phase: preAuthz: {} clearRouteCache: "FALSE" providers: provider1: claimsToHeaders: - append: true claim: org header: x-org - append: true claim: email header: x-email issuer: https://localhost local: inline: |- -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO 39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/ NwIDAQAB -----END PUBLIC KEY----- tokenSource: headers: - name: X-Auth prefix: 'Bearer ' queryParams: - auth_token EOF
Follow the steps in Basic JWT example to update the policy and set a JWT token for the
X-Auth
header.Repeat the request with the
X-email
header again. Now that you kept the route cache, the request is once again denied based on thedirectResponse
routing rules that you set up.curl -vik http://www.example.com:80/status/200 -H "X-Auth: ${TOKEN}" -H "X-email: user2@solo.io" -H "X-httpbin: true" --resolve www.example.com:80:$INGRESS_GW_IP
Example response:
HTTP/1.1 510 Not Extended ... {"message": "Based on your routing configuration, Gloo expects to get the X-email header from the JWT, not from the request. Remove the X-email header from the request and try again."}%
Nested claims
You can use nested claims to allow JWTs that have multiple child values for a parent claim. For example, you might have a user who is part of multiple groups, such as engineering and quality assurance. In the JWT from the identity provider (IdP), you might have a nested claim similar to the following:
{
"iss": "https://dev.example.com",
"exp": 4804324736,
"iat": 1648651136,
"org": "internal",
"email": "dev1@solo.io",
"scope": "is:developer",
"resource_access": {
"account": {
"groups": [
"engineering",
"qa"
]
}
}
}
You can create a JWT policy to allow access to users who are members of a particular nested claim’s group, such as engineering
. For quick testing, you can use sample keys and a dev-example
JWT. For more details about the sample JWT, see the GitHub readme.
Apply the JWT policy. In the policy, include the following configuration for the nested claims. The example gist already has this config set up for you.
spec: config: claims: - key: "resource_access.account.groups" nestedClaimDelimiter: "." values: - admin - engineering
kubectl apply -f https://gist.githubusercontent.com/artberger/9df42cba69f60b08fbb9a489c0b0ed65/raw/e53910f83d32e8dbc2bc22dc63c4092d3a4dd1ce/jwt-policy-nested.yaml
Send a request to the
httpbin
app without any authentication. Notice that your request is denied with a401
error.- HTTP:
curl -vik -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
- HTTPS:
curl -vik -H "X-httpbin: true" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/get
Example output:
HTTP/1.1 401 Unauthorized www-authenticate: Bearer realm="http://www.example.com/get" ... Jwt is missing
- HTTP:
Get a sample JWT that is preconfigured to meet the validation requirements that you set in the JWT policy for the
dev-example
provider, including the nested claim membership in theengineering
group.TOKEN=$(curl https://raw.githubusercontent.com/solo-io/gloo-mesh-use-cases/main/gloo-gateway/jwt/nested.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode
Example output:
{"iss":"https://dev.example.com","exp":4804324736,"iat":1648651136,"org":"internal","email":"dev1@solo.io","scope":"is:developer","resource_access":{"account":{"groups":["engineering","qa"]}}}
Try the request to the
httpbin
app again, this time with your JWT token. Notice that your request is now accepted!- HTTP:
curl -vik -H "X-Auth: Bearer ${TOKEN}" -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
- HTTPS:
curl -vik -H "X-Auth: Bearer ${TOKEN}" -H "X-httpbin: true" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/get
- HTTP:
Cleanup
You can optionally remove the resources that you set up as part of this guide.- Delete the JWT policy that you created.
kubectl delete jwtpolicy jwt-policy -n default
- Remove the direct response route from your route table.
kubectl apply -f- <<EOF apiVersion: networking.gloo.solo.io/v2 kind: RouteTable metadata: name: www-example-com namespace: bookinfo spec: hosts: - www.example.com # Selects the virtual gateway you previously created virtualGateways: - name: istio-ingressgateway namespace: bookinfo http: # Route for the main productpage app - name: productpage matchers: - uri: prefix: /productpage forwardTo: destinations: - ref: name: productpage namespace: bookinfo cluster: $CLUSTER_NAME port: number: 9080 # Routes all /reviews requests to the reviews-v1 or reviews-v2 apps - name: reviews labels: route: reviews matchers: - uri: prefix: /reviews forwardTo: destinations: - ref: name: reviews namespace: bookinfo cluster: $CLUSTER_NAME port: number: 9080 # Routes all /ratings requests to the ratings-v1 app - name: ratings-ingress labels: route: ratings matchers: - uri: prefix: /ratings forwardTo: destinations: - ref: name: ratings namespace: bookinfo cluster: $CLUSTER_NAME port: number: 9080 # Main route for requests to the httpbin app - name: httpbin-ingress labels: route: httpbin matchers: - headers: - name: X-httpbin forwardTo: destinations: - ref: name: httpbin namespace: httpbin cluster: $CLUSTER_NAME port: number: 8000 EOF
- Delete the JWT policy from the httpbin example.
kubectl delete jwtpolicy -n httpbin jwt-policy