Skip to content

Using OIDC for user provisioning and authorization

Agora can integrate its user store with a corporate directory over OIDC.

The local user database can be disabled (optional — you can keep it) and becomes fully driven by the external system (usually the central corporate directory, below IdP — identity provider). User rights are configured externally; user create, lock, and delete happen externally.

Important: Agora does not call the IdP on every API request — it validates the JWT (signature via JWKS or a local key) and reads access rights for that request from the token.

JIT provisioning into the database happens on the first successful request with a valid IdP token only if the token contains a mappable role (see OIDC_ROLE_CLAIM / OIDC_ROLE_MAP). There is no default fallback role; without a mappable role the account is not created and the request is denied (403). If local login stays enabled, built-in accounts and JWT issuance via POST /login continue to work alongside the IdP (hybrid mode).

All communication with the IdP uses HTTP(S). LDAP and similar are handled by the external system (the IdP itself).

OIDC is not the only mode: by default Agora uses a built-in user database and local user management, issuing its own JWTs.

OIDC setup can be complex, with many knobs and variation across corporate deployments.

The rest of this document walks through:

  1. Running OIDC with Authentik locally
  2. Which JWT fields we use
  3. Agora OIDC configuration options
  4. A glossary of terms used here

1. Running OIDC with Authentik

1.1. Deploying Authentik

Full Authentik installation (Docker Compose, Kubernetes, LDAP integration, etc.) is covered in the official Authentik documentation.

Here we run Agora and Authentik on a server in the LAN with Docker Compose to illustrate OIDC configuration. Moving to production does not change the approach.

Start Authentik locally:

echo "PG_PASS=$(openssl rand -base64 36 | tr -d '\n')" >> .authentik-env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')" >> .authentik-env
curl -o authentik-compose.yml https://docs.goauthentik.io/compose.yml
docker compose -f authentik-compose.yml --env-file .authentik-env -p authentik up -d

If something goes wrong, you can stop and fully remove Authentik:

docker compose -f authentik-compose.yml --env-file .authentik-env -p authentik down -v

Open http://authentik.local:9000/if/flow/initial-setup/ — here authentik.local is the host where you run the IdP (often localhost, but the browser must reach it). Create an Authentik administrator in that UI. Values here do not matter much; this is a temporary install.

1.2. Configure Authentik

  1. In Authentik create an Application named Agora (the slug is filled automatically).

authentik-1-new-app

authentik-2-agora

  1. Create an OAuth2/OIDC Provider (pick it from the list).

authentik-3-provider

  1. Choose Authorization flow implicit (default-provider-authorization-implicit-consent).

authentik-4-flow

  1. On that page set Client ID to agora-admin-api for convenience. Copy Client Secret from the page; you will put them in OIDC_CLIENT_ID and OIDC_CLIENT_SECRET (see Agora settings below).

In Advanced protocol settings add offline_access to allowed Scopes.

authentik-4-provider-scopes

  1. On the next page skip adding groups because none exist yet.
  2. On the last page choose Submit — the application is created.

authentik-5-apps

  1. Open the Groups section.

authentik-10-no-groups

  1. Create two groups, e.g. "Content managers" and "Security".

authentik-11-new-group

  1. You will see a short list of groups.

authentik-12-groups

  1. Open Users to create a test user.

authentik-20-no-users

  1. Create a user, e.g. "Event Manager 1".

authentik-21-new-user

  1. User created.

authentik-22-user-created

  1. Open the user card.

authentik-23-user-card

  1. Open the "Groups" tab.

authentik-24-user-groups

  1. Add a group.

authentik-24-new-group

  1. Pick the existing "Content managers" group.

authentik-26-group-selected

  1. Save and return to the user’s Groups tab.

authentik-27-group-added

  1. Finally set the user’s password.

authentik-28-password

1.2. Configure Agora

