> For the complete documentation index, see [llms.txt](https://docs.empe.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.empe.io/getting-started/tutorial/authentication.md).

# Authentication

**What We Are Doing:**

* Obtaining an OIDC access token — a signed JWT presented as `Authorization: Bearer <token>`.
* Making sure that token carries the `admin` role (in `realm_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=true` shortcut 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:

1. Reads the JWT from the `Authorization: Bearer <token>` header.
2. Fetches the signing keys from the JWKS endpoint configured in `OIDC_JWKS_URL` and verifies the token's RS256 signature and expiry.
3. For agent/DID, issuer-record, verifier-record and holder/wallet management (the `/agent/*` and `/holders/:holderId/*` routes), checks that the token grants the `admin` role.

Two kinds of endpoint sit behind this:

* **Token + `admin` role** — the agent endpoints (`/agent/*`: DIDs, issuer records, verifier records, holders) and the cloud-wallet endpoints (`/holders/:holderId/*`). The `admin` role can come from the token's `realm_access.roles` array or from any client's `resource_access.<client>.roles` array; this tutorial uses `realm_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:

```
# The JWKS endpoint of your identity provider (required unless AUTH_DISABLED=true)
OIDC_JWKS_URL=https://auth.example.com/realms/empe/protocol/openid-connect/certs
```

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](/develop/intro/authenticating-to-your-deployment.md)). 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:

```json
{
  "sub": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
  "realm_access": {
    "roles": ["admin"]
  },
  "exp": 1750594800,
  "iss": "https://auth.example.com/realms/empe"
}
```

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:

```bash
ISSUER_BASE_URL=https://your-issuer.evdi.app
VERIFIER_BASE_URL=https://your-deployment.evdi.app
TOKEN=<your OIDC access token>   # a JWT carrying the admin role; stored per service as ISSUER_ACCESS_TOKEN / VERIFIER_ACCESS_TOKEN in the deploy pages
```

Then attach it to every protected request with the `Authorization` header:

```bash
curl "$ISSUER_BASE_URL/schemas" \
  -H "Authorization: Bearer $TOKEN"
```

A successful response confirms the token was accepted — on a fresh deployment, the schema list is simply empty:

```json
[]
```

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 `KYCCredential@1.0: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:

```json
{
  "statusCode": 401,
  "message": "Unauthorized",
  "path": "/agent/dids",
  "timestamp": "2026-06-22T12:00:00.000Z"
}
```

## 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:

```bash
AUTH_DISABLED=true npm run start:dev
```

With auth disabled, the same request needs no token:

```bash
curl "http://localhost:3000/schemas"
```

A few things to keep in mind:

* **`AUTH_DISABLED=true` is for local development only.** It disables all token validation and role checks, so never enable it on a deployed or shared environment.
* When `AUTH_DISABLED` is not set to `true`, `OIDC_JWKS_URL` is 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=true` exists 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.

<figure><img src="/files/jpo9cAHKGkCnyBvQCD3V" alt="Authorize button in the /api-docs Swagger UI"><figcaption><p>The <strong>Authorize</strong> button (top right) in the deployment's <code>/api-docs</code> Swagger UI</p></figcaption></figure>

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.empe.io/getting-started/tutorial/authentication.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
