Multiple JWT policies

Enforce gateway- or route-based JWT authentication for different routes by using multiple policies. For example, you might have use cases similar to the following:

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

You cannot apply multiple JWT policies to the same route in a route table. If you want to configure multiple providers for the same route, see Multiple JWT providers.

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.

Configure JWT policies

Create a JWT policy for each route that requires JWT authentication. Note that you cannot apply multiple JWT policies to the same route in a route table. For more information, see Applying policies.

The following example is for a basic JWT policy that uses a local JWT issuer and inline public key. The policy does the following:

  • Enables JWT authentication for the selected route.
  • Configures the https://dev.example.com issuer.
  • Adds the value of an org claim from the JWT payload to an X-Org header in the request, if present.
  • Adds the value of an email claim from the JWT payload to an X-Email header in the request, if present.
  • Checks requests by using the token found in the X-Auth header with the prefix Bearer <token>, or in a query parameter auth_token=<token>. Note that if a request has both the header and query parameter, both tokens must be valid for Gloo Gateway to accept the request.
apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
metadata:
  annotations:
    cluster.solo.io/cluster: ""
  name: jwt-policy-httpbin
  namespace: default
spec:
  applyToRoutes:
  - route:
      labels:
        route: httpbin
  config:
    phase:
      preAuthz: {}
    providers:
      dev-provider:
        claimsToHeaders:
        - append: true
          claim: org
          header: x-org
        - append: true
          claim: email
          header: x-email
        issuer: https://dev.example.com
        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

The following example is for a basic JWT policy that uses a local JWT issuer and inline public key. The policy does the following:

  • Enables JWT authentication for the selected route.
  • Configures the https://docs.example.com issuer.
  • Adds the value of an org claim from the JWT payload to an X-Org header in the request, if present.
  • Adds the value of an email claim from the JWT payload to an X-Email header in the request, if present.
  • Adds the value of a scope claim from the JWT payload to an X-Scope header in the request, if present.
  • Checks requests by using the token found in the X-Auth header with the prefix Bearer <token>, or in a query parameter auth_token=<token>. Note that if a request has both the header and query parameter, both tokens must be valid for Gloo Gateway to accept the request.
apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
metadata:
  annotations:
    cluster.solo.io/cluster: ""
  name: jwt-policy-ratings
  namespace: default
spec:
  applyToRoutes:
  - route:
      labels:
        route: ratings
  config:
    phase:
      preAuthz: {}
    providers:
      docs-provider:
        claimsToHeaders:
        - append: true
          claim: org
          header: x-org
        - append: true
          claim: email
          header: x-email
        - append: true
          claim: scope
          header: x-scope
        issuer: https://docs.example.com
        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

Review the following table to understand this configuration. For more information, see the API docs.

Setting Description
applyToRoutes Use labels to configure which routes to apply the policy to. This example label matches the app and route from the example route table that you previously applied. If omitted, the policy applies to all routes in the workspace.
phase Set when to apply the JWT filter in the request chain, either before (preAuthz) or after (postAuthz) authorization to have access to the JWT token. You can also set the priority if you have multiple policies in the same phase. The lowest numbered priority is run first. For more information, see Phase considerations. This example sets no priority, so the default value of zero is used.
providers Enter a name for the provider to help you map the provider when viewing logs to debug. The provider name does not affect the policy's behavior and cannot be used by other resources to select the policy.
claimsToHeaders Optionally set the claims from the JWT payload that you want to extract and add as headers to the request before the request is forwarded to the upstream destination. This example extracts the same two org and email claims and adds them as headers for both providers. The docs-provider also has an additional scope claim.
claimsToHeaders.append Enter a boolean value to add a claim's value if the header exists in the request. Use true to append the claim's value to the header, and false to overwrite any existing value in the header.
claimsToHeaders.claim Enter the name of the claim in the JWT payload to get the value for the header.
claimsToHeaders.header Enter the request header that the value of the claim is copied to.
issuer Optionally, set the JWT issuer, usually as a subdomain of a URL or email address. If set, the iss field in the JWT token must match this field, or else the request is denied. If unset, the iss field in the JWT token is not checked. In this example, the issuer is set to a unique URL per provider, dev.example.com or docs.example.com.
keepToken This value is set to true so that the JWT is kept in the request after verification. This way, other policies can use the JWT information as needed.
local Provide the PEM-formatted public key to verify the JWT token. In this example, the public key is written inline to the policy for testing purposes. For production scenarios, you can set a remote reference to your JSON Web Key Set (JWKS) server instead of this local setting.

