Okta

Proofroom reads Okta’s Management API with a read-only credential and files five kinds of evidence. Add it on Integrations → Catalogue → Okta → Connect.

What each collector shows

Collector Kind Default cadence What it shows Suggested controls
Users with MFA status snapshot quarterly Every user who is not deprovisioned, with their Okta status (active, locked out, password expired, in recovery, pending activation, suspended, staged), whether they hold an admin role, and their active second factors. The summary counts users per status. PCI DSS 8.4.2, 8.4.3; SOC 2 CC6.1
Admin and privileged roles snapshot quarterly Every user holding an admin role directly; every group holding one, with each member as their own row naming the group; the role; and whether the account is deactivated or suspended. PCI DSS 7.2.1, 7.2.2, 7.2.4; SOC 2 CC6.2, CC6.3
Leaver removals window monthly Users deactivated, suspended or deleted in the window, from the System Log, with who did it and whether it succeeded. With a People directory, the leaver lag: how long after each person left their Okta account was removed. PCI DSS 8.2.5; SOC 2 CC6.2
Inactive accounts snapshot quarterly Every user who is not deprovisioned, with status, admin status, creation date and last sign-in to Okta (lastLogin), and those with none in 90 days. PCI DSS 8.2.6; SOC 2 CC6.2
MFA enforcement snapshot quarterly Whether MFA is required for everyone: each rule of the global session (sign-on) policies and, on Identity Engine, of the app sign-in policies, with its state, whether it asks for two factors, who it covers and leaves out (named), and which apps it covers. Lists the gaps. PCI DSS 8.4.1, 8.4.2, 8.4.3; SOC 2 CC6.1

Users with MFA status

Proofroom first asks /.well-known/okta-organization whether the org runs Identity Engine (pipeline: "idx") or Classic ("v1").

  • Identity Engine: each user’s /api/v1/users/{id}/authenticator-enrollments. Okta’s Factors API lists only the factors in the user’s highest-priority enrolment policy and can show a false “no MFA” on Identity Engine. Okta marks this call Limited GA, Identity Engine only.
  • Classic: each user’s /api/v1/users/{id}/factors.

Scope for the enrolments call. Okta’s management API spec (version 2026.42.1) lists okta.users.read for it. It has also been reported to need okta.authenticators.read. Which one Okta enforces is UNVERIFIED, so a service app asks for both: okta.authenticators.read is optional. If Okta refuses it (invalid_scope), Proofroom signs in without it and still reads the enrolments; only if Okta then refuses the call does it read the Factors API, and the note names the missing scope.

Fallback to the Factors API. If Okta refuses the enrolments call for any user (the feature is not enabled for the org, E0000015; not permitted, 403; not found, 404; bad request, 400), the whole run is read again from the Factors API, never a mix. The evidence says which API was used and why. A Factors-API reading can show a user without MFA who has a factor enrolled, so grant the scope or ask Okta to enable the call. A 401 (bad credential) still fails the run.

What counts as a second factor: every active enrolment except the password and the security question (both are knowledge factors like the password). On Identity Engine, email is listed as “not counted”: Okta enrols every user’s email and it can be set to recovery only, which this call does not show per user. On Classic, the Email factor counts: an administrator turns it on as an MFA factor and the user enrols it.

This evidence shows enrolled factors, not whether MFA is required; the MFA enforcement evidence shows that.

Admin and privileged roles

Roles assigned to a user directly come from /api/v1/iam/assignees/users and each user’s /roles (roles a user holds only through a group are left out there, so nobody is counted twice). Okta has no call that lists only the groups holding a role, so Proofroom lists every group (/api/v1/groups) and reads each group’s /roles: one call per group. With N groups that is N calls, so a large org (thousands of groups) takes longer and uses more of Okta’s rate limit for /api/v1/groups (Proofroom waits when Okta says so; the run has 30 minutes). A group holding a role gives a row for the group and one per member (/api/v1/groups/{id}/users), naming the group. If any group cannot be read, the run fails. Role targets (the groups or apps a role is limited to) are not read.

MFA enforcement

  • Identity Engine: global session policies (type=OKTA_SIGN_ON) and their rules (actions.signon.requireFactor), and app sign-in policies (type=ACCESS_POLICY) and their rules (actions.appSignOn.verificationMethod: ASSURANCE with factorMode 2FA counts as MFA). Each app sign-in policy’s apps come from /api/v1/policies/{id}/mappings, named from /api/v1/apps. A sign-in asks for MFA when either layer requires it, so MFA is required for everyone when either layer requires it in every active rule that lets people in.
  • Classic: Okta sign-on policy rules (requireFactor, factorPromptMode). Classic per-app sign-on policies are not read; they can add MFA for an app but cannot remove MFA the sign-on policy asks for.

Okta has no report-only mode: a policy or rule is active (shown as enforced) or inactive (off). Rules are read in Okta’s order (by priority; the first match applies). A later rule, or for sign-on policies a later policy, is marked “never reached” and not counted only after an active rule (in an active policy) that has no condition narrowing it (network zone, device, platform, risk, a custom expression), leaves nobody out (no excluded users or groups, in the rule or its policy) and covers everyone (no group or user list, or only the Everyone group). A policy for everyone whose rules are all narrowed hides nothing. User and group ids in conditions are shown as logins and group names; a condition such as a network zone, device, platform, risk or a custom expression is shown with the rule.

