In the previous basic JWT guide, you created a JWT filter with an inline JWKS and used your own JWTs to authenticate. Now, learn how to use an IAM provider to enforce a JWT filter with role-based access control (RBAC). This way, users can log in with their own credentials to authenticate to your services.

Before you begin

  1. Follow the Get started guide to install Gloo Gateway, set up a gateway resource, and deploy the httpbin sample app.

  2. Get the external address of the gateway and save it in an environment variable.

Step 1: Set up an IAM provider

Complete one of the OAuth guides to set up an IAM provider, such as Auth0 or Google. The guides cover the following steps.

  1. If you do not already have one, create an account with the IAM provider.

  2. Create the auth app in the IAM provider.

  3. Skip the rest of the guides that involve creating an AuthConfig for an OAuth filter. Instead, you create a JWT filter in this guide.

  4. Make sure that you have the following details for your IAM provider.

    • The issuer domain of the IAM provider.
    • The port that the issuer domain listens on, such as 443 for HTTPS.
    • The JWKS endpoint of the issuer domain.
    • To get a JWT to test the steps, you typically need the client ID and secret of the auth application, the auth endpoint, as well as user credentials.

    Example endpoint values by IAM provider:

Step 2: Create a JWT filter

Use a JSON Web Key Set (JWKS) endpoint to get the email claim information of the JWT from the IAM provider. The claim is placed in an email header that you use to enforce fine-grained RBAC decisions.

  1. Create an Upstream to expose the endpoint of your IAM provider.

      kubectl apply -f- <<EOF
    apiVersion: gloo.solo.io/v1
    kind: Upstream
    metadata:
      name: jwks-upstream
      namespace: gloo-system
    spec:
      static:
        hosts:
          # The host domain where the IAM provider publishes the JWKS endpoint.
          - addr: ${DOMAIN}
            port: 443
    EOF
      
  2. Create a VirtualHostOption with the following JWT rules. Note that instead of the VirtualHostOption, you can also use a RouteOption. This way, the JWT rules apply only to the route that the RouteOption configures. The JWT rules of a VirtualHostOption apply to each route along the same host that the gateway listens on (unless overridden by a RouteOption). For more information, see the Policy Overview.

      kubectl apply -f- <<EOF
    apiVersion: gateway.solo.io/v1
    kind: VirtualHostOption
    metadata:
      name: jwt-vho
      namespace: gloo-system
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: http
        namespace: gloo-system
      options:
        jwtStaged:
          beforeExtAuth:
            providers:
              auth0:
                issuer: https://dev-1234567.us.auth0.com/
                tokenSource:
                  headers:
                  - header: jwt
                claimsToHeaders:
                - claim: email
                  header: x-solo-claim-email
                jwks:
                  remote:
                    url: https://dev-1234567.us.auth0.com/.well-known/jwks.json
                    upstreamRef:
                      name: jwks-upstream
                      namespace: httpbin
    EOF
      

    Review the following table to understand this configuration.

    SettingDescription
    targetRefsSelect the Gateway that you want to configure with this virtual host. The example uses the http gateway that you configured as part of the getting started.
    jwtStagedConfigure the rules for the JWT filter. The staged JWT lets you select whether to apply the JWT filter before or after external authentication. In this example, the JWT filter applies before external auth.
    providersConfigure the details of your IAM provider, such as auth0 in this example.
    issuerEnter the issuer domain of the IAM provider. This value must match the ISS claim in the JWT that the IAM provider returns. Common errors such as Jwt issuer is not configured might indicate a different issuer, such as forgetting a trailing slash.
    tokenSourceSpecify where the JWT token is retrieved, such as the jwt header in this example.
    claimsToHeaderExtract and add claims from the JWT as headers in the response. In this example, the email claim from the JWT is added to an x-solo-claim-email header that you use in a later step.
    jwksThe remote URL is the JWKS endpoint from your IAM provider, such as the Auth0 example. If you use a different IAM provider, update the endpoint accordingly. You created the Upstream in the previous step.
  3. Create an HTTPRoute for the httpbin app on the same gateway as the VirtualHostOption configures. If you used a RouteOption instead, make sure to add an ExtensionRef filter that selects the RouteOption.

      kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1beta1
    kind: HTTPRoute
    metadata:
      name: httpbin-jwt-route
      namespace: httpbin
    spec:
      parentRefs:
        - name: http
          namespace: gloo-system
      hostnames:
        - extauth.example.com
      rules:
        - matches:
          - path:
              type: PathPrefix
              value: /
          backendRefs:
            - name: httpbin
              port: 8000
    EOF
      
  4. Send a request to the protected httpbin route without a JWT. Verify that you get back a 401 Unauthorized error code.

      curl -i http://extauth.example.com:8080/get
      

    Example response:

      HTTP/1.1 401 Unauthorized
    Jwt is missing
      
  5. Follow your IAM provider’s docs to get a JWT through an authorization code flow, and save the ID token in an environment variable.

    • To get a JWT, you often need the client ID and secret of the auth application, as well as user credentials.
    • Make sure that you get back a JWT that includes the scope that you configured in the JWT filter, like the email scope. Typically, you add the scopes that you need in a field such as scope: openid email.
    • For steps to get a JWT, follow your provider’s documentation. For example, you might use the Auth0, Google, Keycloak, or Okta guides along with a tool to help get the auth code that you exchange for the token, such as the OpenID Connect Debugger.
      export TOKEN=<id_token>
      
  6. Repeat the request to your httpbin app. This time, include the JWT that you just created as part of the jwt header.

      curl -i http://extauth.example.com:8080/get -H "jwt: <TOKEN>"
      

    Example output: Verify that you get back a 200 success status code. Notice that the email scope from the claim is added to the X-Solo-Email-Claim header that you included in the JWT filter. In the example, the user email is docs@solo.io.

      HTTP/1.1 200 OK
    
    {
      "args": {},
      "headers": {
        "Accept": [
          "*/*"
        ],
        "Host": [
          "extauth.example.com:8080"
        ],
        "User-Agent": [
          "curl/8.7.1"
        ],
        "X-Solo-Claim-Email": [
          "docs@solo.io"
        ]
      },
      ...
    }
      