Verify multiple JWT policies

  1. Create a private-public key pair by using OpenSSL. You use this pair to sign your own JWT tokens later.

    1. Create a private key to sign the JWT.

      openssl genrsa 2048 > private-key.pem
      
    2. Extract the public key from the private key.

      openssl rsa -in private-key.pem -pubout
      
  2. Create a JWT policy for the httpbin app. In the inline section, copy in the public key value from the output of the previous step, including the BEGIN/END parts. Make sure the indentation is kept.

    kubectl apply -f- <<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: JWTPolicy
    metadata:
      annotations:
        cluster.solo.io/cluster: ""
      name: jwt-policy-httpbin
      namespace: default
    spec:
      applyToRoutes:
      - route:
          labels:
            route: httpbin
      config:
        phase:
          preAuthz: {}
        providers:
          dev-provider:
            claimsToHeaders:
            - append: true
              claim: org
              header: x-org
            - append: true
              claim: email
              header: x-email
            issuer: https://dev.example.com
            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
    
  3. Create another JWT policy for the ratings app and enter the same public key in the inline section.

    kubectl apply -f- <<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: JWTPolicy
    metadata:
      annotations:
        cluster.solo.io/cluster: ""
      name: jwt-policy-ratings
      namespace: bookinfo
    spec:
      applyToRoutes:
      - route:
          labels:
            route: ratings
      config:
        phase:
          preAuthz: {}
        providers:
          docs-provider:
            claimsToHeaders:
            - append: true
              claim: org
              header: x-org
            - append: true
              claim: email
              header: x-email
            - append: true
              claim: scope
              header: x-scope
            issuer: https://docs.example.com
            local:
              inline: |-
                -----BEGIN RSA PUBLIC KEY-----
                MIIBCgKCAQEAyTnCHqv2VYNyDUkeFXM6guSjDBH6op1WTAgMIY57vbjrXxbgi63g
                MdxNww5kf0xeIQYg3maNfghY66x9HQrbocs/UEv/mk76mHURztIywJK+tTTx0jN9
                8TH8O2s2xHKbqL/rIM1gEtvxZtV5DOD+bTHF/UONDxlE/w3xwTZOmCDquTKauEWk
                Xcb2/Bt1jGK9GMOnopynQcXnomy9bsESrUA1qlBi0CYXuMPxxn8iVh4a4cDwVrl/
                ztR0Lce4RAWJO5q27ZB0OKCBdQTtCtRFiIv8tSHN8wTf987ykotMIwSaExLvTZ1b
                lZ7q8UmMODESVaV1SBWvAvbF99CgaT/D9QIDAQAB
                -----END RSA PUBLIC KEY-----
    EOF
    
  4. Create a JWT for your first provider, dev-provider, that uses your private key. Refer to following image to help understand the UI location of the steps. JWT token

    1. In your browser, go to https://jwt.io/. For the subsequent steps, you can refer to the following image.
    2. From the Algorithm dropdown, select RS256.
    3. From the Decoded section, scroll to the Payload: Data pane. Replace the content by pasting the following JWT payload information.
      {
        "iss": "https://dev.example.com",
        "exp": 4804324736,
        "iat": 1648651136,
        "org":"solo-io",
        "email": "user2@solo.io"
      }
      
    4. From the Decoded section, scroll to the Verify Signature pane. In the first input box for -----BEGIN PUBLIC KEY-----, replace the content by pasting the value of your public key from the previous output of your terminal.
    5. In your terminal, copy the contents of your private key.
      cat private-key.pem | pbcopy
      
    6. Return to your browser and go to the Verify Signature pane in the Decoded section. In the second input box for -----BEGIN PRIVATE KEY-----, replace the content by pasting the value of your private key.
    7. Optional: Click Share JWT and paste the JWT values into a file to refer to later.
    8. From the Encoded section, copy the token value to your clipboard, such as eyJhbG.....
    9. Add the token value to an environment variable on your local machine.
      export TOKEN_DEV="Bearer <token_value>"
      
  5. Repeat the previous step to create another JWT for your second provider, docs-provider.

    1. For the Payload: Data section, use the following details. Note that the issuer, org, and email details differ from the dev-provider. Also, this payload has a scope field for the additional header.
      {
        "iss": "https://docs.example.com",
        "exp": 4804324736,
        "iat": 1648651136,
        "org":"end-user",
        "email": "user1@example.com",
        "scope": "reader"
      }
      
    2. After filling out the rest of the details and getting the encoded token, set the token as a different environment variable.
      export TOKEN_DOCS="Bearer <token_value>"
      
  6. Try the 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
    
  7. Try the request to the httpbin app again, this time with your dev-provider token. Notice that your request is now accepted!

    curl -vik -H "X-Auth: ${TOKEN_DEV}" -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
    
    curl -vik -H "X-Auth: ${TOKEN_DEV}" -H "X-httpbin: true" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/get
    
    In the example output you get back a 200 response. You also can see the X-Email and X-Org headers that you appended in the claimsToHeaders section of the policy.

       HTTP/1.1 200 OK 
       ...
       {
         "args": {}, 
         "headers": {
           "Accept": "*/*", 
           "Host": "www.example.com", 
           "X-Email": "user2@solo.io", 
           "X-Envoy-Attempt-Count": "1", 
           "X-Envoy-Decorator-Operation": "httpbin.default.svc.cluster.local:8000/*", 
           "X-Envoy-Internal": "true", 
           "X-Httpbin": "true", 
           "X-Org": "solo-io"
         },
       
  8. Try the request to the ratings app without authentication. Notice that your request is denied with a 401 error.

    curl -vik --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/ratings/1
    
    curl -vik --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/ratings/1
    
    Example output:

    HTTP/1.1 401 Unauthorized
    www-authenticate: Bearer realm="http://www.example.com/get"
    ...
    Jwt is missing
    
  9. Try the request to the ratings app again, this time with your docs-provider token. Notice that your request is now accepted!

    curl -vik -H "X-Auth: ${TOKEN_DOCS}" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/ratings/1
    
    curl -vik -H "X-Auth: ${TOKEN_DOCS}" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/ratings/1
    
    In the example output you get back a 200 response.

       HTTP/1.1 200 OK 
       ...
       * Connection #0 to host www.example.com left intact
       {"id":1,"ratings":{"Reviewer1":5,"Reviewer2":4}}%   
       
  10. Optional: Clean up the resources that you created.

    kubectl -n bookinfo delete jwtpolicy jwt-policy-ratings
    kubectl -n bookinfo delete jwtpolicy jwt-policy-httpbin