JWT, ext auth, and transformation policies
Use JWT, external auth, and transformation policies together to secure access to your routes.
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 basicAuthorization
header with a user password in requests. - A JWT policy that secures access to the
httpbin
route by requiring a JWT in anX-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
This guide assumes that you use the same names for components like clusters, workspaces, and namespaces as in the getting started. If you have different names, make sure to update the sample configuration files in this guide.
- Set up Gloo Mesh Gateway in a single cluster.
- Install Bookinfo and other sample apps.
- Configure an HTTP listener on your gateway and set up basic routing for the sample apps.
Make sure that you have the following CLI tools, or something comparable:
htpasswd
to generate hashed, salted passwords.base64
to encode strings.
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
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-23 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.
You can do the following steps in a different order, depending on when you want the policy to take effect. For example, you might want the policy to always take effect as soon as the route is created. To do so, you can create the policy before you add the route to the route table.
Generate a salt and hashed password for your user credentials. The following example uses the
htpasswd
tool for a user nameduser
.htpasswd -nbm user password
Example output:
user:$apr1$TYiryv0/$8BvzLUO9IfGPGGsPnAgSu1
Retrieve the salt and hashed password from the output of the previous step.
- Salt:
TYiryv0/
- Hashed password:
8BvzLUO9IfGPGGsPnAgSu1
- Salt:
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.
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 apply separately. If omitted and you do not have another selector such as applyToDestinations
, the policy applies to all routes in the workspace.booleanExpr
Set 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: basic
Set the name for the basic auth configuration, such as basic
. This name must match the name that you set in thebooleanExpr
.basicAuth
Configure 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.hashedPassword
The hashed password that you generated in the previous step. The example sets 8BvzLUO9IfGPGGsPnAgSu1
.salt
The salt, or random data that hashes the password, that you generated in the previous step. The example sets TYiryv0/
.jwt
Add a dummy external auth service to support multiple authentication methods with JWT. server
The ExtAuthServer
resource that represents the server for the policy to use, which you create in the next step.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
Send an unauthenticated request to the
httpbin
app.Example output: Notice that the request is denied with a
401 Unauthorized
response.HTTP/2 401
Encode the expected user credentials from the external auth policy in base64 format.
echo -n "user:password" | base64
Example output:
dXNlcjpwYXNzd29yZA==
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.
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
andemail
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
- Okta
- 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
- Okta
- 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
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 thehttpbin
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
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.
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 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.validationPolicy
Set 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 thehttpbin
app. However, when clients do pass a JWT, the JWT is validated so that subsequent steps, like a transformation policy, use validated information.providers
Configure the JWT providers for the policy with the information that you previously retrieved. This example sets up an okta
provider.claimsToHeaders
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 org
andemail
claims.issuer
Set the JWT issuer that you previously retrieved. remote.url
Configure the reference to the remote JWKS server with the details that you previously retrieved. remote.destinationRef
Add the details of the external service that you created to refer to the remote JWKS server. tokenSource
Specify where Gloo Gateway looks for the token to use for JWT authentication. If unset, the default locations are tried in the following order: - The authorization header
X-Auth
with the valueBearer <token>
. - The query parameter for
access_token
, such ashttps://<path>?access_token=<token>
.
- The authorization header
Create a transformation policy that uses the information in the JWT
email
claim to create a new header calleduser
.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.
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 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.phase
Set the phase to postAuthz
so that the transformation policy can use the information previously extracted from the JWT claim during the auth phase.request
Configure 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 newX-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.
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.
- Okta refresh token docs or the Okta developer forum about Using Postman to get an access token.
- StackOverflow about Using Postman to get an access token from Keycloak.
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
).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
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 theX-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
andemail
claims as headers to thehttpbin
app. Also, the transformation policy extracted the email from the JWT claim and added anX-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