Use JWT along with external auth and transformation policies together to secure access to your routes. The following example walks you through setting up the following resources:

  • An external auth policy that secures access to the httpbin route by requiring a basic Authorization header with a user password in requests.
  • A JWT policy that secures access to the httpbin route by requiring a JWT in an X-Auth header in requests. The policy also extracts claims from the JWT and adds the claims as headers in requests.
  • An external service to represent a remote JWKS server to get the public keys to validate the JWT.
  • A transformation policy that extracts information from a header, transforms the information with a regular expression, and adds the information as a new request header. This example shows how you can use information extracted from the JWT claim in subsequent steps for more fine-grained traffic control.

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.
  4. Make sure that you have the following CLI tools, or something comparable:

    • htpasswd to generate hashed, salted passwords.
    • base64 to encode strings.
  5. Make sure that the external auth service is installed and running. If not, install the external auth service.

      kubectl get pods -A -l app=ext-auth-service
      
  6. Get the external address of your ingress gateway. The steps vary depending on the type of load balancer that backs the ingress gateway.

     ```shell
     export INGRESS_GW_ADDRESS=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
     echo $INGRESS_GW_ADDRESS
     ```
    

    Note: Depending on your environment, you might see <pending> instead of an external IP address. For example, if you are testing locally in kind or minikube, or if you have insufficient permissions in your cloud platform, you can instead port-forward the service port of the ingress gateway:

      kubectl -n gloo-mesh-gateways port-forward deploy/istio-ingressgateway-1-22 8081
      

Step 1: Create an external auth policy

Create a basic external auth policy to secure access to the httpbin route. For more information about other types of external auth, see External authentication and authorization.

  1. Generate a salt and hashed password for your user credentials. The following example uses the htpasswd tool for a user named user.

      htpasswd -nbm user password
      

    Example output:

      user:$apr1$TYiryv0/$8BvzLUO9IfGPGGsPnAgSu1
      
  2. Retrieve the salt and hashed password from the output of the previous step.

    • Salt: TYiryv0/
    • Hashed password: 8BvzLUO9IfGPGGsPnAgSu1
  3. Create a basic external auth policy that uses the salt and hashed password from the previous step.

      kubectl apply -f - <<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: ExtAuthPolicy
    metadata:
      name: httpbin-ext-auth
      namespace: httpbin
    spec:
      applyToRoutes:
      - route:
          labels:
            route: httpbin
      config:
        glooAuth:
          booleanExpr: jwt || basic
          configs:
          - name: basic
            basicAuth:
              apr:
                users:
                  user:
                    hashedPassword: 8BvzLUO9IfGPGGsPnAgSu1
                    salt: TYiryv0/
          - jwt: {}
            name: jwt
        server:
          name: httpbin-ext-auth-server
          namespace: gloo-mesh
    EOF
      

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

    SettingDescription
    applyToRoutesUse 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 apply separately. If omitted and you do not have another selector such as applyToDestinations, the policy applies to all routes in the workspace.
    booleanExprSet the order for processing auth configs when multiple authentication methods are used. In this example, requests that have a JWT are processed first. The basic auth that this external auth policy configures is only processed if a JWT is not included in the request. This setting saves extra processing time so that clients do not need to include both auth methods in their requests.
    name: basicSet the name for the basic auth configuration, such as basic. This name must match the name that you set in the booleanExpr.
    basicAuthConfigure the basic auth credentials to use to authenticate requests. The example sets up user credentials for a user named user in the required APR1 format. For more information, see the API reference.
    hashedPasswordThe hashed password that you generated in the previous step. The example sets 8BvzLUO9IfGPGGsPnAgSu1.
    saltThe salt, or random data that hashes the password, that you generated in the previous step. The example sets TYiryv0/.
    jwtAdd a dummy external auth service to support multiple authentication methods with JWT.
    serverThe ExtAuthServer resource that represents the server for the policy to use, which you create in the next step.
  4. Create an external auth server to use for your policy.

      kubectl apply -f - <<EOF
    apiVersion: admin.gloo.solo.io/v2
    kind: ExtAuthServer
    metadata:
      name: httpbin-ext-auth-server
      namespace: gloo-mesh
    spec:
      destinationServer:
        port:
          number: 8083
        ref:
          cluster: $CLUSTER_NAME
          name: ext-auth-service
          namespace: gloo-mesh
    EOF
      
  5. Send an unauthenticated request to the httpbin app.

    Example output: Notice that the request is denied with a 401 Unauthorized response.

      HTTP/2 401
      
  6. Encode the expected user credentials from the external auth policy in base64 format.

      echo -n "user:password" | base64
      

    Example output:

      dXNlcjpwYXNzd29yZA==
      
  7. Repeat the request to the httpbin app, including the authorization header with the user credentials. This time, the request succeeds.

    Example output:

      HTTP/2 200
      

