Authentication
What We Are Doing:
Obtaining an OIDC access token — a signed JWT presented as
Authorization: Bearer <token>.Making sure that token carries the
adminrole (inrealm_access.roles) so it can reach every endpoint in this tutorial.Pointing the API at the identity provider that signs those tokens via
OIDC_JWKS_URL.Using the
AUTH_DISABLED=trueshortcut for fast local development.
Why: Every protected endpoint on the Issuer and Verifier APIs is guarded by a standard OIDC JWT. Your client authenticates with exactly one credential: a valid OIDC Bearer token, sent in the Authorization header on every protected request. The service validates the token's signature against the identity provider's public keys, and for the agent/DID and wallet-management endpoints it additionally checks for the admin role. Once you have a token, every later step in this tutorial reuses the exact same header: Authorization: Bearer <token>.
How authentication works
When a request arrives, the API:
Reads the JWT from the
Authorization: Bearer <token>header.Fetches the signing keys from the JWKS endpoint configured in
OIDC_JWKS_URLand verifies the token's RS256 signature and expiry.For agent/DID, issuer-record, verifier-record and holder/wallet management (the
/agent/*and/holders/:holderId/*routes), checks that the token grants theadminrole.
Two kinds of endpoint sit behind this:
Token +
adminrole — the agent endpoints (/agent/*: DIDs, issuer records, verifier records, holders) and the cloud-wallet endpoints (/holders/:holderId/*). Theadminrole can come from the token'srealm_access.rolesarray or from any client'sresource_access.<client>.rolesarray; this tutorial usesrealm_access.Token only (no specific role) — the Issuer endpoints (schemas, credential offers, issuance sessions, direct signing) and the Verifier endpoints (authorization requests, verification sessions,
/credentials/verify). These require a valid Bearer token but do not check for a particular role.
A handful of endpoints are fully public and need no token at all: the root and /version endpoints, and the served DID documents (/:uuid/did.json, also served at /:uuid/.well-known/did.json).
To keep things simple, this tutorial uses one token that carries the admin role, so the same header works for every request.
Where the token comes from
The access token is issued by an OIDC identity provider (for example, Keycloak) — the API never mints tokens itself, it only validates them. The provider exposes a JWKS (JSON Web Key Set) document containing the public keys used to verify token signatures, and the API is told where to find it through the OIDC_JWKS_URL environment variable:
When you provision a deployment through the portal, this wiring is already done for you: you obtain a ready-to-use admin token from the deployment's Keycloak (see Authenticating to Your Deployment). For local development you run the identity provider yourself, or skip validation entirely with AUTH_DISABLED=true (see below).
The token's shape
The token is an ordinary OIDC access token (a signed JWT). What the API looks at are the role claims — an admin role in realm_access.roles (or in any resource_access.<client>.roles array) is what unlocks the agent/DID and wallet routes. A decoded payload looks roughly like this:
You never construct this yourself — the identity provider builds and signs it. You only need to make sure your provider assigns the admin role to the account whose token you use.
Sending the token
Set the token (and your base URLs) as environment variables so the rest of the tutorial can reuse them:
Then attach it to every protected request with the Authorization header:
A successful response confirms the token was accepted — on a fresh deployment, the schema list is simply empty:
The same Authorization: Bearer $TOKEN header applies to every administrative endpoint you'll use later — uploading and assigning schemas, creating credential offers (for example, referencing the [email protected]:sd-jwt configuration), signing credentials directly, creating authorization requests, and managing holders.
If a token is missing, expired, or has an invalid signature, the request is rejected with 401. If the token is valid but lacks the admin role on an agent or wallet route, the request is rejected with 403. The error filter returns a consistent body:
Local development: AUTH_DISABLED=true
While building and testing locally you usually don't want to stand up an identity provider just to call the API. Start the service with AUTH_DISABLED=true and every request is treated as an authenticated admin, so you can omit the Authorization header entirely:
With auth disabled, the same request needs no token:
A few things to keep in mind:
AUTH_DISABLED=trueis for local development only. It disables all token validation and role checks, so never enable it on a deployed or shared environment.When
AUTH_DISABLEDis not set totrue,OIDC_JWKS_URLis required — the service needs to know where to fetch the signing keys.Tokens must travel over HTTPS in any non-local environment so the credential is never sent in clear text. (
ALLOW_INSECURE_HTTP=trueexists for local HTTP only.)
Trying it in the interactive API reference
Each deployment serves a live API reference at /api-docs (for example, https://your-issuer.evdi.app/api-docs) where you can browse every endpoint, paste your Bearer token once, and try requests directly from the browser.

/api-docs Swagger UIWith a valid admin token in hand (or AUTH_DISABLED=true running locally), you're ready to move on. Next, we'll create the DIDs our Issuer and Verifier are anchored to and register both identities.
Last updated