Set these environment variables so Agora can use OIDC with the IdP:

  • OIDC_AUDIENCE=agora-admin-api — expected aud claim in the IdP JWT
  • OIDC_CLIENT_ID=agora-admin-api — effectively the application login registered with the IdP
  • OIDC_CLIENT_SECRET=oCSarud3XmFrYyjVl....ko1VW8n2O3SJLHfwCoTiy — the application secret from the IdP
  • OIDC_SCOPES="openid profile offline_access" — optional; these are Agora defaults. offline_access allows refresh tokens
  • OIDC_ISSUER=http://authentik.local:9000/application/o/agora/ — your server URL, e.g. the Authentik from above. The agora segment is Authentik’s convention: the application slug is part of the issuer path
  • OIDC_TITLE=Authentik — label shown to users; OIDC stays off without this option
  • OIDC_ROLE_CLAIM=groups — optional; which JWT field carries group membership
  • OIDC_ROLE_MAP='{"authentik Admins":"administrator","Content managers":"content_manager","Sysops":"monitoring"}' — required mapping from IdP groups to Agora roles (see role list below)
  • LOCAL_LOGIN_ENABLED=false — optional; disable username/password and use only the IdP

1.3. End-user steps

  1. On the login screen the user sees local login and OIDC.

agora-1-login

  1. They choose IdP login and land in Authentik.

agora-2-authentik

  1. They enter login (here "Event Manager 1") and the password screen.

agora-3-password

  1. After success they return to Agora and are logged into the app.

agora-4-success

The user is created in Agora.

2. Configuration details

2.1. IdP roles and mapping to Agora

Practical steps for admins mapping IdP → Agora roles.

Agora reads roles from the access token:

  • claim name is set by OIDC_ROLE_CLAIM;
  • string-to-role mapping is set by OIDC_ROLE_MAP.

Allowed Agora roles:

  • administrator;
  • content_manager;
  • monitoring;
  • security.

If a new user matches nothing in OIDC_ROLE_MAP, login ends with 403 and no JIT account is created.

If the account already exists and the new token has no mappable role, existing Account.role is unchanged.

Step-by-step

  1. Decide which claim carries roles.

Often groups (string array), sometimes a dedicated string claim.

  1. Configure the IdP so that claim is present in the access token.

In Authentik you typically use Scope/Property Mapping on the OAuth2/OpenID Provider; defaults often already expose groups.

  1. Keep claim values stable and easy to map.

Examples: agora-administrator, agora-content-manager, or human-readable names like Content managers.

  1. Set Agora variables.
OIDC_ROLE_CLAIM=groups
OIDC_ROLE_MAP='{"agora-administrator":"administrator","agora-content-manager":"content_manager","agora-monitoring":"monitoring","agora-security":"security"}'
  1. Restart Agora, log in via IdP, verify.

Verification and debugging

Agora logs include the decoded token:

{"level":"info","token_endpoint":"http://localhost:9000/application/o/token/","has_refresh_token":true,"expires_in":300,"access_token_payload":"{\"iss\":\"http://localhost:9000/application/o/agora/\",\"sub\":\"4b34f838bad570b486a2168b5353666fc19a77a230f9df03aff8c1bf7c1baf77\",\"aud\":\"agora-admin-api\",\"exp\":1775037446,\"iat\":1775037146,\"auth_time\":1775037103,\"acr\":\"goauthentik.io/providers/oauth2/default\",\"amr\":[\"pwd\"],\"sid\":\"1799899e3648369f24a47c987310fd96a5c5678dce14bbdcf61ce67c4e61b430\",\"jti\":\"oUgCDmUr9jtxiBn2zN4N0ExsZV4rptZ8eUuajmOy\",\"name\":\"\",\"given_name\":\"\",\"preferred_username\":\"Event Manager 1\",\"nickname\":\"Event Manager 1\",\"groups\":[\"Content managers\"],\"azp\":\"agora-admin-api\",\"uid\":\"yfgs9bVO0Lg2Okc3c0HdDjN5OxxGG1SlHen4b1l4\",\"scope\":\"openid profile offline_access\"}","time":1775037146,"message":"oidc: token response received"}
{"level":"info","jwks_url":"http://localhost:9000/application/o/agora/jwks/","keys":1,"ttl":3600000,"time":1775037146,"message":"oidc: jwks cache refreshed"}
{"level":"info","issuer":"http://localhost:9000/application/o/agora/","subject":"4b34f838bad570b486a2168b5353666fc19a77a230f9df03aff8c1bf7c1baf77","jti":"oUgCDmUr9jtxiBn2zN4N0ExsZV4rptZ8eUuajmOy","exp":1775037446,"time":1775037146,"message":"oidc: access token verified"}

Check what arrives in groups.

Backend logs:

  • mapping issues: oidc login: no mappable role for JIT user;

  • success: POST /login/oidc and account create/login.

