Migrate from Okta
What maps from Okta to IdRa and what doesn't, exporting users, groups and apps, issuer changes, cutover and rollback.
IdRa covers much of what a typical Okta workforce org uses, and its /api/v1 API follows Okta's, but it isn't a drop-in replacement. Check the gaps below against what you use before you plan a date. Move app by app, and keep Okta running until the last app has moved.
What maps directly
| Okta | IdRa | Notes |
|---|---|---|
| Users and lifecycle states | Directory → People | The login must equal the email. |
| Groups, group rules | Directory → Groups | Rules use a subset of the expression language. |
| OIDC web, SPA, native, service apps | Applications | New client IDs and secrets. |
| SAML 2.0 apps | Applications | New IdP metadata and certificate. |
| Bookmark apps | Applications | |
| Authorization servers, scopes, claims | Security → API | One shared issuer, told apart by aud. |
| Global session and app sign-on policies | Security → Global Session Policy, Authentication Policies | Rebuild them; see the API table. |
| Network zones, password policies | Security → Networks, Password Policies | |
| Authenticators: TOTP, passkeys, SMS and voice, security question, YubiKey OTP, Duo | Security → Authenticators | People enroll again. |
| Okta Verify push | IdRa Verify | A web app with number challenge, not a native app. |
| System Log, event hooks | Reports → System Log, Workflow → Event Hooks | Event types are IdRa's own names. |
| SCIM provisioning to apps | App → Provisioning | Create, update, deactivate. No app imports or password sync. |
What doesn't
These come from IdRa's own audit against Okta, dated 2026-10-30. If you rely on one, plan a replacement or wait.
| Area | Missing in IdRa today |
|---|---|
| Passwords | No password or hash import, and no password import hook. Everyone sets a new password. |
| MFA enrollments | Not portable. Everyone enrolls again. |
| Directories | No AD or LDAP agent, Desktop SSO, LDAP interface or RADIUS. |
| Sign-on types | No WS-Federation (commonly used for Office 365) and no SWA password-vaulted apps. |
| Device-bound passwordless | No FastPass equivalent or native Verify app. Passkeys are the nearest option. |
| SAML | No Single Logout or assertion encryption. One signing key per organization. |
| OAuth | No implicit or password grants, token exchange, PAR, JAR or wildcard redirects. |
| Directory model | No user types or linked objects. One global profile schema. |
| Admin | Five fixed roles. No custom roles or resource sets. |
| Automation | No inline hooks, log streaming or no-code workflows. Event hooks are the automation point. |
| Catalog | A small catalog of SAML presets, not thousands of integrations. Most apps are set up by hand. |
| Federation | Linking existing people to an external IdP is opt-in per provider and domain, and never applies to administrators, so set it up before they sign in through the IdP. |
| Language | The UI and emails are English only. |
API path compatibility
Checked by calling a running IdRa with an SSWS token. Create the token under Security → API → Tokens.
Path under /api/v1 | Status | Differences |
|---|---|---|
/users, /users/me, lifecycle, /users/{id}/groups | Works | login must equal email. Profile keys must exist in the Profile Editor, or 400 E0000001. |
/groups, /groups/{id}/users, /groups/rules | Works | One target group per rule. |
/apps, /apps/{id}/users, /apps/{id}/groups | Works | BOOKMARK, OPENID_CONNECT and SAML_2_0 only. Unknown fields are rejected, so you can't replay Okta's JSON unchanged. |
/logs, /zones, /eventHooks, /authorizationServers | Works | See Differences from Okta. |
/policies | Works | Only PASSWORD can be created. |
/idps, /authenticators, /meta/schemas/*, /meta/types/*, /sessions, /domains, /brands, /inlineHooks, /logStreams, /iam/*, /org, /features, /devices, /trustedOrigins, /templates/*, /threats/configuration, /behaviors, /apps/{id}/credentials/keys, /apps/{id}/connections/*, /apps/{id}/features | 404 E0000007 | Not implemented. |
At the last audit, IdRa answered 118 of the 815 operations in Okta's Node SDK, so test your own scripts. The Terraform provider okta/okta 4.20 has been run against IdRa with SSWS tokens for users, groups, group rules, OAuth and bookmark apps, assignments, authorization servers, zones, event hooks and password policies. Its OAuth mode doesn't work, because it calls Okta's token path.
Okta SDK compatibility
We ran these calls on Node.js against a local IdRa on 2026-10-31. This is a sample of calls, not a full list, and the hosted service wasn't part of the run.
| SDK | Changed | Calls that passed | Calls that failed |
|---|---|---|---|
@okta/okta-sdk-nodejs 8.2.0 | Org URL, SSWS token | Create, list, get, update, deactivate and delete users; list a user's factors; create, list and delete groups; add, list and remove group members; create, list, get, deactivate and delete OIDC apps; list sign-on policies, network zones, System Log events, event hooks and authorization servers | listIdentityProviders, getOrgSettings and listCustomDomains return 404 |
@okta/okta-auth-js 8.0.1 | Issuer only | Discovery with getWellKnown; exchangeCodeForTokens; token.verify; token.getUserInfo; token.revoke | token.introspect with a client that has no secret; signInWithCredentials; session.exists returned false |
@okta/jwt-verifier 4.0.2 | Issuer only | verifyIdToken; verifyAccessToken on a JWT access token from a sign-in that requested an API resource, including refusing a wrong audience | verifyAccessToken on the opaque access token from a sign-in with no API resource. http issuers are refused unless the SDK's test flag is set, which the local run used |
The okta-auth-js run was in Node, with codes from a browser sign-in at the SDK's authorize URL. getWithRedirect and signOut weren't run.
IdRa access tokens are opaque unless the sign-in requests an API resource. To use verifyAccessToken, create the API under Security → API, link the client, add an access rule, and send resource=<identifier> with one of the API's scopes. Verify against the identifier as the audience.
Issuer and endpoint changes
Every authorization server in an IdRa organization shares one issuer, so update every app. Okta's paths at the root of the domain and under /oauth2/default return 404 on IdRa. Under the issuer, /oauth2/v1/authorize, token, userinfo, revoke, introspect, logout and keys, and /v1/keys, run the endpoints below, because the Okta SDKs build those URLs from the issuer. Discovery lists the endpoints below.
| Okta | IdRa |
|---|---|
Issuer https://acme.okta.com or https://acme.okta.com/oauth2/default | https://<your-org>.idra.team/api/auth |
/.well-known/openid-configuration under the issuer | /api/auth/.well-known/openid-configuration |
/oauth2/v1/authorize, /oauth2/default/v1/authorize | /api/auth/oauth2/authorize |
.../v1/token | /api/auth/oauth2/token |
.../v1/userinfo | /api/auth/oauth2/userinfo |
.../v1/keys | /api/auth/jwks |
.../v1/introspect | /api/auth/oauth2/introspect |
.../v1/revoke | /api/auth/oauth2/revoke |
.../v1/logout | /api/auth/oauth2/end-session |
Most libraries only need the new issuer, client ID and secret. Also check these, because each one can break an app that worked on Okta:
- Redirect URIs: matched exactly, with no wildcards. Web and Single-Page apps need
https, except for local development:httponlocalhost,127.0.0.1or[::1]with a port, and PKCE on. - Claims:
emailandnameare in UserInfo, not the ID token. If an app reads them from the ID token, change it. - Groups: request the
groupsscope. The claim has every group name, includingEveryone, with no filter. sub: IdRa issues new user IDs, sosubchanges. Apps keyed onsubmust match on email first, or they create duplicate accounts.- APIs that validate tokens: update the expected issuer, JWKS URL and audience.
- SAML apps: give the service provider IdRa's metadata from
/sso/saml/<appId>/metadata. The entity ID, SSO URL and certificate all change. Set the NameID to what the app already expects.
Export and import
Users and groups
The script reads Okta's API and writes to IdRa's. It creates people as Staged, so nobody is emailed yet. Run it once on an organization without these groups: it doesn't merge with existing ones, and people who already exist are reported as failures.
// Copies groups, users and memberships. Creates users as STAGED, so nobody is emailed yet.
const { OKTA_URL, OKTA_TOKEN, IDRA_URL, IDRA_TOKEN } = process.env;
async function call(base, token, path, init = {}) {
const res = await fetch(new URL(path, base), {
...init,
headers: {
authorization: `SSWS ${token}`,
accept: 'application/json',
'content-type': 'application/json',
},
});
if (res.status === 429) {
const reset = Number(res.headers.get('x-rate-limit-reset')) * 1000;
await new Promise((r) => setTimeout(r, Math.max(reset - Date.now(), 1000)));
return call(base, token, path, init);
}
return res;
}
// Follows the Link: rel="next" header.
async function* all(path) {
for (let next = path; next; ) {
const res = await call(OKTA_URL, OKTA_TOKEN, next);
if (!res.ok) throw new Error(`${next}: ${res.status} ${await res.text()}`);
yield* await res.json();
next = res.headers.get('link')?.match(/<([^>]+)>;\s*rel="next"/)?.[1];
}
}
const post = async (path, body) => {
const res = await call(IDRA_URL, IDRA_TOKEN, path, { method: 'POST', body: JSON.stringify(body) });
const json = await res.json();
return res.ok ? json : Promise.reject(new Error(JSON.stringify(json.errorCauses ?? json)));
};
// Okta group id -> IdRa group id. OKTA_GROUP only: Everyone is built in, app groups stay with their app.
const groups = new Map();
for await (const g of all('/api/v1/groups?limit=200')) {
if (g.type !== 'OKTA_GROUP') continue;
const { name, description } = g.profile;
groups.set(g.id, (await post('/api/v1/groups', { profile: { name, description } })).id);
}
const failed = [];
for await (const u of all('/api/v1/users?limit=200')) {
const { firstName, lastName, email, login } = u.profile;
try {
// Add custom attributes here, once they exist in IdRa's Profile Editor.
const { id } = await post('/api/v1/users?activate=false', {
profile: { firstName, lastName, email, login },
});
for await (const g of all(`/api/v1/users/${u.id}/groups`)) {
if (groups.has(g.id))
await call(IDRA_URL, IDRA_TOKEN, `/api/v1/groups/${groups.get(g.id)}/users/${id}`, { method: 'PUT' });
}
} catch (e) {
failed.push({ login, error: e.message });
}
}
console.log(`${groups.size} groups copied, ${failed.length} users failed`);
if (failed.length) console.table(failed);OKTA_URL=https://acme.okta.com OKTA_TOKEN=... \
IDRA_URL=https://<your-org>.idra.team IDRA_TOKEN=idra_... \
node okta-to-idra.mjsBefore running it:
- Add custom attributes in Directory → Profile Editor, then add them to the script.
- Find logins that differ from the email address. IdRa refuses them, and the script lists them as failures. Decide how those people will sign in before you continue.
- Any status comes across as Staged, including Okta's suspended people. Review the list, because the activation step below includes everyone Staged.
- Password hashes aren't imported. IdRa ignores a
credentials.password.hashwithout an error, so don't send one.
For a one-off, Directory → People → Import takes a CSV of up to 10,000 rows, with a preview before anything is written. See Import a CSV. Groups named in the file must already exist.
Apps
Recreate apps one at a time. IdRa issues new client IDs and refuses client_id on create, so app configuration changes either way.
- By hand: Applications → Create App Integration, or a preset from the catalog.
- By API: read each app from Okta and
POST /api/v1/appswith only the fields IdRa accepts. For an OIDC web app:
curl -X POST https://<your-org>.idra.team/api/v1/apps \
-H "Authorization: SSWS $IDRA_TOKEN" -H 'Content-Type: application/json' \
-d '{
"label": "Wiki",
"name": "oidc_client",
"signOnMode": "OPENID_CONNECT",
"settings": { "oauthClient": {
"application_type": "web",
"redirect_uris": ["https://wiki.example.com/callback"],
"grant_types": ["authorization_code"]
} }
}'The response holds credentials.oauthClient.client_id and client_secret, which is the only time you see the secret. Through this API, application_type is web, browser or service. Create native apps in the console.
Assign with PUT /api/v1/apps/{appId}/groups/{groupId} or POST /api/v1/apps/{appId}/users. Assign an app before you switch it, or everyone gets You are not assigned to this application.
Cutover
- Inventory. List every app, policy, hook and API script in Okta. Check each against the tables above, and decide what happens to anything IdRa can't do.
- Build. Set up zones, password policies, authenticators and authentication policies. Copy users and groups as above, and create and assign apps.
- Pilot. Activate a small group with the activation step, switch one low-risk app, and run a sign-in, sign-out and refused-access test on it. Check Reports → System Log.
- Lower token lifetimes in Okta ahead of the move, so old tokens are gone soon after an app switches.
- Activate everyone. People can't sign in to IdRa until they've activated, so do this before their apps switch. Activation links last 7 days.
- Switch apps in batches. Change each app's issuer, client ID and secret, redirect URIs and token validation, and redeploy. Add SCIM on IdRa only after turning it off on Okta for that app: two sources writing to one app conflict.
- Point admin automation at IdRa's
/api/v1, with new tokens. - Keep Okta for a while after the last app, with its users left active and unchanged, then decommission it.
Activate people
This emails every Staged person a link to set a password. Activation through the API always sends the email: sendEmail=false has no effect.
// Sends every STAGED person their activation email. Run it at cutover, not before.
const { IDRA_URL, IDRA_TOKEN } = process.env;
const headers = { authorization: `SSWS ${IDRA_TOKEN}`, accept: 'application/json' };
for (let next = '/api/v1/users?filter=status%20eq%20%22STAGED%22&limit=200'; next; ) {
const res = await fetch(new URL(next, IDRA_URL), { headers });
for (const u of await res.json()) {
const r = await fetch(new URL(`/api/v1/users/${u.id}/lifecycle/activate`, IDRA_URL), { method: 'POST', headers });
console.log(u.profile.login, r.status);
}
next = res.headers.get('link')?.match(/<([^>]+)>;\s*rel="next"/)?.[1];
}Rollback
Migration here is per app, so rollback is too, as long as Okta stays intact:
- One app: restore its Okta issuer, client ID and secret, and redeploy. Keep the old values in your secret store until decommission. People sign in to Okta as before.
- SAML app: restore the Okta metadata on the service provider.
- Provisioning: turn off SCIM on IdRa and back on in Okta. Reconcile accounts created or deactivated during the window, because IdRa doesn't sync them back to Okta.
- People: changes made in IdRa during the window, such as new hires and deactivations, don't reach Okta. Log them while you're in the window, and watch deactivations in particular: someone removed only in IdRa stays active in Okta.
Set a rollback deadline for each batch, and test the rollback on the pilot app before moving a critical one.