Configure JWT authentication
Overview
Vault can authenticate users using JWT access tokens generated by an external identity provider. When Vault receives a JWT access token, it verifies the token's signature and validates the claims in the token. If the token is valid, Vault uses the roles
claim in the token to choose the roles of the user.
The JWT access token is validated for each request to determine that:
- It is active (not expired).
- These claims match the setting in the IAM configuration:
iss
aud
- The
roles
claim contains only one role and that role exists in the IAM configuration. - It is signed using the public key set in the IAM configuration.
Configuration
The following configuration options are available for JWT authentication
Option | Description |
---|---|
iss | The issuer of the JWT access token. If the JWT token does not match this value, Vault fails the authentication. |
aud | The audience of the JWT access token. If the JWT token does not match this value, Vault fails the authentication. |
roles_claim | The claim in the JWT access token that contains the roles of the user. If the JWT token does not contain this claim, Vault fails the authentication. If not set, defaults to roles . |
keys | The public key of the key-pair used to sign the JWT access token, in JWKS format. Must be used if jwks_uri is not used. |
jwks_uri | The URL of the JWKS endpoint that contains the public key in JWT format. Usually will be in the following format: https://<identity-privoder/.well-known/jwks.json . Must be used if keys is not used. |
bound_claims | Extra claims validation. See section below for more details. |
extra_claims | Extra claims to be parsed alongside the claims of the JWT access token. Usually will contain extra namespace claims with prop-claim-ref enforcement. If namespace_top_claim is configured, the extra claims will be nested under it. See section below for more details. |
namespace_top_claim | A claim that have the namespace claims nested in. This claim is expected to be an object. Useful if the platform used for generate JWTs only allows custom claims nested under a claim that is not under your control. This configuration does not affect bound_claims , and roles_claim which operates on the top-level. Defaults to the top-level of the JWT ("root"). |
allowed_roles | The Vault IAM roles JWT tokens are allowed to use, in the format of an array of strings. If a token evaluates to a role that is not in the list, the request is not authorized. If not configured, all roles are allowed. |
roles_map | A mapping of the role defined in the JWT token to the Vault IAM role. If this configuration is used, the role in the JWT must match one of the keys in the mapping. See the Google Cloud Platform section for an example. |
Using jwks_uri
requires Vault to have outbound access to the JWKS endpoint. The response will be cached for 24 hours, unless configured differently by the HTTP response from the endpoint.
Bound claims
If an extra claims validation is required, you can use the bound_claims
configuration. After successful validation of the JWT access token, Vault compares the bound claims with the claims in the JWT access token. If any of the bound claims are not present in the JWT access token, or doesn't match, the authentication fails.
The bound claims configuration is a map of strings to values or an array of values of the following types: string, boolean, integer, or float. The keys are the names of the claims, and the values are the expected values of the claims. If the value is an array, the claim must match one of the values in the array.
Example configuration:
[idps]
[idps.app1]
type = "direct-jwt"
allowed_roles = ["VaultRole1", "VaultRole2"] # Optional.
[idps.app1.conf]
iss = "https://jwt.io/"
aud = "vault1"
# Change the value of "keys" to the JWK key.
keys = ...
[idps.app1.conf.bound_claims]
group = ["group-1", "group-2"]
email_verified = true
Namespace claims
Vault supports special meaning to claims under the https://app.piiano.io/
namespace. The following claims are supported:
https://app.piiano.io/role
The https://app.piiano.io/role
claim is used to specify the role of the user (expecting a string). roles_claim
overrides this configuration if specified.
https://app.piiano.io/prop/<prop>
Vault supports enforcement of object-level access control using claims under the https://app.piiano.io/prop/
namespace. The https://app.piiano.io/prop/<prop>
claim is used to specify the value of the property to enforce when interacting with objects, tokens, and encrypted objects.
The value of the claim is a string or an array of strings. If the value is an array, the operation is allowed when the property value matches any of the values in the array.
For example, the following claims enforce that the request is allowed only if the user_id
property is user1
and the group_id
property is either group-1
or group-2
in the object, token, or ciphertext being accessed:
{
"https://app.piiano.io/prop/user_id": "user1",
"https://app.piiano.io/prop/group_id": ["group-1", "group-2"]
}
The following API endpoints support object-level access control:
Endpoint | Description |
---|---|
add-object | Object fields must match the property values specified by the claim. |
get-object-by-id | Returned objects must match the property values specified by the claim. |
update-object-by-id | The fields of an object being updated must match the property values specified by the claim, before and after the update. |
delete-objects | The object to delete must match the property values specified by the claim. |
search-objects | Returned objects must match the property values specified by the claim. |
list-objects | Returned objects must match the property values specified by the claim. |
update-objects | The fields of objects being updated must match the property values specified by the claim, before and after the update. |
delete-objects | The objects to delete must match the property values specified by the claim. |
tokenize | The new tokens' fields must match the property values specified by the claim. |
detokenize | Detokenized token's field must match the property values specified by the claim. |
update-tokens | The fields of the token being updated must match the property values specified by the claim, before and after the update. The non-reversible token type is not supported and returns an error. |
encrypt | The fields to encrypt must match the property values specified by the claim. |
decrypt | Decrypted fields must match the property values specified by the claim. |
update-encrypted | The encrypted object being updated must match the property values specified by the claim, before and after the update. |
hash-objects | Fields to be hashed must match the property values specified by the claim. |
invoke-http-call-action | The objects, tokens, and encrypted-objects being accessed by the action role must match the property values specified by the claim. |
Other API endpoints return an error if the https://app.piiano.io/prop/<prop>
claim is present in the JWT access token.
When using object-level access control, the properties enforced must be present in the object, token, or ciphertext being operated on. If the property is not present, the operation fails.
To allow a property to be optional, set the value of the claim to null
or include null
in the array of values.
JSON and BLOB data types are not supported for object-level access control.
Claims within a JWT are not encrypted and are visible in plain text. Therefore, do not include any sensitive information in these claims. JWTs are best suited for non-sensitive properties like id
, tenant_id
, owner_id
, and owner_collection
.
https://app.piiano.io/prop-claim-ref/<prop>
The https://app.piiano.io/prop-claim-ref/<prop>
claim is used to specify that the value for the property is in another claim in the JWT token. The value of the claim is the name of the claim in the JWT access token. Then, the property is being validated like the https://app.piiano.io/prop/<prop>
claim.
For example, the following claims enforce that the request is allowed only if the user_id
property is user1
in the object, token, or ciphertext being accessed. Vault looks for the value of the myapp_user_id
claim in the JWT access token:
{
"myapp_user_id": "user1",
"https://app.piiano.io/prop-claim-ref/user_id": "myapp_user_id"
}
The https://app.piiano.io/prop-claim-ref/<prop>
claim are useful for not repeating the same value in multiple claims, or when only a static custom claims are supported by the identity provider.
https://app.piiano.io/any-of
The https://app.piiano.io/any-of
claim is used to specify enforcement between multiple properties. The claim is an object, where each key is another namespace claim.
The keys of the object does not have to be prefixed by the https://app.piiano.io/
namespace, and can be used with the path part only (i.e. https://app.piiano.io/prop/<prop>
can be written as prop/<prop>
).
For example, the following claims enforce that the request is allowed only if the user_id
property is user1
OR the group_id
property is either group-1
or group-2
in the object, token, or ciphertext being accessed:
{
"https://app.piiano.io/any-of": {
"prop/user_id": "user1",
"prop/group_id": ["group-1", "group-2"]
}
}
You can think of the https://app.piiano.io/any-of
claim as an OR statement between the properties. If any of the properties are satisfied, the operation is allowed.
The https://app.piiano.io/any-of
claim can be nested within other claims. For example, the following claims enforce that the request is allowed only if the tenant_id
property is tenant1
AND either the user_id
property is user1
OR the group_id
property is either group-1
or group-2
in the object, token, or ciphertext being accessed:
{
"https://app.piiano.io/any-of": {
"prop/tenant_id": "tenant1",
"any-of": {
"prop/user_id": "user1",
"prop/group_id": ["group-1", "group-2"]
}
}
}
The nesting depth of namespace claims is up to 5 levels.
When using multiple https://app.piiano.io/any-of
claims at the same level, add an identifier to separate them. For example, https://app.piiano.io/any-of/1
, https://app.piiano.io/any-of/2
, etc.
https://app.piiano.io/all-of
The https://app.piiano.io/all-of
claim is used to specify enforcement of multiple properties. The claim is an object, where each key is another namespace claim. See any-of for more details of the structure.
You can think of the https://app.piiano.io/all-of
claim as an AND statement between the properties. If all of the properties are satisfied, the operation is allowed.
By default, if not using the https://app.piiano.io/any-of
claim, then namespace claims at the same level are considered as an AND
statement.
When using multiple https://app.piiano.io/all-of
claims at the same level, add an identifier to separate them. For example, https://app.piiano.io/all-of/1
, https://app.piiano.io/all-of/2
, etc.
Extra claims
You can configure Vault to parse extra claims alongside the claims of the JWT access token by filling the extra_claims
field of the IDP configuration with the list of claims and values in JSON string format.
For example, the following configuration enforces that the request is allowed only if the user_id
property is the value of the "myapp_user_id" claim in the JWT access token, or the group_id
property is the value of the myapp_group_ids
claim in the JWT access token:
[idps]
[idps.app1]
type = "direct-jwt"
[idps.app1.conf]
iss = ...
aud = ...
jwks_uri = ...
extra_claims = '''
{
"https://app.piiano.io/any-of": {
"prop-claim-ref/user_id": "myapp_user_id,
"prop-claim-ref/group_ids": "myapp_group_ids"
}
}
'''
If the JWT access token claims are:
{
...
"myapp_user_id": "user1",
"myapp_group_ids": ["group-1", "group-2"]
...
}
Then the request is allowed only if the user_id
property is user1
OR the group_id
property is either group-1
or group-2
in the object, token, or ciphertext being accessed.
The extra_claims
configuration is useful for enforcing property values that are already part of the JWT access token. That way, you don't need to change the JWT access token structure at all.
Learn how to configure JWT authentication
You can configure Vault to authenticate users using JWT access tokens generated by an external identity provider. This guide shows you how to configure Vault to authenticate users using JWT access tokens.
Prerequisites
You should know the following information about the JWT access token:
- The issuer (
iss
). - The audience (
aud
). - The public key of the key-pair used to sign it.
For this guide, you use jwt.io to generate a JWT access token for testing purposes. For production, you should use an identity provider such as Auth0, Azure AD, etc.
Walkthrough
For this guide, use the following values for the JWT access token:
- The issuer (
iss
) of the JWT access token ishttps://jwt.io/
- The audience (
aud
) of the JWT access token isvault1
You generate a key-pair using the following command:
openssl genrsa -out pvault_jwt_private_key.pem 2048
openssl rsa -pubout -in pvault_jwt_private_key.pem -out pvault_jwt_public_key.pem
And turn it into JWT format using a JWK Creator. Identity providers such as Auth0 provide you with a URL to a JWKS endpoint that contains the public key in JWT format.
Vault also supports the use of a JWKS endpoint to fetch the public key, using the jwks_uri
option in the IAM configuration.
- In the IAM configuration file, edit the section defining the IdPs, such as
[idps.app1]
, like this:
[idps]
[idps.app1]
type = "direct-jwt"
[idps.app1.conf]
iss = "https://jwt.io/"
aud = "vault1"
# Change the value of "keys" to the JWK key.
keys = "{\"kty\": \"RSA\",\"n\": \"o54d-ACKhVI8-sEX57zGyzpf83cISFNBT1HqY78eQP0bKzX6q5RGAtZS4FixzivS76Gv830sTb50d_qjtgCw8XWjnvPj0sTuYOT4D3wNlInziEaSVwsGQ7zf5BPAHs0sLb5skBRB_YFbcIEhe3uK35vLpz-JqRjoRUdhOJe63gWxx4kcQQRQw9760Zoywkf1YPU3S1klViGoNMelkuYP35Djk1qGM2ELhGXSlaON_1KpfvGNKyNfBZGj4SSJJcIOHcjYEQV0Y8UTUe-AWcz3GKesnulqhaKL7VHjXMlHfdy-j1HBa1PCWMHDWBBsv_OCrY-BSfE7KgTd89UTiTr0lQ\",\"e\": \"AQAB\",\"alg\": \"RS256\",\"kid\": \"abc\",\"use\": \"sig\"}"
# roles_claim = "roles" # optional, defaults to "roles". To be used if the IDP uses a different claim name for roles.
-
Apply the IAM configuration to Vault.
-
Generate a JWT access token on jwt.io. The headers section should look like this:
{
"alg": "RS256",
"typ": "JWT",
"kid": "abc"
}
The payload section should look like this:
{
"iss": "https://jwt.io/",
"aud": "vault1",
"sub": "our-jwt-user",
"exp": 1797026866, // Set an expiration time in the future.
"roles": ["VaultAdmin"]
}
- Use the JWT access token to authenticate to Vault:
pvault --authtoken <jwt-token> version
IDPs
Auth0
Basically follow the steps in the Walkthrough section, but notice that by default an access token generated by Auth0 does not contain the roles
claim. You need to add an login flow Action to your Auth0 tenant to add an equivalent claim to the access token:
exports.onExecutePostLogin = async (event, api) => {
if (event.authorization) {
api.accessToken.setCustomClaim("my-roles", event.authorization.roles);
}
}
Notice how the claim name is not roles
, but my-roles
. This is because roles
is a restricted claim on Auth0 platform. Vault's IAM configuration should be changed accordingly, by setting the roles_claim
option to my-roles
:
[idps]
[idps.app1]
type = "direct-jwt"
[idps.app1.conf]
iss = "https://<tenant-id>.<region>.auth0.com/" # Set by Auth0 and can be seen in the "Application" settings.
aud = "my-app" # Configured by your application.
roles_claim = "my-roles" # Configured by the login flow Action.
jwks_uri = "https://<tenant-id>.<region>.auth0.com/.well-known/jwks.json" # Set by Auth0 and can be seen in the "Application" settings, under "Advanced Settings" -> "Endpoints".
Google Cloud Platform
When using token authentication and authorization with Google Cloud Platform (GCP), you can configure Vault IAM to support the ID token generated by Google Cloud Platform (for example, when your backend application is deployed on Cloudrun).
See the following example configuration:
[idps]
[idps.app1]
type = "direct-jwt"
# https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
[idps.app1.conf]
jwks_uri = "https://www.googleapis.com/oauth2/v3/certs"
iss = "https://accounts.google.com"
aud = "https://my-cloud-run-service.a.run.app/"
roles_claim = "email"
[idps.app1.conf.bound_claims]
email_verified = true
[idps.app1.roles_map]
"<your-backend-service-account-email>" = "<role-in-vault>"
- Change the
aud
section to the audience that you use when generating the ID token. - Change the
roles_map
section to the email of the service account used to authenticate with Vault and the Vault role assigned to it.
You can generate an ID token using this Go code:
func GenerateIDToken(ctx context.Context) (string, error) {
// The audience should match the "aud" field in the IAM configuration.
ts, err := idtoken.NewTokenSource(ctx, "https://my-cloud-run-service.a.run.app/")
if err != nil {
return "", fmt.Errorf("idtoken.NewTokenSource: %w", err)
}
token, err := ts.Token()
if err != nil {
return "", fmt.Errorf("ts.Token: %w", err)
}
return token.AccessToken, nil
}
See Get an ID token from the metadata server to learn more about ID token generation and find code samples for more languages.
Keys configuration for symmetric keys
It's recommended to use asymmetric keys for JWT authentication (e.g. RS256 / ES256 signing algorithms), so Vault can verify the signature of a JWT with a public key without being able to generate new tokens on its own.
If you are using an identity provider that only supports symmetric keys, you can configure Vault to use a symmetric key for JWT verification and externalize the key configuration to an environment variable or a secret file for better security.
[idps]
[idps.app1]
type = "direct-jwt"
[idps.app1.conf]
keys = "{\"kty\": \"oct\",\"k\": \"${secret:APP1_JWT_HS256_KEY}\",\"alg\": \"HS256\",\"kid\": \"abc\"}"
Then, you can reference the symmetric key from a secret file, such as /etc/pvault/secrets/secrets_iam_app1_jwt_hs256_key/content
or pass it as a PVAULT_SECRETS_IAM_APP1_JWT_HS256_KEY
IAM secrets environment variable.