Multiple JWT providers
Allow JWT credentials from multiple providers. Then, users can include any valid JWT from any provider to access your secured services. You might use multiple providers when you want to extract different claims for different issuers, such as subdomains on your website. For more information about JWTs, see the JWT overview and API docs.
If you import or export resources across workspaces, your policies might not apply. For more information, see Import and export policies.
Before you begin
Follow the getting started instructions to:
- Set up Gloo Gateway in a single cluster.
- Deploy sample apps.
- Configure an HTTP listener on your gateway and set up basic routing for the sample apps.
Configure JWT policies
For multiple JWT providers, you can add the providers to the same JWT policy as shown in the following example. You can also create separate JWT policies for each provider. You can apply a JWT policy at the route level, but you cannot apply multiple JWT policies to the same route in a route table. For more information, see Applying policies.
The following example is for a basic JWT policy with multiple providers that use a local JWT issuer and inline public key. The policy does the following:
- Enables JWT authentication for the selected routes.
- Configures two providers for different issuing subdomains in your organization: the
dev-provider
forhttps://dev.example.com
anddocs-provider
forhttps://docs.example.com
. - For both providers, adds the value of an
org
claim from the JWT payload to anX-Org
header in the request, if present. - For both providers, adds the value of an
email
claim from the JWT payload to anX-Email
header in the request, if present. - For only the docs provider, adds the value of a
scope
claim from the JWT payload to anX-Scope
header in the request, if present. - Checks requests by using the token found in the
X-Auth
header with the prefixBearer <token>
, or in a query parameterauth_token=<token>
. Note that if a request has both the header and query parameter, both tokens must be valid for Gloo Gateway to accept the request.
apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
metadata:
annotations:
cluster.solo.io/cluster: ""
name: jwt-policy
namespace: default
spec:
applyToRoutes:
- route:
labels:
route: httpbin
config:
phase:
preAuthz: {}
providers:
dev-provider:
claimsToHeaders:
- append: true
claim: org
header: x-org
- append: true
claim: email
header: x-email
issuer: https://dev.example.com
local:
inline: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz
oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO
G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ
e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH
LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO
39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/
NwIDAQAB
-----END PUBLIC KEY-----
tokenSource:
headers:
- name: X-Auth
prefix: 'Bearer '
queryParams:
- auth_token
docs-provider:
claimsToHeaders:
- append: true
claim: org
header: x-org
- append: true
claim: email
header: x-email
- append: true
claim: scope
header: x-scope
issuer: https://docs.example.com
local:
inline: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz
oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO
G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ
e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH
LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO
39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/
NwIDAQAB
-----END PUBLIC KEY-----
tokenSource:
headers:
- name: X-Auth
prefix: 'Bearer '
queryParams:
- auth_token
Review the following table to understand this configuration. For more information, see the API docs.
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 previously applied. If omitted, the policy applies to all routes in the workspace. |
phase |
Set when to apply the JWT filter in the request chain, either before (preAuthz ) or after (postAuthz ) authorization to have access to the JWT token. You can also set the priority if you have multiple policies in the same phase. The lowest numbered priority is run first. For more information, see Phase considerations. This example sets no priority, so the default value of zero is used. |
providers |
Set up multiple JWT providers for the policy. You can name the providers to help you map the provider when viewing logs to debug. However, the provider name does not affect the policy's behavior and cannot be used by other resources to select the policy. In this example, two providers are set up, with the names dev-provider and docs-provider . |
claimsToHeaders |
Optionally 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 same two org and email claims and adds them as headers for both providers. The docs-provider also has an additional scope claim. |
claimsToHeaders.append |
Enter a boolean value to add a claim's value if the header exists in the request. Use true to append the claim's value to the header, and false to overwrite any existing value in the header. |
claimsToHeaders.claim |
Enter the name of the claim in the JWT payload to get the value for the header. |
claimsToHeaders.header |
Enter the request header that the value of the claim is copied to. |
issuer |
Optionally, set the JWT issuer, usually as a subdomain of a URL or email address. If set, the iss field in the JWT token must match this field, or else the request is denied. If unset, the iss field in the JWT token is not checked. In this example, the issuer is set to a unique URL per provider, dev.example.com or docs.example.com . |
keepToken |
This value is set to true so that the JWT is kept in the request after verification. This way, other policies can use the JWT information as needed. |
local |
Provide the PEM-formatted public key to verify the JWT token. In this example, the public key is written inline to the policy for testing purposes. For production scenarios, you can set a remote reference to your JSON Web Key Set (JWKS) server instead of this local setting. |
Verify JWT policies
-
Save the example JWT policy with multiple providers for the
httpbin
route that you reviewed in the previous section.cat << EOF > jwt-policy_bookinfo_jwt-policy.yaml apiVersion: security.policy.gloo.solo.io/v2 kind: JWTPolicy metadata: annotations: cluster.solo.io/cluster: "" name: jwt-policy namespace: default spec: applyToRoutes: - route: labels: route: httpbin config: phase: preAuthz: {} providers: dev-provider: claimsToHeaders: - append: true claim: org header: x-org - append: true claim: email header: x-email issuer: https://dev.example.com local: inline: |- -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO 39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/ NwIDAQAB -----END PUBLIC KEY----- tokenSource: headers: - name: X-Auth prefix: 'Bearer ' queryParams: - auth_token docs-provider: claimsToHeaders: - append: true claim: org header: x-org - append: true claim: email header: x-email - append: true claim: scope header: x-scope issuer: https://docs.example.com local: inline: |- -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO 39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/ NwIDAQAB -----END PUBLIC KEY----- tokenSource: headers: - name: X-Auth prefix: 'Bearer ' queryParams: - auth_token EOF
-
Create a private-public key pair by using OpenSSL. You use this pair to create your own JWT token.
- Create a private key to sign the JWT.
openssl genrsa 2048 > private-key.pem
- Extract the public key from the private key.
openssl rsa -in private-key.pem -pubout
- Create a private key to sign the JWT.
-
Open the downloaded JWT policy.
open jwt-policy_bookinfo_jwt-policy.yaml
-
In the
inline
section for both providers, copy in the public key value from the output of the previous step, including theBEGIN/END
parts. Make sure the indentation is kept. -
Create the JWT policy with your new public key value, which is used to verify the JWT token in subsequent requests.
kubectl apply -f jwt-policy_bookinfo_jwt-policy.yaml
-
Create a JWT for your first provider,
dev-provider
, that uses your private key. Refer to following image to help understand the UI location of the steps.- In your browser, go to https://jwt.io/. For the subsequent steps, you can refer to the following image.
- From the Algorithm dropdown, select
RS256
. - From the Decoded section, scroll to the Payload: Data pane. Replace the content by pasting the following JWT payload information.
{ "iss": "https://dev.example.com", "exp": 4804324736, "iat": 1648651136, "org":"solo-io", "email": "user2@solo.io" }
- From the Decoded section, scroll to the Verify Signature pane. In the first input box for
-----BEGIN PUBLIC KEY-----
, replace the content by pasting the value of your public key from the previous output of your terminal. - In your terminal, copy the contents of your private key.
cat private-key.pem | pbcopy
- Return to your browser and go to the Verify Signature pane in the Decoded section. In the second input box for
-----BEGIN PRIVATE KEY-----
, replace the content by pasting the value of your private key. - Optional: Click Share JWT and paste the JWT values into a file to refer to later.
- From the Encoded section, copy the token value to your clipboard, such as
eyJhbG....
. - Add the token value to an environment variable on your local machine.
export TOKEN_DEV="Bearer <token_value>"
-
Repeat the previous step to create another JWT for your second provider,
docs-provider
.- For the Payload: Data section, use the following details. Note that the issuer, org, and email details differ from the
dev-provider
. Also, this payload has a scope field for the additional header.{ "iss": "https://docs.example.com", "exp": 4804324736, "iat": 1648651136, "org":"end-user", "email": "user1@example.com", "scope": "reader" }
- After filling out the rest of the details and getting the encoded token, set the token as a different environment variable.
export TOKEN_DOCS="Bearer <token_value>"
- For the Payload: Data section, use the following details. Note that the issuer, org, and email details differ from the
-
Try the request to the
httpbin
app without any authentication. Notice that your request is denied with a401
error.curl -vik -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
curl -vik -H "X-httpbin: true" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/get
HTTP/1.1 401 Unauthorized www-authenticate: Bearer realm="http://www.example.com/get" ... Jwt is missing
-
Try the request to the
httpbin
app again, this time with yourdev-provider
token. Notice that your request is now accepted!curl -vik -H "X-Auth: ${TOKEN_DEV}" -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
curl -vik -H "X-Auth: ${TOKEN_DEV}" -H "X-httpbin: true" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/get
200
response. You also can see theX-Email
andX-Org
headers that you appended in theclaimsToHeaders
section of the policy.HTTP/1.1 200 OK ... { "args": {}, "headers": { "Accept": "*/*", "Host": "www.example.com", "X-Email": "user2@solo.io", "X-Envoy-Attempt-Count": "1", "X-Envoy-Decorator-Operation": "httpbin.default.svc.cluster.local:8000/*", "X-Envoy-Internal": "true", "X-Httpbin": "true", "X-Org": "solo-io" },
-
Try the request to the
httpbin
app again, this time with yourdocs-provider
token. This request is also accepted, but the output differs from the request that used thedev-provider
token.curl -vik -H "X-Auth: ${TOKEN_DOCS}" -H "X-httpbin: true" --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/get
curl -vik -H "X-Auth: ${TOKEN_DOCS}" -H "X-httpbin: true" --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/get
200
response. You also can see theX-Email
,X-Org
, andX-Scope
headers that you appended in theclaimsToHeaders
section of the policy.HTTP/1.1 200 OK ... { "args": {}, "headers": { "Accept": "*/*", "Host": "www.example.com", "X-Email": "user1@example.com", "X-Envoy-Attempt-Count": "1", "X-Envoy-Decorator-Operation": "httpbin.default.svc.cluster.local:8000/*", "X-Envoy-Internal": "true", "X-Httpbin": "true", "X-Org": "end-user", "X-Scope": "reader" },
-
Optional: Clean up the resources that you created.
kubectl -n bookinfo delete jwtpolicy jwt-policy