Good job! You just secured basic access to the httpbin app. Now, you can add JWT verification with a JWT policy.

Step 2: Create a JWT policy

Create a basic JWT policy with a remote JWKS server to add claims from the JWT as headers to requests to the httpbin route. For more information about JSON web tokens, see JWT.

  1. Get the following details for your remote JWKS server from your OIDC provider, such as Keycloak or Okta. Keep in mind that this server must be accessible from your cluster.

    • Verify that the claims you want to pass along in the request header are included in the JWT that the OIDC provider generates, such as org and email claims.
    • Get the Issuer URL for your OAuth2 server instance. The following examples show what the URLs might look like from common providers.
      • Okta default server: https://dev-1234567.okta.com/oauth2/default
      • Deployed Keycloak instance in AWS with a solo realm: http://a1aa111aaaa1a-123456789.us-west-1.elb.amazonaws.com:8080/auth/realms/solo
    • Get the remote JWKS URL to fetch the public keys. The following examples are from common providers.
      • Okta default server: https://dev-1234567.okta.com/oauth2/default/v1/keys
      • Deployed Keycloak instance in AWS with a solo realm: http://a1aa111aaaa1a-123456789.us-west-1.elb.amazonaws.com:8080/auth/realms/solo/protocol/openid-connect/certs
  2. Create an external service to represent the remote JWKS server. The following example uses an okta host and assumes that you set up Okta and your Gloo virtual gateway for the httpbin app to listen for HTTPS traffic.

      kubectl apply -f - <<EOF
    apiVersion: networking.gloo.solo.io/v2
    kind: ExternalService
    metadata:
      name: httpbin-jwks
      namespace: httpbin
      labels:
        expose: "true"
        host: okta
    spec:
      hosts:
      - dev-1234567.okta.com
      ports:
      - name: https
        number: 443
        protocol: HTTPS
        clientsideTls: {}
    EOF
      
  3. Create a basic JWT policy with the remote JWKS issuer details that you previously retrieved.

      kubectl apply -f - <<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: JWTPolicy
    metadata:
      annotations:
        cluster.solo.io/cluster: ""
      name: httpbin-jwt
      namespace: httpbin
    spec:
      applyToRoutes:
      - route:
          labels:
            route: httpbin
      config:
        phase:
          preAuthz: {}
        validationPolicy: ALLOW_MISSING
        providers:
          okta:
            keepToken: true
            claimsToHeaders:
            - append: true
              claim: org
              header: x-org
            - append: true
              claim: email
              header: x-email
            issuer: https://dev-1234567.okta.com/oauth2/default
            remote:
              url: https://dev-1234567.okta.com/oauth2/default/v1/keys
              destinationRef:
                kind: EXTERNAL_SERVICE
                port:
                  number: 443
                ref:
                  name: httpbin-jwks
            tokenSource:                    
              headers:  
              - name: X-Auth
                prefix: 'Bearer '
              queryParams:       
              - auth_token
    EOF
      

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

    SettingDescription
    applyToRoutesUse 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 apply separately. If omitted and you do not have another selector such as applyToDestinations, the policy applies to all routes in the workspace. Note that this policy applies to the same httpbin route that the external auth policy applies to.
    validationPolicySet to ALLOW_MISSING to allow requests to succeed even if JWT authentication is missing, but fail when an invalid JWT token is presented. This way, clients do not have to pass a JWT in order to access the httpbin app. However, when clients do pass a JWT, the JWT is validated so that subsequent steps, like a transformation policy, use validated information.
    providersConfigure the JWT providers for the policy with the information that you previously retrieved. This example sets up an okta provider.
    claimsToHeadersSet 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 org and email claims.
    issuerSet the JWT issuer that you previously retrieved.
    remote.urlConfigure the reference to the remote JWKS server with the details that you previously retrieved.
    remote.destinationRefAdd the details of the external service that you created to refer to the remote JWKS server.
    tokenSourceSpecify where Gloo Gateway looks for the token to use for JWT authentication. If unset, the default locations are tried in the following order:
    1. The authorization header X-Auth with the value Bearer <token>.
    2. The query parameter for access_token, such as https://<path>?access_token=<token>.
    You can set up multiple JWT tokens to verify per request. If a request has multiple header and query parameters, all tokens must be valid for Gloo Gateway to accept the request.
  4. Create a transformation policy that uses the information in the JWT email claim to create a new header called user.

      kubectl apply -f - <<EOF
    apiVersion: trafficcontrol.policy.gloo.solo.io/v2
    kind: TransformationPolicy
    metadata:
      name: httpbin-transform-user
      namespace: httpbin
    spec:
      applyToRoutes:
      - route:
          labels:
            route: httpbin
      config:
        phase:
          postAuthz: {}
        request:
          injaTemplate:
            extractors:
              user:
                header: 'X-Email'
                regex: '(.*)@.*$'
                subgroup: 1
            headers:
              x-user:
                text: "{{ user }}"
    EOF
      

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

    SettingDescription
    applyToRoutesUse 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 apply separately. If omitted and you do not have another selector such as applyToDestinations, the policy applies to all routes in the workspace. Note that this policy applies to the same httpbin route that the JWT policy applies to.
    phaseSet the phase to postAuthz so that the transformation policy can use the information previously extracted from the JWT claim during the auth phase.
    requestConfigure an inja template to extract information from the existing headers, transform the information with a regular expression, and add a new request header. In the example, the policy extracts the X-Email header, grabs the information before the @ sign in the email address, and adds this information to a new X-User request header.