Step 3: Add RBAC rules

Now that you extracted the email claim from the JWT, apply RBAC rules to restrict access.

  1. Update the VirtualHostOption with the following RBAC rules. Note that instead of the VirtualHostOption, you can also use a RouteOption. This way, the RBAC rules apply only to the route that the RouteOption configures. The RBAC rules of a VirtualHostOption apply to each route along the same host that the gateway listens on (unless overridden by a RouteOption). For more information, see the Policy Overview.

      kubectl apply -f- <<EOF
    apiVersion: gateway.solo.io/v1
    kind: VirtualHostOption
    metadata:
      name: jwt-vho
      namespace: gloo-system
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: http
        namespace: gloo-system
      options:
        jwtStaged:
          beforeExtAuth:
            providers:
              auth0:
                issuer: https://dev-1234567.us.auth0.com/
                tokenSource:
                  headers:
                  - header: jwt
                claimsToHeaders:
                - claim: email
                  header: x-solo-claim-email
                jwks:
                  remote:
                    url: https://dev-1234567.us.auth0.com/.well-known/jwks.json
                    upstreamRef:
                      name: jwks-upstream
                      namespace: httpbin
        rbac:
          policies:
            viewer:
              permissions:
                methods:
                - GET
                pathPrefix: /anything
              principals:
              - jwtPrincipal:
                  claims:
                    email: docs@solo.io
    EOF
      

    Review the following table to understand this configuration.

    SettingDescription
    permissionsRestrict permissions of the RBAC role. In this example, the viewer role is restricted to use only the GET method along the /anything endpoint.
    principalsConfigure the principals that the RBAC rules apply to. In this example, the email claim is used to restrict permissions for the example docs@solo.io user.
  2. Repeat the request to your httpbin app with the JWT. This time, the request is denied because you tried to access an endpoint that you are no longer authorized to (/get).

      curl -i http://extauth.example.com:8080/get -H "jwt: <TOKEN>"
      

    Example output:

      HTTP/1.1 403 Forbidden
    RBAC: access denied
      
  3. Send a GET request to your httpbin app along the /anything endpoint that you have access to. This time, the request succeeds.

      curl -i http://extauth.example.com:8080/anything -X GET -d "test: This is anything." -H "jwt: <TOKEN>"
      

    Example output:

      HTTP/1.1 200 OK
    
    {
      "args": {},
      "headers": {
        "Accept": [
          "*/*"
        ],
        "Host": [
          "extauth.example.com:8080"
        ],
        "User-Agent": [
          "curl/8.7.1"
        ],
        "X-Solo-Claim-Email": [
          "docs@solo.io"
        ],
        "form": {
          "test: This is anything.": [
            ""
          ]
        },
      },
      ...
    }
      

Cleanup

You can optionally remove the resources that you set up as part of this guide.

  kubectl delete Upstream -n gloo-system jwks-upstream
kubectl delete VirtualHostOption -n gloo-system jwt-vho
kubectl delete HTTPRoute -n httpbin httpbin-jwt-route