This is a How-to guide to show how to authenticate to Nomad via OIDC auth method using Vault as IdP. We are also utilising Nomad's New feature of PKCE for authentication. PKCE plays an important role in improvising the OIDC authentication by additional security for the authorization code provided by IdP to Client(Nomad)
Introduction
What is PKCE(Proof Key for Code Exchange) about:
The PKCE-enhanced Authorization Code Flow introduces a secret created by the calling application that can be verified by the authorization server; this secret is called the Code Verifier. Additionally, the calling app creates a transform value of the Code Verifier called the Code Challenge and sends this value over HTTPS to retrieve an Authorization Code. This way, a malicious attacker can only intercept the Authorization Code, and they cannot exchange it for a token without the Code Verifier.
PKCE is introduced to be used in Nomad v1.10.x, and below steps showcase a use case Where Nomad(Client with PKCE enabled) will be authenticated via OIDC auth method by Vault(OIDC Provider).
Expected Outcome:
After successfully completing the below steps, Nomad will be able to authenticate using Vault as an OIDC provider. Nomad will also be utilising the latest feature of PKCE enabled in OIDC based authentication method.
Pre-requisites:
- Nomad version >=1.10.x
- ACLs enabled in Nomad cluster
Procedure:
Step 1: Spin Nomad and Vault:
$ nomad agent -dev -acl-enabled -log-level trace $ vault server -dev -dev-root-token-id root -log-level trace $ export VAULT_ADDR=http://127.0.0.1:8200 $ export VAULT_TOKEN=root
Step 2: Create Vault auth method:
When Vault acts as an OIDC provider, it is the source of identity and these auth methods verify that identity.
Enable the userpass auth method at the default path and create username and password.
$ vault auth enable userpass $ vault write auth/userpass/users/end-user \ password="password" \ token_ttl="1h"
Step 3: Create Vault Identity Entity and group:
Nomad can have various IdP that are enabled on vault. Example user A logins to vault using SSO from google or user B logins using SSO from Microsoft.
So Vault clients can be mapped as entities and corresponding accounts with IdPs.
$ vault write identity/entity \ name="end-user" \ disabled=false
$ ENTITY_ID=$(vault read -field=id identity/entity/name/end-user) $ vault write identity/group \ name="engineering" \ member_entity_ids="$ENTITY_ID" $ GROUP_ID=$(vault read -field=id identity/group/name/engineering)
- The
end-user
entity is a member of theengineering
group. An entity alias maps an entity to client of an authentication method. This mapping requires the entity ID and the authentication accessor ID.
$ USERPASS_ACCESSOR=$(vault auth list -detailed -format json | jq -r '.["userpass/"].accessor') $ vault write identity/entity-alias \ name="end-user" \ canonical_id="$ENTITY_ID" \ mount_accessor="$USERPASS_ACCESSOR"
Step 4: Set up Vault OIDC client configurations
- Create an assignment named
my-assignment
that authorises theend-user
entity andengineering
group.
$ vault write identity/oidc/assignment/my-assignment \ entity_ids="${ENTITY_ID}" \ group_ids="${GROUP_ID}"
- The Vault OIDC authentication process requires an encryption key to sign and verify the JSON web tokens (JWT) that are produced by the authentication flow. The key is usable by all Vault OIDC clients as `allowed_client_ids` is set to `*`.
$ vault write identity/oidc/key/my-key \ allowed_client_ids="*" \ verification_ttl="2h" \ rotation_period="1h" \ algorithm="RS256"
- On Vault, Create an OIDC client named
nomad
.
$ vault write identity/oidc/client/nomad \ redirect_uris="http://localhost:4649/oidc/callback,http://localhost:4200/ui/settings/tokens" \ assignments="my-assignment" \ key="my-key" \ id_token_ttl="30m" \ access_token_ttl="1h"
The redirect_uris
flag describes the callback URL for the client, the value is the address of a Nomad service running on its default port. The assignments
flag limits access to only the entities and groups defined in my-assignment
. The id_token_ttl
flag sets the expiration on the ID token to 30 minutes. The access_token_ttl
flag sets the expiration of the access token to 1 hour.
- Create an environment variable named CLIENT_ID to store the
client_id
field of thenomad
client.
$ CLIENT_ID=$(vault read -field=client_id identity/oidc/client/nomad)
Step 5: Create Vault OIDC Provider:
$ USER_SCOPE_TEMPLATE='{"username": {{identity.entity.name}}}' $ vault write identity/oidc/scope/user \ description="The user scope provides claims using Vault identity entity metadata" \ template="$(echo ${USER_SCOPE_TEMPLATE} | base64)" $ GROUPS_SCOPE_TEMPLATE='{"groups": {{identity.entity.groups.names}}}' $ vault write identity/oidc/scope/groups \ description="The groups scope provides the groups claim using Vault group membership" \ template="$(echo ${GROUPS_SCOPE_TEMPLATE} | base64)"
- Create a Vault OIDC provider named
my-provider
and provide it a list of client IDs and scopes. The provider grants access to thenomad
client.
$ vault write identity/oidc/provider/my-provider \ allowed_client_ids="${CLIENT_ID}" \ scopes_supported="groups"
Display Vault OIDC endpoint:
$ curl -s $VAULT_ADDR/v1/identity/oidc/provider/my-provider/.well-known/openid-configuration | jq
- Show Vault OIDC public keys.
$ curl -s $VAULT_ADDR/v1/identity/oidc/provider/my-provider/.well-known/keys | jq
Step 6: Nomad Configurations for OIDC Auth-method creation:
- Grab the issuer from previous commands:
$ ISSUER=$(curl -s $VAULT_ADDR/v1/identity/oidc/provider/my-provider/.well-known/openid-configuration | jq -r .issuer)
- Get client secret , and bootstrap Nomad with ACLs, and configure Nomad root token in the shell.
$ CLIENT_SECRET=$(vault read -field=client_secret identity/oidc/client/nomad) $ NOMAD_TOKEN=$(nomad acl bootstrap -json | jq -r .SecretID) $ export NOMAD_TOKEN
- Create a Nomad policy that allows read access to the
default
namespace.
$ nomad acl policy apply engineering-read - <<'EOF' \ namespace "default" { policy = "read" } node { policy = "read" } EOF
- Create a corresponding role that contains the policies to assign to engineers.
$ nomad acl role create \ -name=engineering-read \ -policy=engineering-read
Step 7: Create Nomad Auth method and Binding rules:
- Now, Create the OIDC Nomad Auth Method based on shown OIDC configuration file, followed by creating binding-rules for auth method. Notice the PKCE configuration for Nomad
OIDCEnablePKCE: true
in order for Nomad to be PKCE enabled to secure Authorization code provided by Vault.
Note: Please substitute the values in below OIDC config file placeholders(< >
) manually or by using envsubst
command to export all shell variable into config.
{ "OIDCDiscoveryURL": "<ISSUER>", "OIDCClientID": "<CLIENT_ID>", "OIDCEnablePKCE": true, "VerboseLogging": true, "OIDCClientSecret": "<CLIENT_SECRET>", "BoundAudiences": ["<CLIENT_ID>"], "OIDCScopes": ["groups"], "AllowedRedirectURIs": [ "http://localhost:4649/oidc/callback", "http://localhost:4646/ui/settings/tokens" ], "ListClaimMappings": { "groups": "roles" } }
$ nomad acl auth-method create \ -default=true \ -name=vault \ -token-locality=global \ -max-token-ttl="10m" \ -type=oidc \ -config @acl_auth_method.json $ nomad acl binding-rule create \ -auth-method=vault \ -bind-type=role \ -bind-name="engineering-read" \ -selector="engineering in list.roles"
Auth method named vault
has been created using OIDC config file acl_auth_method.json
. Also in subsequent command, an acl binding-rule is created as well to bind the role engineering-read
with the auth method vault
to apply relevant privileges to user upon authentication.
- Validate the Auth method, and make sure PKCE is enabled:
OIDC Enable PKCE = true
:
$ nomad acl auth-method info vault Name = vault Type = OIDC Locality = global Max Token TTL = 10m0s Token Name Format = ${auth_method_type}-${auth_method_name} Default = true Create Index = 24 Modify Index = 24 Auth Method Config JWT Validation Public Keys = <none> JWKS URL = <none> OIDC Discovery URL = http://127.0.0.1:8200/v1/identity/oidc/provider/my-provider OIDC Client ID = OzIyTVkCiza03G1hZAPrNH02bkF0DKCe OIDC Client Secret = redacted OIDC Enable PKCE = true OIDC Disable UserInfo = false OIDC Scopes = groups Bound audiences = OzIyTVkCiza03G1hZAPrNH02bkF0DKCe Bound issuer = <none> Allowed redirects URIs = http://localhost:4649/oidc/callback,http://localhost:4646/ui/settings/tokens Discovery CA pem = <none> JWKS CA cert = <none> Signing algorithms = <none> Expiration Leeway = 0s NotBefore Leeway = 0s ClockSkew Leeway = 0s Claim mappings = <none> List claim mappings = {groups: roles}
- Now, Test the Login:
$ nomad login -method=vault Password: Successfully logged in via OIDC and vault Accessor ID = 6ef8064c-5a6b-cb1f-b978-8d9844704742 Secret ID = 6a6948c7-7204-27b3-d310-733794f48f6a Name = OIDC-vault Type = client Global = true Create Time = 2025-06-11 23:23:08.365008 +0000 UTC Expiry Time = 2025-06-11 23:33:08.365008 +0000 UTC Create Index = 24 Modify Index = 24 Policies = [] Roles ID Name 5bdf3055-9568-6447-1d00-05a0f2b6fa57 engineering-read
Outcome:
In the above setup, we were able to configure Nomad authentication(using PKCE) using Vault as an OIDC provider. The PKCE mechanism adds additional security for Client's Authorization code and mitigates the chances of Authorization code compromise.
Additional Information:
https://developer.hashicorp.com/nomad/docs/secure/authentication/sso-vault
https://developer.hashicorp.com/vault/docs/concepts/oidc-provider
https://developer.hashicorp.com/nomad/docs/secure/authentication/sso-pkce-jwt