IdRa

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

OktaIdRaNotes
Users and lifecycle statesDirectory → PeopleThe login must equal the email.
Groups, group rulesDirectory → GroupsRules use a subset of the expression language.
OIDC web, SPA, native, service appsApplicationsNew client IDs and secrets.
SAML 2.0 appsApplicationsNew IdP metadata and certificate.
Bookmark appsApplications
Authorization servers, scopes, claimsSecurity → APIOne shared issuer, told apart by aud.
Global session and app sign-on policiesSecurity → Global Session Policy, Authentication PoliciesRebuild them; see the API table.
Network zones, password policiesSecurity → Networks, Password Policies
Authenticators: TOTP, passkeys, SMS and voice, security question, YubiKey OTP, DuoSecurity → AuthenticatorsPeople enroll again.
Okta Verify pushIdRa VerifyA web app with number challenge, not a native app.
System Log, event hooksReports → System Log, Workflow → Event HooksEvent types are IdRa's own names.
SCIM provisioning to appsApp → ProvisioningCreate, 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.

AreaMissing in IdRa today
PasswordsNo password or hash import, and no password import hook. Everyone sets a new password.
MFA enrollmentsNot portable. Everyone enrolls again.
DirectoriesNo AD or LDAP agent, Desktop SSO, LDAP interface or RADIUS.
Sign-on typesNo WS-Federation (commonly used for Office 365) and no SWA password-vaulted apps.
Device-bound passwordlessNo FastPass equivalent or native Verify app. Passkeys are the nearest option.
SAMLNo Single Logout or assertion encryption. One signing key per organization.
OAuthNo implicit or password grants, token exchange, PAR, JAR or wildcard redirects.
Directory modelNo user types or linked objects. One global profile schema.
AdminFive fixed roles. No custom roles or resource sets.
AutomationNo inline hooks, log streaming or no-code workflows. Event hooks are the automation point.
CatalogA small catalog of SAML presets, not thousands of integrations. Most apps are set up by hand.
FederationLinking 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.
LanguageThe 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/v1StatusDifferences
/users, /users/me, lifecycle, /users/{id}/groupsWorkslogin must equal email. Profile keys must exist in the Profile Editor, or 400 E0000001.
/groups, /groups/{id}/users, /groups/rulesWorksOne target group per rule.
/apps, /apps/{id}/users, /apps/{id}/groupsWorksBOOKMARK, OPENID_CONNECT and SAML_2_0 only. Unknown fields are rejected, so you can't replay Okta's JSON unchanged.
/logs, /zones, /eventHooks, /authorizationServersWorksSee Differences from Okta.
/policiesWorksOnly 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}/features404 E0000007Not 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.

SDKChangedCalls that passedCalls that failed
@okta/okta-sdk-nodejs 8.2.0Org URL, SSWS tokenCreate, 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 serverslistIdentityProviders, getOrgSettings and listCustomDomains return 404
@okta/okta-auth-js 8.0.1Issuer onlyDiscovery with getWellKnown; exchangeCodeForTokens; token.verify; token.getUserInfo; token.revoketoken.introspect with a client that has no secret; signInWithCredentials; session.exists returned false
@okta/jwt-verifier 4.0.2Issuer onlyverifyIdToken; verifyAccessToken on a JWT access token from a sign-in that requested an API resource, including refusing a wrong audienceverifyAccessToken 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.

OktaIdRa
Issuer https://acme.okta.com or https://acme.okta.com/oauth2/defaulthttps://<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: http on localhost, 127.0.0.1 or [::1] with a port, and PKCE on.
  • Claims: email and name are in UserInfo, not the ID token. If an app reads them from the ID token, change it.
  • Groups: request the groups scope. The claim has every group name, including Everyone, with no filter.
  • sub: IdRa issues new user IDs, so sub changes. Apps keyed on sub must 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.

okta-to-idra.mjs
// 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.mjs

Before 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.hash without 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/apps with 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

  1. 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.
  2. Build. Set up zones, password policies, authenticators and authentication policies. Copy users and groups as above, and create and assign apps.
  3. 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.
  4. Lower token lifetimes in Okta ahead of the move, so old tokens are gone soon after an app switches.
  5. 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.
  6. 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.
  7. Point admin automation at IdRa's /api/v1, with new tokens.
  8. 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.

activate.mjs
// 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.

On this page