Step 3: Verify secured access to httpbin

To verify access with a JWT, you need an access token from your OIDC provider to include in the request.

  1. Get an access token from your OIDC provider. The steps vary by provider, and you might need several details from your OIDC provider, such as the client ID, client secret, and token endpoint. For more information, consult your OIDC provider, such as the following guides that use the Postman and OIDC Debugger tools to get an access token.

  2. Send an authenticated request to the httpbin app without the JWT. Notice that the request succeeds because you set the JWT validation to be optional (validationPolicy: ALLOW_MISSING).

  3. Send a request to the httpbin app again, this time with a fake JWT. The request fails because the JWT is not valid.

    Example output: Notice that the error message describes how the JWT is not formatted correctly.

      HTTP/2 401
    www-authenticate: Bearer realm="https://example.com:443/get", error="invalid_token"
    ...
    Jwt is not in the form of Header.Payload.Signature with two dots and 3 sections
      
  4. Repeat the request to the httpbin app, including the access token that you previously retrieved in the "X-Auth: Bearer <access-token>" header. Don’t include the X-Authorization header with basic auth, to avoid an unnecessary hop to the external auth service. This time, the JWT is validated and the request succeeds.

    Example output: Notice that the request includes the org and email claims as headers to the httpbin app. Also, the transformation policy extracted the email from the JWT claim and added an X-User header.

    HTTP/2 200
    ...
    {
    "args": {}, 
    "headers": {
      "Accept": "*/*", 
      "Authorization": "basic dXNlcjpwYXNzd29yZA==", 
      "Host": "example.com", 
      "User-Agent": "curl/7.87.0", 
      "X-Auth": "Bearer eyJra...", 
      "X-B3-Sampled": "0", 
      "X-B3-Spanid": "c8547b27e22e0890", 
      "X-B3-Traceid": "eb4be0e080550506c8547b27e22e0890", 
      "X-Email": "user1@solo.io", 
      "X-Envoy-Attempt-Count": "1", 
      "X-Envoy-Decorator-Operation": "httpbin.httpbin.svc.cluster.local:8000/*", 
      "X-Envoy-Internal": "true", 
      "X-Envoy-Peer-Metadata": "ChQ...", 
      "X-Envoy-Peer-Metadata-Id": "router~10.16.2.29~istio-ingressgateway-1-18-1-78b97d9d8d-vgmmn.gloo-mesh-gateways~gloo-mesh-gateways.svc.cluster.local", 
      "X-Httpbin": "true", 
      "X-Org": "solo.io",
      "X-User": "user1"
    }
       

Cleanup

You can optionally remove the resources that you set up as part of this guide.
  kubectl -n httpbin delete ExtAuthPolicy httpbin-ext-auth
kubectl -n gloo-mesh delete ExtAuthServer httpbin-ext-auth-server
kubectl -n httpbin delete ExternalService httpbin-jwks
kubectl -n httpbin delete JWTPolicy httpbin-jwt
kubectl -n httpbin delete TransformationPolicy httpbin-transform-user