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, and that your Kubernetes context is set to the cluster you store your Gloo config in (typically the management cluster). If you have different names, make sure to update the sample configuration files in this guide.

Follow the getting started instructions to:

  1. Set up Gloo Gateway in a single cluster.
  2. Deploy sample apps.
  3. 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.

  1. 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 an X-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
       

  2. Send the following requests to test the httpbin routing behavior.

    When you include only the X-httpbin header, the request succeeds based on the forwardTo routing rules that you set up.

    curl -vik http://www.example.com:80/status/200 -H "X-httpbin: true" --resolve www.example.com:80:$INGRESS_GW_IP  
    

    Example response:

    HTTP/1.1 200 OK
    

    When you include the X-email header, the request is denied based on the directResponse routing rules that you set up.

    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 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."}%
    

  3. Create a JWT policy without the clearRouteCache setting. By default, the route cache is cleared if you add claims in the claimsToHeaders 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
    
  4. 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, a 401 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 the x-email header in the claimsToHeader 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
    
  5. 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
       

  6. Follow the steps in Basic JWT example to update the policy and set a JWT token for the X-Auth header.

  7. Repeat the request with the X-email header again. Now that you kept the route cache, the request is once again denied based on the directResponse 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."}%
    
  8. Optional: Clean up the changes that you made.

    1. Delete the JWT policy that you created.
      kubectl delete jwtpolicy jwt-policy -n default
      
    2. 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
            

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.

  1. 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
    
  2. Send a request to the httpbin app without any authentication. Notice that your request is denied with a 401 error.

    curl -vik -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
    
    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
    
  3. 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 the engineering 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"]}}} 
    
  4. Try the request to the httpbin app again, this time with your JWT token. Notice that your request is now accepted!

    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
    
    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
    
  5. Optional: Clean up the resources that you created.

    kubectl delete jwtpolicy -n httpbin jwt-policy