IBM Support

How-to: Authenticate to Nomad using Vault OIDC with PKCE enabled

Troubleshooting


Problem

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).

Environment

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).

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. 

Environment Details :

  • Nomad version >=1.10.x
  • ACLs enabled in Nomad cluster

Resolving The Problem

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 the engineering 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 the end-user entity and engineering 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 the nomad 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 the nomad 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.

Document Location

Worldwide

[{"Type":"MASTER","Line of Business":{"code":"LOB77","label":"Automation Platform"},"Business Unit":{"code":"BU048","label":"IBM Software"},"Product":{"code":"SSNSVB","label":"IBM Nomad Self-Managed"},"ARM Category":[{"code":"a8mgJ0000000Dv1QAE","label":"Nomad-\u003ESecurity \u0026 Identity"}],"ARM Case Number":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"1.10.0;1.10.1;1.11.0;1.11.1;1.8.0;1.8.1"}]

Historical Number

43273671173267

Document Information

Modified date:
12 May 2026

UID

ibm17263691