For shell configs keep OIDC_ROLE_MAP as one quoted JSON string (as above) so source .env does not break it.

1.6. Sessions in Agora and revocation

Agora follows a BFF (Backend for Frontend) pattern: the browser does not store or send the IdP JWT to the Agora API. The browser uses Agora’s opaque session token; the backend holds IdP access/refresh tokens.

This matches IETF guidance for browser apps: OAuth 2.0 for Browser-Based Applications (BFF).

Agora session_id for OIDC login is initialized from the first access token’s jti and stays stable for that local session.

  • GET /sessions, GET /sessions/{id}{id} is jti.

  • session_logout — ends session by jti.

OIDC logout closes the local Agora session. If the IdP exposes revocation_endpoint, the backend also revokes the refresh token (best effort).

After session close, Agora returns 401 for that session token.

1.7. Verification after IdP linking

  1. OIDC_ISSUER, OIDC_AUDIENCE, and JWKS match the real access token.

  2. OIDC login in the UI succeeds.

  3. JIT: new sub plus mappable role → account in MongoDB with correct role; without mappable role → 403, no account.

  4. Session revocation by jti.

2. Access token fields used by the Agora backend

Below are IdP access token fields the Agora backend validates and uses in OIDC flows. The browser does not send the IdP JWT to Agora APIs.

Claim / aspect Purpose for Agora
iss Must match OIDC_ISSUER.
aud Must include agora-admin-api (string or array element).
sub Stable user id at the IdP; stored as external_account_id in MongoDB.
jti Required; session id in the API and audit (session_id). Missing jti401.
exp Expiry; validated with 120 s leeway (clock skew).
nbf Optional “not before”; same 120 s leeway when present.
iat Issued-at; auxiliary use.
Claim from OIDC_ROLE_CLAIM String or string array (e.g. groups); mapped via OIDC_ROLE_MAP.
preferred_username, email Help choose login on JIT (with sub).

Agora also uses the JWT header kid to pick JWKS keys and cache them (OIDC_JWKS_CACHE_TTL; JWKS reload on signature failure depends on your build).

4. Glossary

  • OIDC — OpenID Connect. Login and JWT issuance without the app querying the directory on every user action.
  • JWT — JSON Web Token. Signed payload (claims): who the user is, expiry, audience. Readable content; cannot be forged without the key.
  • IdP — Identity Provider. Central directory holding users and access policies; issues tokens after login.
  • Claim — one named field inside a JWT (sub, aud, groups, etc.).
  • Access token — IdP JWT the Agora backend uses to validate identity and roles. The browser does not send it to Agora APIs.
  • ID token — JWT about the browser login event. Not used for Agora admin APIs (different aud and purpose).
  • JWKS — JSON Web Key Set. IdP public keys at a URL; Agora verifies access token signatures with them.
  • RS256 — RSA-SHA256 JWT signing. Typical for IdP access tokens.
  • OAuth2 / OpenID Provider — IdP component that issues access (and optionally ID) tokens over OAuth2/OIDC.
  • Discovery — JSON at {issuer}/.well-known/openid-configuration: issuer, jwks_uri, and other endpoints.
  • Scope — rights/data the client requests at authorization; controls which claims appear in the token.
  • Audience (aud) — which application the JWT is for. Agora expects agora-admin-api in the access token (or in the aud list).
  • Bearer — HTTP scheme Authorization: Bearer <token>. Here the browser sends Agora’s session Bearer token, not the IdP JWT.
  • JIT (Just-In-Time) — first successful Agora request with a valid IdP token creates the DB account if missing and a mappable role exists; otherwise 403 and no account.
  • Property Mapping (Authentik) — rule for fields added to the access token (e.g. groups); without it groups may be missing from the JWT.
  • iss (issuer) — who issued the JWT (IdP URL). Agora picks JWKS from iss for signature verification.
  • sub (subject) — stable user id at the IdP; stored as external_account_id in Agora.
  • jti (JWT ID) — unique id for an IdP access token issuance. Agora uses the first successful jti as stable session_id for OIDC sessions.
  • exp / nbf — token validity window (and optional not-before).
  • SSO — single sign-on: user authenticates once to the IdP; apps consume issued tokens.
  • End session — IdP URL to end the browser session (“sign out everywhere”), unlike clearing only client-side tokens.