JWT claim- and scope-based auth
Authorize requests based on claims or scopes in a JWT.
To access your destinations in the service mesh, you can require specific JWT claims or scopes for authorization. These rules can match values extracted from the JWT for allowed issuers, expiries, audiences, scopes, or other valid JSON claims from the JWT payload.
About claim-based JWT policies
You can restrict a request’s access based on the claims and scopes in a JWT. For more information about JWTs, see the JWT overview and API docs.
Claims
Claims are key-value pairs that provide identity details, such as the subject’s user ID, the entity that issued the token, and expiration time. RFC 7519 spec reserves seven claims, and the IANA JSON Web Token Claims outline many more registered claims to encourage interoperability across providers. Further, your OIDC provider might have custom claims, such as described in the Auth0 docs.
For Gloo JWT policies, claims have the following features:
- When you require multiple claims, the JWT must have all of the specified claims to be allowed. Claims are logically
AND
’d together. - You can optionally specify required
values
ornotValues
for a claim’s key, which are logicallyOR
’d together.- For
values
, the claim can have any of the specified values to be allowed. - For
notValues
, the claim cannot have any of the specified values to be allowed. - For information about nesting, see JWT nested claims.
- For
The claims
field does not support the following features:
- Multiple list entries with the same key.
- Requiring each scope in the claim. For the following example policy, a JWT with
is:developer
orread:secrets
scopes would be allowed. To require the JWT to have both scopes, use therequiredScopes
field instead.claims: - key: scope values: - is:developer - read:secrets
Nested claims
Nested claims are when the value of a claim in a token is an object with more claims. This hierarchical, or nested, structure helps you organize complex data and design tokens for more flexible usage by various apps.
"resource_access": {
"account": {
"groups": [
"engineering",
"qa"
]
}
Nested claims are separated by delimiters. By default, a comma (,
) is used to delimit nested claims. If your token structure uses a different delimiter such as a period (.
) or pipe (|
), make sure to specify the delimiter in your configuration.
You cannot extract a nested claim. From the example token, you cannot extract the values of the nested claims of account
or groups
. You can extract a top-level claim with multiple values, such as permissions
.
Scopes
Scopes are a special type of claim that represent specific permissions that a client has. These scopes might be returned from an Identity and Access Management (IAM) provider, and are typically formatted similar to is:developer
or read:profile
.
For Gloo JWT policies, scopes have the following features:
- Scopes are specified as a list, not key-value pairs.
- When you require multiple scopes, the JWT must have all of the specified scopes to be allowed. Scopes are logically
AND
’d together.
Scopes do not support the following features:
- Listing scopes that, if present in the claim, are not allowed. Instead, you can use the
claims
field with thenotValue
setting. - Requiring that only one of the listed scopes is present (logical
OR
). Instead, you can useclaims
.
More considerations
As you develop your JWT authorization policies, keep in mind the following considerations about how claims, scopes, and other policy features work together.
- The main difference between
requiredScopes
andclaims
for configuring scopes is that therequiredScopes
value sets a list of scopes that must all be included in the request (logicalAND
). On the other hand,claims
lets you set several scopes that can be accepted (logicalOR
), or scopes that will be denied (notValue
). - You can use claims and scopes together. A request must meet all of the claim and scope conditions that you specify.
- If your policy has multiple providers, the claims and scopes apply to all the providers. If one of your providers does not return a claim or scope value, consider using a separate policy for that provider.
- Because you use the JWT policy for authorization, you cannot set the policy’s phase to
postAuthz
. - You cannot apply multiple JWT policies to the same route in a route table, so make sure to keep all your rules for a route in the same policy.
- 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. If you have different names, make sure to update the sample configuration files in this guide.
Complete the multicluster getting started guide to set up the following testing environment.
- Three clusters along with environment variables for the clusters and their Kubernetes contexts.
- The Gloo
meshctl
CLI, along with other CLI tools such askubectl
andistioctl
. - The Gloo management server in the management cluster, and the Gloo agents in the workload clusters.
- Istio installed in the workload clusters.
- A simple Gloo workspace setup.
- Install Bookinfo and other sample apps.
Example token
Your company might organize permissions to reflect your physical locations or business units. In the following example, the resource_access
key has nested claims. The permissions
key has an array of multiple values.
{
"iss": "https://dev.example.com",
"exp": 4804324736,
"iat": 1648651136,
"org": "internal",
"email": "dev1@solo.io",
"scope": "is:developer",
"permissions": [
"read",
"write",
"approve"
],
"resource_access": {
"account": {
"groups": [
"engineering",
"qa"
]
}
}
}
Example JWT policies
Review the following examples for claim- and scope-based JWT authorization. You can apply a JWT policy at the destination or route level, but you cannot apply multiple JWT policies to the same destination or to the same route in a route table. If you try to apply multiple policies, only the policy that you created first takes effect. In general, you apply policies to routes to protect ingress traffic through the gateway, and to destinations to protect traffic within the service mesh. For more information, see Applying policies and the API docs.
Steps
In this guide, you learn how to set up JWT policies to cover the following use cases:
- Authorization: Accept only requests that have JWTs with certain information in a nested claim. This approach configures the details of the nested claims as part of the JWT policy’s authorization settings by using the
spec.config.claims
section. - Headers: Extract claims from the JWT in the request and put the claims into a header for subsequent processing. This approach configures the details of the claims as part of the JWT policy’s provider settings by using the
spec.config.providers
section.
Authorize requests with nested claims
Create a JWT policy to allow access to users with a particular nested claim, such as the engineering
group in the example token. For quick testing, you can use an in-line JWKS provider with sample keys. To make your own, see Create your own keys and JWT.
Create a JWT policy that authorizes requests based on a nested claim. For other configuration examples, see Example JWT policies. For more information about each field, review the API docs.
kubectl apply -f- <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: JWTPolicy metadata: annotations: cluster.solo.io/cluster: "" name: jwt-policy namespace: httpbin spec: applyToRoutes: - route: labels: route: httpbin config: claims: - key: "resource_access.account.groups" nestedClaimDelimiter: "." values: - admin - engineering phase: preAuthz: {} providers: dev-example: issuer: https://dev.example.com local: inline: |- -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- tokenSource: headers: - name: X-Auth prefix: 'Bearer ' queryParams: - auth_token EOF
Send a request to the
httpbin
app without any authentication. Notice that your request is denied with a401
error. Create a temporary curl pod in thebookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler.- Create the curl pod.
kubectl run -it -n httpbin --context ${REMOTE_CONTEXT1} curl --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the httpbin app.
curl -vik -H "X-httpbin: true" http://www.example.com/get
Example output:
HTTP/1.1 401 Unauthorized www-authenticate: Bearer realm="http://www.example.com/get" ... Jwt is missing
- Create the curl pod.
Save the following example token as an environment variable.
export TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2Rldi5leGFtcGxlLmNvbSIsImV4cCI6NDgwNDMyNDczNiwiaWF0IjoxNjQ4NjUxMTM2LCJvcmciOiJpbnRlcm5hbCIsImVtYWlsIjoiZGV2MUBzb2xvLmlvIiwic2NvcGUiOiJpczpkZXZlbG9wZXIiLCJwZXJtaXNzaW9ucyI6WyJyZWFkIiwid3JpdGUiLCJhcHByb3ZlIl0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7Imdyb3VwcyI6WyJlbmdpbmVlcmluZyIsInFhIl19fX0.aBuMQ7LnCbjIrZ7srI-2zr031XY3fE77_UJEX38GB86k5htZG-bMhqBBd140YgUpzf_KwfD0DkhCF8bc_0ZmD3Mb8xLletj9rmvbevKY5IVLnPeZ_NTz7akR2uk6rfmJrYHucJqeo-yG8YIwZU4NM7YjLKl_nYCjdIjoablOS-FRmBwdQs5vmrKVf7sM2qPuivgLgbhvwEzSufYr8tzz52_6Dk7MIYIJydPFLuHbC6JC1ZYxYBbo6W-cXWdWiiF5e1icHK2sV0RkHkn6L1KnK5W3tDLmSopvWOgW2ZXmPi_RACi6Djb3mYq7PmoTuGb-dvNNRwQcI44JFt1dxV5cmA
Decode the example token to verify that the token meets the validation requirements that you set in the JWT policy for the
dev-example
provider, including the nested claim membership in theengineering
group.echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode
Example output:
{"iss":"https://dev.example.com","exp":4804324736,"iat":1648651136,"org":"internal","email":"dev1@solo.io","scope":"is:developer","resource_access":{"account":{"groups":["engineering","qa"]}}}
Try the request to the
httpbin
app again, this time with your JWT token. Notice that your request is now accepted! Create a temporary curl pod in thebookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler.- Create the curl pod.
kubectl run -it -n httpbin --context ${REMOTE_CONTEXT1} curl --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the httpbin app. Make sure to replace
<$TOKEN>
with the token that you previously retrieved.curl -vik -H "X-httpbin: true" http://www.example.com/get?auth_token=<$TOKEN>
- Create the curl pod.
Extract claims to headers
You can extract a claim with an array of values from the JWT in a request. Then, this claim information is appended to a header for use in subsequent processing.
You cannot extract a nested claim. From the example token, you cannot extract the values of the nested claims of account
or groups
. You can extract a top-level claim with multiple values, such as permissions
.
Update the JWT policy from the previous section as follows. For more information, review the API docs.
- Append
claimsToHeaders
for theorg
,email
, andpermissions
value of the token. - Set a custom delimiter of a pipe (
|
) so that the appended values are separated by a pipe instead of the default comma (,
). - Note that the custom delimiter applies to each of the
claimsToHeaders
that you configure.
kubectl apply -f- <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: JWTPolicy metadata: annotations: cluster.solo.io/cluster: "" name: jwt-policy namespace: httpbin spec: applyToRoutes: - route: labels: route: httpbin config: claims: - key: "resource_access.account.groups" nestedClaimDelimiter: "." values: - admin - engineering phase: preAuthz: {} providers: dev-example: claimsToHeaders: - append: true claim: org header: x-org - append: true claim: email header: x-email - append: true claim: permissions header: x-permissions customDelimiter: '|' issuer: https://dev.example.com local: inline: |- -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp/ZO8Qhfj6kB5BxndHds x12rgJ2DyU0lvlbC4Ip1zTlULV/Fuy1uAqKbBRC9IyoFiYxuWTLbvpLv5SLnrIPy f4nvX4oHGdyFrcwvCtKvcgtttB363HWiG0PZwSwEn0yMa7s4Rhmy9/ZSYm+sMZQw 8wKv40pYnBuqRv1DpfvZLOXvICCkd5f03zv1HQXIfO3YjXOy58vOkajpzTmx4q2A UilrCJcR6tBMoAph5FiJxgRmdLziKx3QXukUSNWfrFVSL+D/BoQV+2TJDZjKfPgj DDMKeb2OsonQ0me3VSw2gkdnE9cyIklXcne/+oKEqineG8a12JSfEibf29iLiIXO gQIDAQAB -----END PUBLIC KEY----- tokenSource: headers: - name: X-Auth prefix: 'Bearer ' queryParams: - auth_token EOF
- Append
Send a request to the
httpbin
app with the JWT token from the previous section. Notice that your request now includes the information that you extracted from the claims in the headers Create a temporary curl pod in thebookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler.- Create the curl pod.
kubectl run -it -n httpbin --context ${REMOTE_CONTEXT1} curl --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the httpbin app. Make sure to replace
<$TOKEN>
with the token that you previously retrieved.curl -vik -H "X-httpbin: true" http://www.example.com/get?auth_token=<$TOKEN>
Example output: Notice the values of the
org
,email
, andpermissions
claims. The multiple values ofpermissions
are delimited with the custom pipe that you configured.... "X-Org": [ "internal" ], "X-Email": [ "dev1@solo.io" ], "X-Permissions": [ "read|write|approve" ],
- Create the curl pod.
Cleanup
You can optionally remove the resources that you set up as part of this guide.
kubectl delete JwtPolicy -n httpbin jwt-policy