Skip to content

OIDC Provider

VeraID can act as an OpenID Connect (OIDC) identity provider, enabling your applications to authenticate users and obtain identity tokens directly from VeraID. This eliminates the need for a separate IdP for human users while keeping your NHI governance in the same platform.

Discovery endpoint

Every organization gets a dedicated OIDC discovery URL:

https://app.veraid.io/api/oidc/{orgId}/.well-known/openid-configuration

Replace {orgId} with your organization ID (a UUID). The dashboard does not display it yet; ask your VeraID contact for it. The discovery document advertises every endpoint, grant type, signing algorithm and scope the provider supports, and is the source of truth for all of them.


Registering an OIDC client

Clients are registered in the dashboard by an Admin or Owner:

  1. Navigate to Settings > OIDC Clients.
  2. Click Register Client.
  3. Fill in the fields:
    • Application Name — A human-readable name (e.g., “Production Dashboard”).
    • Client Type — Confidential for server-side applications that can keep a secret, Public for SPAs and native or mobile apps that cannot.
    • Application Type — web or native.
    • Redirect URIs — One or more allowed callback URLs.
    • Scopes — The scopes this client may request.
    • Require user consent — Clear it for your organization’s own applications (see Consent).
  4. Click Create Client. A confidential client’s secret is displayed once. Store it in a secrets manager immediately.

The client ID has the form veraid_…. The client type decides how the client authenticates at the token endpoint (see below) and cannot be changed after registration.

The dashboard calls POST /api/v1/oidc-clients with your signed-in session. That API is not available to API keys.

New clients require user consent by default, and VeraID does not have a consent screen yet. A sign-in to a client that requires consent ends with the standard OAuth error consent_required, returned to the client’s redirect URI, so such a client cannot sign anyone in.

For your organization’s own applications, an Admin or Owner clears Require user consent when registering the client, or later in its settings in Settings > OIDC Clients. Users are then signed in to that application without being asked, and it receives the scopes it requests (within those the client is allowed). This holds for native applications too, and when the request carries prompt=consent (which offline_access requires; see Refresh tokens): the consent step is answered for the application without showing the user anything. Applications cannot turn consent off for themselves.


Supported grant types

Grant TypeUse CaseRequires User Interaction
authorization_codeWeb and native apps that authenticate end users via browser redirectYes
refresh_tokenObtain new access tokens without re-authenticating the userNo
client_credentialsMachine-to-machine (M2M) service calls with no user contextNo
device_codeCLIs, TVs and other devices without a browser of their ownYes, in a browser elsewhere
passwordMac sign-in agents checking a user’s password at the login window (Mac sign-in)No

Clients registered in the dashboard get the authorization code grant, and the password grant when Computer sign-in: allow password verification is ticked. The other grant types cannot be chosen in the dashboard yet; they are set through the client API (PATCH /api/v1/oidc-clients/{id} with a signed-in Admin or Owner session, as the dashboard uses).

Only a client with the authorization_code grant can sign users in through the browser. A client without it (machine-to-machine, device-only) is refused at the authorization endpoint, whatever response types it holds; take the grant away from a client and its browser sign-ins stop.

Authorization Code flow with PKCE

PKCE (S256) is required for every authorization code flow, for confidential and public clients alike. plain is not accepted.

  1. Redirect the user to the authorization endpoint:
https://app.veraid.io/api/oidc/{orgId}/auth?
response_type=code&
client_id=veraid_AbC123…&
redirect_uri=https://dashboard.example.com/callback&
scope=openid profile email&
state=random_state_value&
nonce=random_nonce_value&
code_challenge=BASE64URL_SHA256_OF_VERIFIER&
code_challenge_method=S256
  1. The user signs in to VeraID, and VeraID redirects back to the redirect URI with an authorization code (valid for 60 seconds).
  2. Exchange the code for tokens at the token endpoint. A confidential client authenticates with HTTP Basic (here CLIENT_ID and CLIENT_SECRET are environment variables holding the client’s credentials):
Terminal window
curl -X POST https://app.veraid.io/api/oidc/{orgId}/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://dashboard.example.com/callback" \
-d "code_verifier=ORIGINAL_VERIFIER"

A public client sends client_id in the body instead, with no secret:

Terminal window
curl -X POST https://app.veraid.io/api/oidc/{orgId}/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=veraid_AbC123…" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://app.example.com/callback" \
-d "code_verifier=ORIGINAL_VERIFIER"

Client Credentials flow (M2M)

For service-to-service authentication where no user is involved (confidential clients with the client_credentials grant):

Terminal window
curl -X POST https://app.veraid.io/api/oidc/{orgId}/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"

Refresh tokens

A refresh token is issued only to a client that has the refresh_token grant, so it can redeem it:

  • A confidential client with the grant receives one with its tokens, without asking. It must authenticate (HTTP Basic) to use it.
  • A public client with the grant receives one only when it asks for the offline_access scope together with prompt=consent (the provider ignores offline_access otherwise), and only if its consent is turned off (see Consent): a client that requires consent cannot sign anyone in, so it never gets that far.
  • A client without the grant never receives one. The dashboard creates clients with the authorization code grant only; the other grants are set through the client API.

By default each use of a refresh token returns a new one and invalidates the old; presenting an already-used refresh token revokes the whole token family.

Device authorization flow

