SAML 2.0 IdP
Set up VeraID as a SAML identity provider for enterprise applications. SAML Setup Guide
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.
Every organization gets a dedicated OIDC discovery URL:
https://app.veraid.io/api/oidc/{orgId}/.well-known/openid-configurationReplace {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.
Clients are registered in the dashboard by an Admin or Owner:
web or native.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.
| Grant Type | Use Case | Requires User Interaction |
|---|---|---|
authorization_code | Web and native apps that authenticate end users via browser redirect | Yes |
refresh_token | Obtain new access tokens without re-authenticating the user | No |
client_credentials | Machine-to-machine (M2M) service calls with no user context | No |
device_code | CLIs, TVs and other devices without a browser of their own | Yes, in a browser elsewhere |
password | Mac 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.
PKCE (S256) is required for every authorization code flow, for confidential and public clients alike. plain is not accepted.
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=S256CLIENT_ID and CLIENT_SECRET are environment variables holding the client’s credentials):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:
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"For service-to-service authentication where no user is involved (confidential clients with the client_credentials grant):
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"A refresh token is issued only to a client that has the refresh_token grant, so it can redeem it:
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.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.
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.
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.
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.
Meanwhile the device polls the token endpoint every interval seconds:
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.
Each client is registered with exactly one authentication method, set by its client type, and the token endpoint accepts only that method:
| Client Type | Method | How the client authenticates |
|---|---|---|
| Confidential | client_secret_basic | Client ID and secret in the Authorization: Basic header |
| Public | none | No 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.
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.
| Scope | Claims |
|---|---|
openid | sub (subject identifier) |
profile | name, given_name, family_name, preferred_username, updated_at |
email | email, email_verified |
phone | phone_number, phone_number_verified |
offline_access | No 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.
{ "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 | Lifetime | Set per client |
|---|---|---|
| Access token | 1 hour by default | 5 minutes to 24 hours |
| Refresh token | 7 days for a new client | 1 hour to 30 days |
| ID token | 1 hour | No |
| Authorization code | 60 seconds | No |
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:
offline_access: the full lifetime set on the client.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.
All endpoints are under https://app.veraid.io/api/oidc/{orgId} and are listed in the discovery document:
| Endpoint | Path |
|---|---|
| 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 |
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.
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