For more information about JWTs, see the JWT overview and API docs.

Before you begin

  1. Set up Gloo Mesh Gateway in a single cluster.
  2. Install Bookinfo and other 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.

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

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.

    • 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
      
  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!

    • 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
        

Cleanup

You can optionally remove the resources that you set up as part of this guide.
  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
      
  3. Delete the JWT policy from the httpbin example.
      kubectl delete jwtpolicy -n httpbin jwt-policy