For a device that cannot show a sign-in page itself (a CLI, a TV app), the user approves it in a browser on another device (RFC 8628). The client needs the device_code grant (set through the client API, as above) and consent turned off (see Consent): send "requireConsent": false with the grant. Clients require consent by default, and the API refuses a device_code client that requires it with a 400, since none of its devices could ever be approved. Registration still asks for a redirect URI, which a device-only client never uses. A device app is usually a public client: it sends client_id in the body instead of authenticating, as shown below.

  1. The device asks for a code:
Terminal window
curl -X POST https://app.veraid.io/api/oidc/{orgId}/device/auth \
-d "client_id=veraid_AbC123…" \
-d "scope=openid email"

A confidential client authenticates with HTTP Basic instead (-u "$CLIENT_ID:$CLIENT_SECRET", no client_id in the body), here and when polling.

The answer carries a device_code, a user_code such as BCDF-GHJK, a verification_uri and a verification_uri_complete. Show the user the user_code and the verification_uri as given; do not build the address yourself.

  1. The user opens the address, enters the code, checks that the application and code shown are the ones on the device, and approves. Approving always asks the user to sign in to VeraID, even in a browser that is already signed in, so your organization’s Conditional Access policies (including those targeting this application) and MFA apply. A user of another organization cannot approve: a code is valid only at its own organization’s address. The code lasts 10 minutes.

  2. Meanwhile the device polls the token endpoint every interval seconds:

Terminal window
curl -X POST https://app.veraid.io/api/oidc/{orgId}/token \
-d "client_id=veraid_AbC123…" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "device_code=DEVICE_CODE"

Until the user approves it answers authorization_pending; then it returns tokens, once. access_denied means the user cancelled; consent_required means the client still requires consent; any other error means the device should start again.


Token endpoint authentication

Each client is registered with exactly one authentication method, set by its client type, and the token endpoint accepts only that method:

Client TypeMethodHow the client authenticates
Confidentialclient_secret_basicClient ID and secret in the Authorization: Basic header
PublicnoneNo secret; client_id in the request body, and PKCE

Sending the secret in the request body (client_secret_post) is refused for a confidential client, because it is not the client’s registered method. JWT-based client authentication (client_secret_jwt, private_key_jwt) is not available: secrets are stored only as hashes, and clients cannot register keys.

Failed authentication is rate-limited

Enforcement is being rolled out and is not yet active everywhere. Once it is, a caller whose client authentication fails 10 times within 5 minutes (counted per organization and caller address, across the token, introspection, revocation, pushed-authorization-request and device-authorization endpoints) receives 429 with error=temporarily_unavailable and a Retry-After header until the window ends. The count is by address, never by client, so someone who knows your client ID cannot lock your application out. If a deployment shares one address between many callers (NAT), a misconfigured secret in one of them can delay the others; fix the secret rather than retrying.


Scopes and claims

ScopeClaims
openidsub (subject identifier)
profilename, given_name, family_name, preferred_username, updated_at
emailemail, email_verified
phonephone_number, phone_number_verified
offline_accessNo claims; asks for a refresh token (needs prompt=consent, see above)

The ID token carries the claims of every scope the client was granted, also when an access token is issued alongside it (the authorization code flow). The same claims are available from the UserInfo endpoint (/me) with the access token. Until 20 October 2026 the ID token carried only sub in that case; Mac sign-in agents name the local account from the ID token, so it now carries the rest.

ID token example

{
"iss": "https://app.veraid.io/api/oidc/3f1e9c2a-0b4d-4e5f-8a6b-7c8d9e0f1a2b",
"sub": "user_id",
"aud": "veraid_AbC123…",
"email": "ada@example.com",
"email_verified": true,
"exp": 1711461600,
"iat": 1711458000,
"nonce": "random_nonce_value"
}

Token lifetimes

TokenLifetimeSet per client
Access token1 hour by default5 minutes to 24 hours
Refresh token7 days for a new client1 hour to 30 days
ID token1 hourNo
Authorization code60 secondsNo

An Admin or Owner sets a client’s access and refresh token lifetimes in its settings in Settings > OIDC Clients; a change applies to tokens issued afterwards. A client cannot set them for itself. The access token lifetime also applies to tokens a client gets for itself with the client_credentials grant (10 minutes when none is set).

How long a token is actually usable depends on how it was issued:

  • With offline_access: the full lifetime set on the client.
  • Without it (a confidential client’s default): the access token and the refresh token also end when the user’s VeraID session ends, which is 8 hours by default, whatever lifetime is set.

A refresh token’s lifetime is counted from the sign-in that issued it: refreshing returns a new token but does not extend it past that point. To carry on beyond it, the user signs in again.


Endpoints

All endpoints are under https://app.veraid.io/api/oidc/{orgId} and are listed in the discovery document:

EndpointPath
Discovery/.well-known/openid-configuration
JSON Web Key Set/jwks
Authorization/auth
Token/token
UserInfo/me
Token introspection/token/introspection
Token revocation/token/revocation
End session (logout)/session/end
Device authorization/device/auth
Device verification (the page the user opens)/device
Pushed authorization requests/request

Signing keys

ID tokens are signed with RS256 using a per-organization RSA key, published at the JWKS endpoint. Keys are rotated manually by an Admin or Owner in Settings > Signing Keys; after a rotation the previous key stays in the key set for 24 hours, so relying parties should refresh their cached JWKS when they see an unknown kid.


Next steps

SAML 2.0 IdP

Set up VeraID as a SAML identity provider for enterprise applications. SAML Setup Guide

Authentication Methods

Configure password, passkeys, magic links, and social login options. Authentication Methods

Federation

Connect external identity providers for enterprise SSO. Federation Guide