Skip to main content
Okta identity provider emulation with OAuth 2.0 / OIDC, user management, groups, apps, and authorization servers. Supports both default org server and custom authorization server paths.

At a glance

Start

Okta listens on port 4006 in the default set, or on http://localhost:4000 when started alone. Without a seed config there is one user ([email protected]), the built-in Everyone group, the default authorization server, a confidential OAuth client (okta-test-client, secret okta-test-secret, redirect URI http://localhost:3000/callback), a public PKCE client (okta-test-app), and one OIDC app (Sample OIDC App). The @okta/okta-sdk-nodejs client works unchanged with orgUrl set to the emulator:

Auth

Every /api/v1/* route needs Authorization: SSWS <token> (or Bearer); a missing or unknown token is 401 E0000011 (“Invalid token provided”). From the CLI any non-empty token works. OAuth for Okta: the Management API and /oauth2/v1/clients also take Authorization: Bearer <access token> from the org authorization server (/oauth2/v1/token) when the token has the scope for the request: okta.<resource>.read or okta.<resource>.manage for GET, okta.<resource>.manage otherwise, for users, groups, apps, authorizationServers, and clients, and okta.clients.register for registering a client. A missing scope is 403 E0000006 with WWW-Authenticate: Bearer error="insufficient_scope". Tokens from custom authorization servers are for your own APIs and get 401 E0000011.

Management API conventions

Lists use Okta’s cursor pagination: limit and after. Each response carries a Link header with rel="self" and, while more remain, rel="next", whose URL holds the cursor; the SDK’s collections follow it. Default and maximum page sizes follow Okta (users 200, groups 10000, group members 1000, apps 20 of up to 200, app users 50 of up to 500, authorization servers 200). search and filter take Okta’s expression language: eq, ne, gt, ge, lt, le, sw, co, ew, and pr, joined with and, or, not, and parentheses, with quoted values (profile.email eq "[email protected]" and status eq "ACTIVE"). String comparisons ignore case. A malformed expression or an attribute Okta does not allow there is 400 E0000031. q matches the start of a name. Errors use Okta’s shape (errorCode, errorSummary, errorLink, errorId, errorCauses). A missing resource is 404 E0000007 with Okta’s summary (Not found: Resource not found: <id> (User)), and a validation failure is 400 E0000001 (Api validation failed: login) with the field in errorCauses.

OAuth and OIDC

Default org server and custom authorization server paths (/oauth2/:authServerId/...):
  • GET /.well-known/openid-configuration — Get OIDC discovery document: default server
  • GET /oauth2/:authServerId/.well-known/openid-configuration — Get server discovery document: per authorization server
  • GET /oauth2/v1/keys — Get JSON Web Key Set (JWKS)
  • GET /oauth2/v1/authorize — Authorize
  • POST /oauth2/v1/token — Exchange token
  • GET /oauth2/v1/userinfo — Get user info
  • POST /oauth2/v1/revoke — Revoke token
  • POST /oauth2/v1/introspect — Introspect token
  • GET /oauth2/v1/logout — End session
response_mode may be query (default), fragment (the code and state come back after #), or form_post. A refresh_token is issued only when the granted scope includes offline_access and the client has the refresh_token grant type, as on Okta. Each refresh rotates it. A scope parameter may narrow the new access token; asking for a scope outside the original grant is 400 invalid_scope, and the rotated refresh token keeps the original grant. A client without the refresh_token grant gets unauthorized_client.

Signing key

ID tokens are signed with a key stored in emulator state (kid: baseline-okta-1), so they stay valid across snapshot restores and persisted restarts. Set okta.signing_key.private_jwk to an RSA private JWK to pin the key. See OIDC signing keys.

Client registration

Okta’s Client Application API, the registration_endpoint every discovery document lists (/oauth2/v1/clients, at the org level for every server). It needs a management token (Authorization: SSWS ...) or an org server access token with okta.clients.register (to register) or okta.clients.manage (see Auth).
  • POST /oauth2/v1/clients — Register client: RFC 7591. client_name (required), redirect_uris, application_type (web, native, browser, service), grant_types (authorization_code, refresh_token, client_credentials), response_types (code), token_endpoint_auth_method (client_secret_basic, client_secret_post, none). Returns 201 with client_id and, for confidential clients, client_secret (client_secret_expires_at: 0)
  • GET /oauth2/v1/clients — List clients: q matches the start of the name, limit
  • GET /oauth2/v1/clients/:clientId — Get client (without the secret)
  • PUT /oauth2/v1/clients/:clientId — Replace client metadata
  • DELETE /oauth2/v1/clients/:clientId — Delete client
  • POST /oauth2/v1/clients/:clientId/lifecycle/newSecret — Rotate client secret
Defaults follow Okta: web clients use client_secret_basic and authorization_code, browser and native clients are public (none), and service clients use client_credentials. Bad metadata is 400 invalid_client_metadata; grant types the emulator does not run (implicit, password, device code) are refused the same way. A registered client can sign in through any authorization server.

Users

  • GET /api/v1/users — List users: search takes any attribute (id, status, created, activated, statusChanged, lastLogin, lastUpdated, passwordChanged, type.id, profile.*) with sortBy and sortOrder; filter takes id, status, lastUpdated, profile.login, profile.email, profile.firstName, and profile.lastName; q matches the start of the first name, last name, or email. Without search or filter, deprovisioned users are left out, as on Okta
  • POST /api/v1/users — Create user: profile.login, profile.email, profile.firstName, and profile.lastName are required; login and email must look like email addresses and logins are unique (emails need not be). Other profile attributes (mobilePhone, custom attributes) are kept. Optional credentials.password.value and groupIds. ?activate=false creates a STAGED user, ?nextLogin=changePassword a PASSWORD_EXPIRED one. New users join the built-in Everyone group
  • GET /api/v1/users/me — Get current user: the token’s user
  • GET /api/v1/users/:userId — Get user: by ID, login, or email
  • POST /api/v1/users/:userId — Update user: partial update, only the profile attributes sent change, and null clears one. credentials.password sets a password
  • PUT /api/v1/users/:userId — Replace user: profile attributes left out are cleared, and the required ones must be sent
  • DELETE /api/v1/users/:userId — Delete user: the first call deactivates, the second deletes
  • GET /api/v1/users/:userId/groups — List user groups
  • POST /api/v1/users/:userId/lifecycle/activate — Activate user: from STAGED, PROVISIONED, or DEPROVISIONED to ACTIVE; an active user is 403 E0000016. Answers {}, or { activationUrl, activationToken } with sendEmail=false (the URL is not served)
  • POST /api/v1/users/:userId/lifecycle/reactivate — Reactivate user: only for PROVISIONED users; answers like activate
  • POST /api/v1/users/:userId/lifecycle/deactivate — Deactivate user: to DEPROVISIONED, removing the user’s app assignments
  • POST /api/v1/users/:userId/lifecycle/suspend — Suspend user: ACTIVE to SUSPENDED (400 E0000001 otherwise)
  • POST /api/v1/users/:userId/lifecycle/unsuspend — Unsuspend user: SUSPENDED to ACTIVE (400 E0000001 otherwise)
  • POST /api/v1/users/:userId/lifecycle/unlock — Unlock user: LOCKED_OUT to ACTIVE (403 E0000032 otherwise)
  • POST /api/v1/users/:userId/lifecycle/expire_password — Expire password: to PASSWORD_EXPIRED; returns the user
  • POST /api/v1/users/:userId/credentials/change_password — Change password: oldPassword and newPassword; a wrong old password is 403 E0000014
Other status changes Okta forbids are 403 E0000038. Only ACTIVE and PASSWORD_EXPIRED users appear on the sign-in page, and a suspended or deactivated user’s codes and refresh tokens stop working.

Groups

  • GET /api/v1/groups — List groups: q, search on id, type, created, lastUpdated, lastMembershipUpdated, profile.name, profile.description; filter on id, type, lastUpdated, lastMembershipUpdated
  • POST /api/v1/groups — Create group (profile.name required and unique)
  • GET /api/v1/groups/:groupId — Get group
  • PUT /api/v1/groups/:groupId — Replace group profile
  • DELETE /api/v1/groups/:groupId — Delete group: with its memberships and app assignments
  • GET /api/v1/groups/:groupId/users — List group members
  • PUT /api/v1/groups/:groupId/users/:userId — Add group member (updates lastMembershipUpdated)
  • DELETE /api/v1/groups/:groupId/users/:userId — Remove group member
  • GET /api/v1/groups/:groupId/apps — List group apps: apps assigned to the group

Apps

  • GET /api/v1/apps — List apps: q on name or label; filter on status, name, user.id, group.id
  • POST /api/v1/apps — Create app (label and signOnMode required; ?activate=false creates it INACTIVE)
  • GET /api/v1/apps/:appId — Get app
  • PUT /api/v1/apps/:appId — Update app
  • DELETE /api/v1/apps/:appId — Delete app: deletes an INACTIVE app
  • POST /api/v1/apps/:appId/lifecycle/activate — Activate app (answers {})
  • POST /api/v1/apps/:appId/lifecycle/deactivate — Deactivate app (answers {})
  • GET /api/v1/apps/:appId/users — List app users: direct (scope: "USER") and through groups (scope: "GROUP")
  • POST /api/v1/apps/:appId/users — Assign user to app (id, optional app profile)
  • GET /api/v1/apps/:appId/users/:userId — Get app user
  • POST /api/v1/apps/:appId/users/:userId — Update app user profile
  • DELETE /api/v1/apps/:appId/users/:userId — Unassign user from app
  • GET /api/v1/apps/:appId/groups — List app group assignments: by priority
  • PUT /api/v1/apps/:appId/groups/:groupId — Assign group to app (priority, profile)
  • GET /api/v1/apps/:appId/groups/:groupId — Get app group assignment
  • DELETE /api/v1/apps/:appId/groups/:groupId — Remove app group assignment
An OPENID_CONNECT app is an OAuth client, as on Okta. settings.oauthClient (redirect_uris, grant_types, response_types, application_type) is required and validated like client registration, and credentials.oauthClient may set token_endpoint_auth_method and a custom client_id. The response’s credentials.oauthClient carries the client_id (the app ID unless one was given) and client_secret. The client signs in through any authorization server while the app is active; deactivating the app makes it invalid_client, and deleting the app removes the client. A client registered at /oauth2/v1/clients is listed as an app too, and deleting either removes both.

Authorization servers

  • GET /api/v1/authorizationServers — List authorization servers (q, limit, after)
  • POST /api/v1/authorizationServers — Create authorization server (name, optional id and audiences; 201)
  • GET /api/v1/authorizationServers/:authServerId — Get authorization server
  • PUT /api/v1/authorizationServers/:authServerId — Update authorization server
  • DELETE /api/v1/authorizationServers/:authServerId — Delete authorization server
  • POST /api/v1/authorizationServers/:authServerId/lifecycle/activate — Activate authorization server (204)
  • POST /api/v1/authorizationServers/:authServerId/lifecycle/deactivate — Deactivate authorization server (204)

Access policies

Each custom authorization server has access policies that decide which requests get tokens and how long the tokens last (OAUTH_AUTHORIZATION_POLICY policies with RESOURCE_ACCESS rules, as in the @okta/okta-sdk-nodejs authorizationServerApi):
  • GET|POST /api/v1/authorizationServers/:authServerId/policies, GET|PUT|DELETE .../policies/:policyId, POST .../policies/:policyId/lifecycle/activate|deactivate
  • GET|POST .../policies/:policyId/rules, GET|PUT|DELETE .../rules/:ruleId, POST .../rules/:ruleId/lifecycle/activate|deactivate
A policy names its clients (conditions.clients.include, client IDs or ALL_CLIENTS). A rule matches grant types (conditions.grantTypes.include), people (conditions.people.users.include user IDs, conditions.people.groups.include group IDs or EVERYONE), and scopes (conditions.scopes.include, or *), and sets actions.token.accessTokenLifetimeMinutes (5 to 1440, default 60), refreshTokenLifetimeMinutes (0 is unlimited), and refreshTokenWindowMinutes (default 10080, seven days). Priorities stay 1 to n; creating or moving one to a taken priority shifts the rest down. For each authorize, token, and refresh request, the first active policy by priority that names the client is used; its first active rule that matches the grant type, the user (client credentials match only EVERYONE), and every requested scope sets the lifetimes. No match is access_denied with “Policy evaluation failed for this request, please check the policy configurations.” (a redirect from /authorize, 400 from /token). Refresh tokens expire after the rule’s lifetime, or after the window passes without use. The default server and seeded servers start with Okta’s Default Policy (all clients) and Default Policy Rule (everyone, any scope, one hour access tokens). A server created with POST /api/v1/authorizationServers starts with no policies, so it issues no tokens until you add one, as on Okta. Seed policies on a server to set its own (rules may name groups and users by name or login). The org server (/oauth2/v1) has no access policies.

Seed config

Keys are snake_case. An entry that matches an existing record (by login, group name, app name, client_id, or server id), including the default [email protected], Everyone, okta-test-client, and okta-test-app, updates that record with the fields it sets. Users take okta_id, status, display_name, locale, and time_zone; groups take okta_id and type; apps take okta_id, status, sign_on_mode, settings, and credentials. OAuth clients take response_types, grant_types, and token_endpoint_auth_method. Authorization servers take description, status, and policies (see Access policies). group_memberships (group_okta_id, user_okta_id) and app_assignments (app_okta_id, user_okta_id) link seeded records, and signing_key pins the ID token key (see Signing key).

Conformance

See CONFORMANCE.md for every provider.