Skip to content

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Set up staged JWT auth

Page as Markdown

Verify JWTs from an identity provider with a remote JWKS and staged enforcement, then explore other entJWT options.

Learn how to use an identity provider (IdP) to enforce a staged JWT filter with a remote JWKS. Verifying the token after external authentication makes the verified claims available to role-based access control (RBAC). This way, users can log in with their own credentials to authenticate to your services. After you set up the filter, see the Other configurations section for more entJWT options.

Before you begin

  1. Follow the Get started guide to install Solo Enterprise for kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

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

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  
    kubectl port-forward deployment/http -n kgateway-system 8080:8080

Step 1: Set up an IAM provider

Complete the Keycloak OAuth guide to set up an IAM provider. 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:

  • Issuer domain: ${KEYCLOAK_URL}/realms/<realm>/
  • JWKS endpoint: ${KEYCLOAK_URL}/realms/<realm>/protocol/openid-connect/certs
  • Auth endpoint: ${KEYCLOAK_URL}/realms/<realm>/protocol/openid-connect/token

Step 2: Create a JWT filter

Use the Keycloak JSON Web Key Set (JWKS) endpoint to validate incoming JWT keys. You also extract claims from the JWT and add them as headers so that you can use them to enforce fine-grained RBAC decisions.

  1. Set the environment variables for your IAM provider. You captured these values when you completed the Keycloak OAuth guide in the previous step.

    echo $HOST_KEYCLOAK
    echo $PORT_KEYCLOAK
    echo $KEYCLOAK_URL
    echo $KEYCLOAK_CLIENT
    echo $KEYCLOAK_SECRET
  2. Create a Backend to expose the endpoint of your IAM provider.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: Backend
    metadata:
      name: keycloak
      namespace: keycloak
    spec:
      type: Static
      static:
        hosts:
          - host: ${HOST_KEYCLOAK}
            port: ${PORT_KEYCLOAK}
    EOF
  3. Create a ReferenceGrant to allow the EnterpriseKgatewayTrafficPolicy in your namespace to access the Keycloak Backend.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1beta1
    kind: ReferenceGrant
    metadata:
      name: allow-keycloak-access
      namespace: keycloak
    spec:
      from:
      - group: enterprisekgateway.solo.io
        kind: EnterpriseKgatewayTrafficPolicy
        namespace: httpbin
      - group: enterprisekgateway.solo.io
        kind: EnterpriseKgatewayTrafficPolicy
        namespace: kgateway-system
      to:
      - group: gateway.kgateway.dev
        kind: Backend
        name: keycloak
    EOF
  4. Create an EnterpriseKgatewayTrafficPolicy with your JWT rules. In this example, you verify incoming JWTs from the jwt header against the Keycloak JWKS endpoint that you created earlier, and add the verified email claim into an x-solo-claim-email header before forwarding the request to the upstream service. The JWT policy is enforced after the external auth stage.

    kubectl apply -f- <<EOF
    apiVersion: enterprisekgateway.solo.io/v1alpha1
    kind: EnterpriseKgatewayTrafficPolicy
    metadata:
      name: jwt-gw-policy
      namespace: kgateway-system
    spec:
      targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
      entJWT:
        afterExtAuth:
          providers:
              keycloak:
                issuer: ${KEYCLOAK_URL}/realms/master
                tokenSource:
                  headers:
                  - header: jwt
                jwks:
                  remote:
                    url: ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/certs
                    backendRef:
                      name: keycloak
                      namespace: keycloak
                      kind: Backend
                      group: gateway.kgateway.dev
                claimsToHeaders:
                - claim: email
                  header: x-solo-claim-email
    EOF

    Review the following table to understand this configuration.

    SettingDescription
    targetRefsSelect the routing resource to apply the policy to, such as a Gateway, ListenerSet, or HTTPRoute. The example uses the http Gateway that you configured as part of the getting started.
    entJWTConfigure the rules for the JWT filter.
    afterExtAuthThe staged JWT lets you select whether to apply the JWT filter before or after external authentication. In this example, the JWT filter applies after external auth.
    providersConfigure the details of your IAM provider, such as the JWKS endpoint that you want to use to validate incoming JWTs.
    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 or a missing trailing slash.
    tokenSourceSpecify where the JWT token is retrieved from, such as the jwt header in this example.
    jwksThe remote JWKS endpoint and Backend reference for your IAM provider. If you use an IAM provider other than Keycloak, update the endpoint accordingly.
    claimsToHeadersExtract 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 the gateway forwards to the upstream service.
  5. Create an HTTPRoute that routes requests along the jwt.example.com domain to the httpbin app.

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

    curl -vik http://$INGRESS_GW_ADDRESS:8080/get \
    -H "host: jwt.example.com:8080"
    curl -vik localhost:8080/get \
    -H "host: jwt.example.com:8080"

    Example response:

    HTTP/1.1 401 Unauthorized
    Jwt is missing
    
  7. 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 claims that you configured in the JWT filter, like the email claim.
    • For steps to get a JWT, follow your provider’s documentation. For example, you might use the Keycloak guide to get the token from the provider that you set up earlier.

    Example command:

    export TOKEN=$(curl -Ssm 10 --fail-with-body \
    -d "client_id=${KEYCLOAK_CLIENT}" \
    -d "client_secret=${KEYCLOAK_SECRET}" \
    -d "username=user1" \
    -d "password=password" \
    -d "grant_type=password" \
    "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" |
    jq -r .access_token)
    
    echo $TOKEN
  8. Verify that the email claim is included in your token.

    echo $TOKEN | python3 -c "
    import sys, base64, json
    payload = sys.stdin.read().strip().split('.')[1]
    padding = 4 - len(payload) % 4
    decoded = base64.urlsafe_b64decode(payload + '=' * padding)
    print(json.dumps(json.loads(decoded), indent=2))
    "
  9. Repeat the request to your httpbin app. This time, include the JWT that you just created as part of the jwt header.

    curl -vik http://$INGRESS_GW_ADDRESS:8080/get \
    -H "host: jwt.example.com:8080" \
    -H "jwt: $TOKEN"
    curl -vik localhost:8080/get \
    -H "host: jwt.example.com:8080" \
    -H "jwt: $TOKEN"

    Example output: Verify that you get back a 200 success status code. Notice that the email claim is added to the X-Solo-Email-Claim header as defined in the JWT filter. In the example, the user email is user1@example.com.

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

