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:
- Running OIDC with Authentik locally
- Which JWT fields we use
- Agora OIDC configuration options
- 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¶
- In Authentik create an Application named Agora (the slug is filled automatically).


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

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

- On that page set Client ID to
agora-admin-apifor convenience. Copy Client Secret from the page; you will put them inOIDC_CLIENT_IDandOIDC_CLIENT_SECRET(see Agora settings below).
In Advanced protocol settings add offline_access to allowed Scopes.

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

- Open the Groups section.

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

- You will see a short list of groups.

- Open Users to create a test user.

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

- User created.
- Open the user card.

- Open the "Groups" tab.

- Add a group.

- Pick the existing "Content managers" group.

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

- Finally set the user’s password.

1.2. Configure Agora¶
Set these environment variables so Agora can use OIDC with the IdP:
OIDC_AUDIENCE=agora-admin-api— expectedaudclaim in the IdP JWTOIDC_CLIENT_ID=agora-admin-api— effectively the application login registered with the IdPOIDC_CLIENT_SECRET=oCSarud3XmFrYyjVl....ko1VW8n2O3SJLHfwCoTiy— the application secret from the IdPOIDC_SCOPES="openid profile offline_access"— optional; these are Agora defaults.offline_accessallows refresh tokensOIDC_ISSUER=http://authentik.local:9000/application/o/agora/— your server URL, e.g. the Authentik from above. Theagorasegment is Authentik’s convention: the application slug is part of the issuer pathOIDC_TITLE=Authentik— label shown to users; OIDC stays off without this optionOIDC_ROLE_CLAIM=groups— optional; which JWT field carries group membershipOIDC_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¶
- On the login screen the user sees local login and OIDC.

- They choose IdP login and land in Authentik.

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

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

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¶
- Decide which claim carries roles.
Often groups (string array), sometimes a dedicated string claim.
- 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.
- Keep claim values stable and easy to map.
Examples: agora-administrator, agora-content-manager, or human-readable names like Content managers.
- Set Agora variables.
OIDC_ROLE_CLAIM=groups
OIDC_ROLE_MAP='{"agora-administrator":"administrator","agora-content-manager":"content_manager","agora-monitoring":"monitoring","agora-security":"security"}'
- 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/oidcand 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}isjti. -
session_logout— ends session byjti.
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¶
-
OIDC_ISSUER,OIDC_AUDIENCE, and JWKS match the real access token. -
OIDC login in the UI succeeds.
-
JIT: new
subplus mappable role → account in MongoDB with correct role; without mappable role → 403, no account. -
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 jti → 401. |
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
audand 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 expectsagora-admin-apiin the access token (or in theaudlist). - 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 fromissfor signature verification.sub(subject) — stable user id at the IdP; stored asexternal_account_idin Agora.jti(JWT ID) — unique id for an IdP access token issuance. Agora uses the first successfuljtias stablesession_idfor 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.