Skip to content

Permissions & Role Mapping

Identity Atlas authorizes every API request against a fixed catalog of permissions. Customers map their Entra ID app roles to those permissions, so "who can do what" is configurable without code changes.

This page is the reference for the permission model. The catalog below is kept in sync with the code by a test (app/api/src/auth/docsCoverage.test.js) — if a permission is added to the catalog without being documented here, CI fails.

How authorization works

  1. Authentication — when AUTH_ENABLED=true, every /api/* request must carry a valid Entra ID bearer token (or a read/crawler API key). When AUTH_ENABLED=false (the default for local/laptop use) the API runs in open mode: all gates are no-ops and a banner warns that anyone with the URL has full access. Open mode is a supported deployment for evaluation and single-user local runs — do not use it on a network without a trusted boundary.
  2. Role → permission resolution — the token's roles claim is resolved to a set of permissions via the configured mapping (Admin → Authentication → Roles & Permissions), falling back to the built-in seed mapping.
  3. Per-route enforcement — each protected route declares the permission(s) it needs via requirePermission(...). The gate is attached to the individual route, not the /api mount, so one router's permission never affects another's endpoints.
  4. Wildcard — a role mapped to * is granted every permission (including ones added in future releases). The seed Admin role uses *.

Read & crawler API keys

  • Read API keys (fgr_…) are accepted only on GET requests to non-admin endpoints (the /api/admin/ check ignores path casing) and resolve to data.read. They're used by the generated Excel/Power Query workbook.
  • Crawler API keys (fgc_…) authenticate the worker on /api/crawlers/* and /api/ingest/* only, with their own per-crawler scope (systemIds) and permissions (ingest, refreshViews, admin). They are separate from the user-facing permission catalog below.

Permission catalog

Permissions are grouped into Read, Export, Write, and Admin.

Read

Permission Label Notes
data.read Read all data Effectively "can sign in at all." Enforced as authentication-required — any signed-in user can read; there is no per-route requirePermission('data.read') gate. fgr_ read tokens are granted it implicitly. The Dashboard reads (GET /api/admin/dashboard-stats, /dashboard-timeseries), run history (GET /api/risk-scoring/runs, /api/context-plugins/runs, /api/account-linking/runs) GET /api/updates/intent and the Performance request log (GET /api/perf*, which also refuses fgr_ read tokens) additionally require at least one mapped permission, so a signed-in user whose roles map to nothing is refused.

Export

Permission Label Gated endpoint (representative)
data.export.ui Export to Excel/CSV GET /api/admin/export/curated, POST /api/admin/data-export/workbook
data.export.apikey Generate read-only API keys POST /api/admin/read-tokens (mint your own fgr_ token)
data.share Create matrix share links POST /api/matrix/shares (also GET /api/matrix/shares and POST /api/matrix/shares/:id/revoke — the Shared matrices management page). Only takes effect while the experimental Matrix sharing feature is switched on — see Experimental features. See Sharing a matrix.

Write

Permission Label Gated endpoint (representative)
data.write.tags Manage tags POST/PATCH/DELETE /api/tags…
data.write.categories Manage categories POST/PATCH/DELETE /api/categories…
data.write.reports Build custom reports POST/PUT/DELETE /api/nl-reports/saved…, and the report builder (/api/nl-reports/*). Running or downloading a report needs only Read data.
data.read.reports Ask questions in plain language Lets the holder ask the local model a question and read the answer: POST /api/nl-reports/interpret, run, resolve and the read-only status, catalog, lookup and their own conversations. Deliberately separate from Build custom reports: somebody who may ask a question should not thereby be able to create and delete the saved reports every analyst sees, and asking is NOT implied by building either — the seed RoleMiner role carries both. Only takes effect while the experimental custom reports feature is switched on.
data.write.contexts Build contexts The context assistant (/api/context-assistant/*), and creating manual contexts and editing context members (/api/contexts… writes — also allowed with admin.context-plugins). Viewing contexts needs only Read data.
data.write.risk Risk score overrides PUT/DELETE /api/risk-scores/:type/:id/override
data.write.identity Identity link decisions PUT/DELETE /api/identities/:id/members/:userId/override (confirm / reject / clear an account-linking decision)
data.write.certifications Certification decisions Reserved — no interactive endpoint yet. Certification decisions are currently ingested via the crawler (POST /api/ingest/governance/certifications). When an approve/revoke endpoint is added it will be gated by this permission.

Admin

Permission Label Gated endpoint (representative)
admin.crawlers Crawler configuration GET/POST/PATCH/DELETE /api/admin/crawlers…, crawler jobs, trigger risk-scoring runs, account-linking config + runs (GET/PUT /api/account-linking/config, POST /api/account-linking/runs), risk profile/classifier reads (GET /api/admin/risk-profile, /api/admin/classifiers)
admin.systems Systems configuration PUT /api/systems/:id, owners, clean-database, history-retention (read + write), auto-update status/log/toggle (/api/admin/updates/*)
admin.llm LLM configuration /api/admin/llm/*, risk profiles/classifiers (incl. GET /api/admin/risk-profile, /api/admin/classifiers)
admin.context-plugins Context plugins /api/context-plugins… (run/configure clustering, manager-hierarchy, etc.)
admin.csv-import CSV import /api/admin/crawler-configs/:id/files (any upload-supporting crawler type), custom-connector ingest
admin.read-tokens Manage read API keys GET/DELETE /api/admin/read-tokens (list/revoke tokens minted by others)
admin.feature-flags Feature flags POST /api/admin/features/toggle
admin.auth Authentication & roles GET/PUT/DELETE /api/admin/roles — edits this very mapping. A self-lockout guard prevents a save that would strip your own admin.auth.

Seed (default) role mapping

A fresh install ships with this mapping (customisable in the Admin UI):

Role Permissions
Admin * (all permissions)
RoleMiner data.read, data.read.reports, data.export.ui, data.export.apikey, data.share, data.write.reports, data.write.contexts
Servicedesk data.read

No-role users fail closed

A signed-in user whose roles resolve to no permissions is denied every permission gate — they can authenticate but cannot perform any admin or write action. There is deliberately no "no roles → full admin" fallback (that was a privilege-escalation flaw). Note that read endpoints are not permission-gated, so a roleless user can still read data; set AUTH_REQUIRED_ROLES to keep roleless users from signing in at all.

Getting the first admin in (bootstrap)

Because there's no fallback, you must grant access explicitly:

  1. In Entra ID, assign your user to an app role that the mapping maps to permissions — the seed maps the Admin role to * (full access).
  2. Then enable auth (AUTH_ENABLED=true) and sign in.

Locked yourself out? If you enabled auth before assigning yourself a role, no one can reach the admin UI. Recover from the host shell with the auth CLI — disable auth, assign the role in Entra, then re-enable:

# 1. Disable auth (recovery path), then restart:
docker compose exec web node src/cli/auth-config.js disable
docker compose restart web

# 2. Assign your user the Admin app role in Entra ID.

# 3. Re-enable auth and restart:
docker compose exec web node src/cli/auth-config.js enable --tenant <tenant-guid> --client <client-guid>
docker compose restart web

UI gating

The SPA calls GET /api/auth-me after sign-in to learn its own permissions and hides controls the user can't use (the Admin tab, its sub-tabs, and export buttons). The server remains the source of truth — hidden controls are also enforced server-side, so hiding is a UX nicety, not a security boundary.

Deployment notes

  • Open mode (AUTH_ENABLED=false) — supported for local/evaluation use; all permission gates are bypassed.
  • Azure App Service — AUTH_ENABLED=true is set from first boot; the Authentication admin sub-tab is hidden (managed platform).
  • Tenant/client IDs are configured via AUTH_TENANT_ID / AUTH_CLIENT_ID; the role→permission mapping is edited live in Admin → Authentication.
  • Token audience — the API accepts only access tokens whose audience is api://<client-id> (its exposed API scope). ID tokens are rejected. This is why the setup walkthrough has you "Expose an API" with the default api://<client-id> Application ID URI.

Testing

Enforcement is verified by:

  • app/api/src/auth/permissionMatrix.test.js — for every gated permission, asserts the representative endpoint is reachable with the permission and returns 403 without it; plus a completeness guard that fails if any catalog permission is unclassified.
  • app/api/src/middleware/requirePermission.test.js — unit tests for the gate decision logic.
  • app/ui/e2e/permission-gating.spec.js — confirms the UI shows/hides the Admin tab and sub-tabs per permission.
  • app/api/src/auth/docsCoverage.test.js — fails if a catalog permission is missing from this page.