Other configurations

The entJWT field supports more options than the previous example shows. Add the following fields to the EnterpriseKgatewayTrafficPolicy as needed. Each snippet shows only the relevant part of the entJWT configuration.

Enforce JWT before external auth

Use beforeExtAuth instead of afterExtAuth to verify the JWT before external authentication runs. The provider options are the same; only the stage changes.

entJWT:
  beforeExtAuth:
    providers:
      keycloak:
        # issuer, jwks, and other provider settings

Change the validation policy

By default, only requests that present a valid JWT are allowed. Set validationPolicy on the stage to allow requests with a missing or failed token, such as when you chain JWT with another authentication method.

entJWT:
  afterExtAuth:
    validationPolicy: AllowMissingOrFailed
    providers:
      keycloak:
        # provider settings
ValueBehavior
RequireValid (default)Allow only requests that authenticate with a valid JWT.
AllowMissingAllow requests with no JWT, but reject requests that present an invalid JWT.
AllowMissingOrFailedAllow requests even when the JWT is missing or fails verification, such as to fall through to another authentication method.

Read the token from a custom location

By default, the token is read from the Authorization header as a bearer token. Use tokenSource to read it from other headers or query parameters. The filter tries each entry in order.

providers:
  keycloak:
    tokenSource:
      headers:
        - header: x-jwt
          prefix: "Bearer "
      queryParams:
        - access_token
    # other provider settings

Keep the token for the upstream service

By default, the gateway removes the token’s header before it forwards the request. Set keepToken to forward the token to the upstream service.

providers:
  keycloak:
    keepToken: true
    # other provider settings

Adjust the clock skew

The filter allows 60 seconds of clock skew when it checks time-based claims such as exp and nbf. Set clockSkewSeconds to change the tolerance.

providers:
  keycloak:
    clockSkewSeconds: 120
    # other provider settings

Disable JWT for a stage

Set disable on a stage to turn off JWT authentication for that stage. This is useful to override a JWT policy that is applied at a higher level in the configuration hierarchy.

entJWT:
  afterExtAuth:
    disable: {}

Cleanup

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

kubectl delete EnterpriseKgatewayTrafficPolicy -n kgateway-system jwt-gw-policy
kubectl delete ReferenceGrant -n keycloak allow-keycloak-access
kubectl delete Backend -n keycloak keycloak
kubectl delete HTTPRoute -n httpbin httpbin-jwt-route
Was this page helpful?