Sign-on rules (factorPromptMode) that ask for MFA only once per new device (DEVICE) or per session (SESSION), not at every sign-in, are shown as such and do not count toward “MFA required for everyone”.

The evidence also says whether MFA is required for administrators at every sign-in: yes when it is required for everyone; no when no active rule in either layer asks for MFA at every sign-in; otherwise not stated (which rule an administrator meets depends on their groups, which this collector does not match).

Gaps listed: active, reachable rules that let people in with one factor; rules asking for MFA only once per new device or session (“MFA asked once per new device, not at every sign-in”); people or groups left out of a rule requiring MFA (only when MFA is not already required for everyone, since a person left out of one rule then meets another); inactive rules that require MFA; active apps on no app sign-in policy; rules using an authentication method chain, which Proofroom does not interpret.

Choose a credential

A service app (recommended by Okta):

  1. In the Admin Console, go to Applications → Create App Integration → API Services, and name it “Proofroom”.
  2. Under Client Credentials, choose Public key / Private key and generate a key; save the private key (PEM or JWK). Leave Require Demonstrating Proof of Possession (DPoP) off: Proofroom does not send DPoP.
  3. On the Okta API Scopes tab, grant okta.users.read, okta.logs.read, okta.roles.read, okta.groups.read, okta.policies.read, okta.apps.read and okta.authenticators.read (a Super Admin grants scopes).
  4. On the Admin roles tab, assign the app the Read-only Administrator role. Okta lets only a Super Admin list administrators; if the Admin and privileged roles collector is refused, assign the app the Super Administrator role: the read scopes still keep its token read-only.

Each collector asks only for its own scopes, so a scope not granted stops only the collector that needs it, and its error names the scopes:

Collector Scopes
Users with MFA status okta.users.read, okta.logs.read, okta.roles.read, okta.authenticators.read (without okta.roles.read, admin status shows as unknown; without okta.authenticators.read, enrolments are still tried and the Factors API is read only if Okta refuses them)
Inactive accounts, Leaver removals okta.users.read, okta.logs.read, okta.roles.read (without okta.roles.read, admin status shows as unknown)
Admin and privileged roles okta.users.read, okta.roles.read, okta.groups.read
MFA enforcement okta.policies.read, okta.users.read, okta.groups.read, okta.apps.read
Credential expiry (key age) okta.apps.read

Or an API token: sign in as a Read-only Administrator, go to Security → API → Tokens → Create token. A token acts with its creator’s permissions and expires after 30 days without use; Okta recommends a service app instead.

Connect

Field Value
Domain Your Okta domain, such as example.okta.com (or your custom domain)
API token The token, or blank for a service app
Client ID, private key The service app’s, or blank with a token

Test connection calls users, admin roles and the System Log and fails naming the scope behind any refusal. It then tries groups (okta.groups.read), policies (okta.policies.read), apps (okta.apps.read) and, for a service app, okta.authenticators.read, each on its own. A refusal there is a warning, not a failure: the test passes and its message names the scope to grant and which collector fails (or, for okta.authenticators.read, reads the Factors API if Okta refuses the enrolments) until it is granted. The collectors themselves still fail naming the scope.

Credential expiry

  • API token: Okta revokes a token after its token window (30 days by default) without use. This is shown as an “idle” expiry: each run uses the token and moves the date on. Proofroom lists the API tokens (/api/v1/api-tokens) and shows the expiry Okta gives for the one just used (Okta does not say which token made the call, so the one with the latest expiry is taken). If Okta refuses the list (viewing API tokens needs a Super Administrator), the expiry is counted as 30 days from the reading, with a note.
  • Service app: Okta keys do not expire. Proofroom finds the configured key among the app’s public keys (/api/v1/apps/{clientId}/credentials/jwks, matched by its public modulus, never by sending the key) and shows a rotate-by date a year after the key was added. Without okta.apps.read it shows a note instead.

Known limits

  • The System Log keeps 90 days, so Leaver removals runs monthly by default; keep it at 90 days or less.
  • Large orgs: one enrolments (or factors) call per user and one roles call per group, within Okta’s rate limits (Proofroom waits when Okta says so). Users with MFA status has an hour; the others 30 minutes.
  • If the credential cannot read admin roles, the MFA and inactive-account evidence shows administrator status as unknown.

UNVERIFIED

  • That /api/v1/iam/assignees/users leaves out users who hold a role only through a group (it does not matter for the count: group-held roles come from the groups).
  • That /api/v1/apps lists Okta’s own apps (Admin Console, Dashboard); a mapped app it does not list is shown by id.
  • That a token’s expiresAt in the API Tokens list moves forward at once when the token is used, and which admin role may list API tokens (Okta’s docs do not say; Super Administrator is assumed).
  • Which scope the authenticator enrolments call needs: Okta’s spec says okta.users.read; it has been reported to need okta.authenticators.read. Both are asked; without the second the enrolments are still tried.
  • That a refused enrolments call answers E0000015 (feature not enabled) on orgs without the Limited GA feature.
  • That the authenticator enrolments call answers for every user status (staged, deprovisioned-pending) as it does for active users.