Okta (Workforce Identity)
Pulls users, enrolled MFA factors, access policies, and System Log events from Okta, deriving KRIs for MFA adoption, phishing-resistant factor usage, inactive-user access, impossible-travel sign-ins, admin session dwell, MFA-fatigue attempts, and policy exceptions.
At a glance
| Vendor | Okta Workforce Identity |
|---|---|
| Source type | identity |
| Vendor ID (slug) | okta |
| Base URL | Per-tenant — https://<your-org>.okta.com. Preview orgs use .oktapreview.com. |
| Auth method | api-key — Authorization: SSWS <token>. The connector overrides the default Bearer prefix for Okta. |
| Schedule default | daily — the 24h KRIs (impossible travel, MFA push, admin sessions) re-window every run. |
| Recommended for | Okta tenants with Behavior Detection enabled — the impossible-travel KRI relies on Okta tagging events with the "Velocity" behavior. |
| Availability | New in 2026.04. |
Required scopes & roles
Draxis authenticates with an Okta SSWS API token issued to a dedicated service-account admin user. That user's admin role is the actual permission boundary — the token inherits it exactly. Use:
- Read-Only Administrator (Okta's built-in role) — covers every API the connector calls: Users (list + factors + appLinks), Apps, Policies (ACCESS_POLICY type), and the System Log.
- If you prefer a custom admin role, grant read on: Users, Groups, Apps, Policies, and System Log. Do not grant any manage permission.
Do not use a Super Administrator account to mint the token. Super Admin's permissions are too broad and the token inherits all of them — if it ever leaks, the blast radius is your entire Okta org. Read-Only Administrator keeps the blast radius to read-only.
Setup steps
- Create a dedicated service-account user in the Okta Admin Console (Directory → People → Add Person). Name it
draxis-connector@your-domain.comor similar — it should be obvious in audit logs who's doing what. Set a long random password. Disable self-service password reset on this user. - Assign the Read-Only Administrator role. Security → Administrators → Add Administrator. Pick the service-account user. Role: Read-Only Administrator. Scope: All resources (the KRI calculations span users, apps, policies, and logs — scoping reduces signal without materially improving security vs. the read-only role).
- Enable Okta Behavior Detection (optional but recommended). Security → General → Behavior Detection. Turn on Velocity at minimum — that's what populates the impossible-travel KRI. New-Device and New-Geo-Location are nice to have.
- Mint the SSWS API token. Sign in as the service account (not your personal admin), go to Security → API → Tokens → Create Token. Name it
draxis-connector. Copy the token value — you cannot retrieve it later. Tokens inherit the role of the user who mints them, which is why step 1 matters: minting with your personal Super Admin account would give the token Super Admin reach. - Note your org URL. Your Okta sign-in URL is
https://<your-org>.okta.com— the part before.okta.comis your org slug. Preview/sandbox orgs use.oktapreview.comand Okta's EU customer cells use.okta-emea.com. You'll paste the full origin (scheme + host, no path) into Draxis as the API Base URL.
Wire it into Draxis
- Open Settings → Integrations in your tenant.
- Click Add integration and pick Identity Provider as the source type.
- Pick Okta (Workforce Identity) from the vendor dropdown. Draxis pre-fills the auth method (API Key) and daily schedule.
- In API Base URL, paste your org URL (e.g.
https://acme.okta.com). No trailing slash; the connector adds/api/v1/...paths itself. - In API Key / Token, paste the SSWS token from step 4. Draxis encrypts it server-side with
encryption.keybefore storage. - Click Test. Green means Draxis reached
/api/v1/users?limit=1and authenticated successfully. - Under KRIs to import, tick the KRIs you want Draxis to manage. All seven
okta_*KRIs are checked by default; uncheck any you don’t need (e.g. drop the impossible-travel KRI if your tenant has Behavior Detection disabled — see Quirks). Selected rows are created on save with the seeded thresholds. Unchecking a previously-imported KRI deletes it on save. - Save. The connector runs
dailyby default; Run now from run history triggers an immediate sync.
KRIs produced
| Slug | Meaning | Derivation |
|---|---|---|
okta_mfa_adoption_pct |
% of sampled active users enrolled in at least one real MFA factor | For each user in GET /users?filter=status eq "ACTIVE" (capped at 2000 by default), fetch /users/{id}/factors and count users with at least one status=ACTIVE factor that is not password or question. Value = round(enrolled / sampled * 100, 1). |
okta_phish_resistant_mfa_pct |
% of MFA-enrolled users whose enrollment includes a phishing-resistant factor | Of the users counted as MFA-enrolled, count those with at least one factor where factorType in ("webauthn","u2f") or provider == "FIDO". Value = round(phish_resistant / mfa_enrolled * 100, 1). |
okta_inactive_users_with_app_access |
Suspended users still holding at least one app assignment | For each user from GET /users?filter=status eq "SUSPENDED" (capped at 500), fetch /users/{id}/appLinks and count those with a non-empty response. |
okta_impossible_travel_24h |
System Log events tagged with Behavior Detection's "Velocity" anomaly in the last 24h | GET /logs?since=<now-24h>&limit=1000 (paginated via Link headers), filter events where debugContext.debugData.behaviors contains Velocity (case-insensitive). |
okta_admin_session_outliers_24h |
Admin sessions (in the 24h window) that lasted more than 8 hours | Pair user.session.access_admin_app events with matching user.session.end events by authenticationContext.externalSessionId; count sessions where end - start > 8h. Sessions without a captured end don't count as outliers. |
okta_failed_mfa_push_24h |
Okta Verify push denials in the last 24 hours — MFA-fatigue signal | count(logs where eventType == "user.mfa.okta_verify.deny_push" and published > now-24h) |
okta_app_policy_exceptions |
Active ACCESS_POLICY rules that ALLOW access without requiring MFA | GET /policies?type=ACCESS_POLICY, then for each policy GET /policies/{id}/rules. Count rules where status == "ACTIVE" and actions.appSignOn.access == "ALLOW" and either verificationMethod is missing or factorMode == "1FA". |
Each row is a slug the connector writes to. Draxis creates the matching kri rows automatically when you check them in the KRIs to import section of the integration form — no manual API call or seed script needed. Thresholds shown in the table are the seeded defaults; you can edit them freely in the KRIs tab afterwards.
Vendor quirks
- Authorization header is
SSWS, notBearer. Okta API tokens predate Bearer convention. The connector overrides Draxis's defaultapi-keyheader per request; no configuration needed. - "Velocity" is Okta's term for impossible travel. It only shows up in System Log events if Behavior Detection is enabled (step 3 above). If you see the KRI flatlined at 0, Behavior Detection is likely off — not that you have no impossible-travel events.
- MFA adoption is sampled, not exhaustive. The connector fetches factors one user at a time; caps at 2000 users by default to stay comfortably under Okta's per-endpoint rate limits (600 req/min is the most common ceiling). For tenants with >2000 users, override by saving
{"user_sample_cap":5000}in Extra Config — the run will just take longer. - Phishing-resistant KRI denominator is MFA-enrolled users, not all users. So a tenant with 10% MFA adoption and 100% of those using WebAuthn reads as
okta_phish_resistant_mfa_pct = 100, which is technically accurate but misleading. Read it alongsideokta_mfa_adoption_pctfor a full picture. - Admin session outliers only count sessions that ended within the window. If a session started at T-9h and is still live at run time, we don't know it lasted >8h yet. The next day's run will catch it if the session ends then. This is why we report it as "outliers closed in the last 24h" rather than "admin sessions over 8h."
- Policy exceptions count rules, not users. One ALLOW-without-MFA rule affecting 5000 users and 500 apps still counts as
1. Use the threshold as a rule-count boundary (e.g. “no more than two exceptions”), not a user-count boundary. - SSWS tokens inherit the minting user's role at creation time and never change. If you later upgrade the service account to Super Admin, the existing token stays read-only. If you downgrade, the existing token still retains the old role. Rotate the token after any role change.
- Token rotation is mandatory. Okta expires SSWS tokens after 30 days of inactivity. Because the connector runs daily, that doesn't happen in practice — but if you disable the integration for a month, the token will silently stop working. Either keep the schedule active or budget for a rotation.
Troubleshooting
- HTTP 401 with
E0000011: Invalid token provided— the SSWS value is wrong, expired (30d inactivity), or the service account was deactivated. Mint a new token from the service account. - HTTP 403 with
E0000006: You do not have permission— the service account's role doesn't include the endpoint the connector hit. Re-check that the user has Read-Only Administrator (or a custom role that reads users, apps, policies, and logs). - HTTP 429 on run — you've hit Okta's per-minute rate limit. The connector doesn't retry internally; the run errors. Lower the sample cap via Extra Config, or move the schedule to
hourly(smaller chunks) — or open a support request if you want us to add exponential backoff. okta_impossible_travel_24his always 0 — Behavior Detection is probably disabled. Enable it in Security → General → Behavior Detection, turn on Velocity, and the next run will populate.okta_mfa_adoption_pctis suspiciously low — the sample cap (default 2000) is hitting your userbase's tail. Raise it via Extra Config JSON:{"user_sample_cap":10000}.okta_app_policy_exceptionsseems high — it counts every active ACCESS_POLICY rule where an ALLOW action doesn't require MFA. If your org uses a lot of "allow if device is managed" style rules without explicit MFA verification, those count. Review them in the Okta Admin Console — device-trust alone isn't phishing-resistant.rowsSkipped > 0androwsWritten = 0— your tenant hasn't wired KRI rows matching the connector's slugs yet. Open the integration in Settings → Integrations, check the KRIs you want under "KRIs to import", and save.- Still stuck? Open a support ticket with the run ID (from Run history) and we'll dig in.