❕ The use of this module requires usage of the Okta Identity Engine. This functionality is in general availability but is being gradually rolled out to customers. If you want to request to gain access to the Okta Identity Engine, please reach out to your account manager. If you do not have an account manager, please reach out to oie@okta.com for more information.
This module is built to communicate with Okta as an OAuth 2.0 + OpenID Connect provider. It works with Okta's Identity Engine to authenticate and register users.
To see this library working in a sample, check out our Embedded auth with SDKs sample.
The IDX API is built on the [Okta Identity Engine][]. The API allows applications to implement features which were not possible with the older authn API, such as multi-factor authentication without redirection to Okta. We recommend that all new application deployments use the IDX API. Existing applications can migrate from authn to IDX by following this guide.
See detail in Using the npm module.
This module provides convenience methods to support popular scenarios to communicate with Okta's Identity Engine.
❕ This distinction is only relevant in
auth-js@8.x. Previous versions always execute in, what is now known as, "Legacy Mode".
The IDX client was developed to be flexible and to enable a couple different invocation patterns to ease use. Over time (and as the IDX API has evolved), we have seen inconsistency in these various patterns. As of auth-js@8.x, each step (remediation) in a given flow will need to be explicitly specificed. This may required more code, but it makes client behavior consistent.
Step Mode is the new default mode as of auth-js@8.x. Step Mode requires each call to idx.proceed to include either an actions or step property, like so
const response = await idx.proceed({ step: 'identify', username: 'foo@bar.com' })The step (or actions) property names the remediation to run. The IDX client will execute that remediation and return the response; it will not automatically perform subsequent (recursive) remediations. You must call idx.proceed for each step.
A handful of features will no longer work in Step Mode, including:
Legacy Mode preserves pre-8.x behavior and is on a deprecation path. Use it only as a temporary migration aid.
Legacy Mode enables other features, which no longer work in Step Mode, including:
However these patterns can be a bit inconsistent (which ultimately led to the decision to phase them out).
Legacy Mode can be enabled when constructing OktaAuth or on a per-call basis when calling idx.proceed.
const oktaAuth = new OktaAuth({
...config,
idx: { enableLegacyMode: true }
})
// OR
const response = await idx.proceed({
username: 'foo@bar.com',
enableLegacyMode: true
})
⚠️ Issues reported while using Legacy Mode will be lower priority, but we will still accept community contributions.
⚠️ As ofauth-js@8.x,flowis not supported in the defaultIDXclient.
Prior to auth-js@8.x, the flow feature allowed bootstrapping an IDX transaction to a specific user experience (for example, { flow: 'unlock-account' }). Internally this worked by proceeding with a sequence of specific remediations, which required hardcoding remediation names for each flow value. As IDX evolved, that approach became brittle and increased bundle size. As a result, flow is deprecated and is not enabled in the default auth-js@8.x IDX client. Instead of flow, call idx.proceed({ step: '...' }) to drive the IDX client into the desired state; the exact steps depend on your Okta org configuration. (You can re-enable the old behavior by using Legacy Mode, but that is not recommended long-term.)
async function bootstrapUnlockAccount () {
await idx.start();
// NOTE: The steps needed will depend on your Okta Org configuration
const response = await idx.proceed({ step: 'unlock-account' });
}Alternatively, Legacy Mode can be used to re-enable flow, however this approach is not recommended long term
Prior to
auth-js@8.xor Legacy Mode
In addition to the default authentication flow, this SDK supports several pre-defined flows, such as register, recoverPassword and unlockAccount. A flow can be started by calling one of the available flow entrypoints or by passing a valid flow identifier string to startTransaction. The flow is saved with the transaction which enables the proceed method to corrrectly handle remediations without additional context. Starting a new flow discards any existing in-progress transaction of a different type. For example, if an authentication flow is in-progress, a call to authenticate or proceed will continue using the current transaction but a call to a flow entrypoint will start a new transaction.
// a recover password flow can be started by calling the `recoverPassword` entrypoint
await authClient.idx.recoverPassword();
const flow = authClient.idx.getFlow(); // "recoverPassword"
// or by passing the flow identifier to `startTransaction`
await authClient.idx.startTransaction({ flow: 'recoverPassword' })The flow is set automatically when calling one of these methods:
The flow will be set to default unless otherwise specified in idx.startTransaction
A redirect callback occurrs when your app is reloaded in the browser as part of a flow. During a redirect callback, the app is loaded at a specific URL path that you have defined in your Okta App configuration. Most callbacks can only be handled once and will produce an error if there is an attempt to handle it twice. Typically, the app will redirect itself to a well known or previously saved URL path after the callback logic has been handled to avoid errors on page reload.
Note: Most apps should be prepared to handle one or more redirect callbacks. Depending on how the App sign-on policy is configured, some SPA applications may be able to receive tokens without any redirect. However, logic will need to be added if the policy includes signing in with a Social / IDP provider)) or allows account recovery using email.
All web applications will handle an OAuth callback which includes an interaction_code query parameter. SPA applications can receive an interactionCode directly from an IDX Response. However, they may still need to implement OAuth callback logic if the sign-on policy includes signing in with a Social / IDP provider.
// https://myapp.mycompany.com/login/callback?interaction_code=ABC&state=XYZ
if (authClient.isLoginRedirect()) {
await authClient.handleLoginRedirect();
}After signing in with a 3rd party IDP, the user is redirected back to the application. If no further input is needed from the user, then this will be an OAuth callback containing an interaction_code parameter. If further input is required, then the callback will contain an error parameter with the value interaction_required
const search = window.location.search;
if (authClient.idx.isInteractionRequired(search)) {
await authClient.idx.proceed();
}After the user clicks the link in an email, they are redirected back to the application. The query parameters include state and otp
const { search } = window.location;
if (authClient.idx.isEmailVerifyCallback(search)) {
try {
// Proceed, if possible. Throws an `EmailVerifyCallbackError` if proceed is not possible.
const { status, nextStep, tokens } = await authClient.idx.handleEmailVerifyCallback(search);
if (status === IdxStatus.SUCCESS) {
// user has successfully authenticated
authClient.tokenManager.setTokens(tokens);
} else {
// follow nextStep to see what is needed to proceed
}
} catch (e) {
if (authClient.idx.isEmailVerifyCallbackError(e)) {
const { otp, state } = e;
// you can add special handling for the email verify callback error
// by default it will have a message that says
// `Enter the OTP code in the originating client: ${otp}`
}
console.log(e.message);
}
}You can work with these methods with Up-Front and On-Demand approaches, normally a mix of both approaches will be needed when user inputs are required in the middle of the flow (e.g. multiple factors auth). Below are the general explanation of these two approaches, more code examples will be provided with the specific methods.
⚠️ As ofauth-js@8.x, the Up-Front approach is no longer recommended and is only supported in Legacy Mode.
You can provide parameters based on your app's policy configuration and user inputs to drive the methods to communicate with Okta's Identity Engine.
You can provide minimum or even no parameters to call the methods, and the methods can provide direction for the flow by indicating the nextStep and required inputs.
Most methods in this module resolve IdxTransaction as the response. With Okta's Identity Engine's dynamic nature, it's very important to understand how to follow the response to proceed to the next step.
This field indicates the status of the current flow.
IdxStatus.SUCCESS: This status indicates the flow has ended successfully with tokens.IdxStatus.PENDING: This status indicates the flow is still in progress, checknextStepandmessages(intermediate form errors) fields in the response to proceed.IdxStatus.FAILURE: This status indicates error has happened in SDK level, checkerrorfield in the response for error handling.IdxStatus.TERMINAL: This status indicates the flow has run into aterminalstate, checkmessagesfield to handle it.IdxStatus.CANCELED: This status indicates the flow has been canceled. It's normally the response status ofidx.cancel.
This field contains information to proceed with the next step. It's available when in IdxStatus.PENDING status.
-
name: The identifier of the next step. -
type?: The type of the authenticator that the step belongs to. -
authenticator?: The authenticator that the step belongs to. -
canSkip?: This field indicates if the step is skippable or not. -
inputs?: parameters for the next step.// get "inputs" from the response // inputs: [{ name: 'username', label: 'Username' }] const { nextStep: { inputs } } = await authClient.idx.authenticate(); // gather user inputs (this call should happen in a separated request) const transaction = await authClient.idx.authenticate({ username: 'from user input' });
It's available with IdxStatus.SUCCESS status. Provides tokens set based on scopes configuration.
It passes back messages from Okta's Identity Engine. Form message and Terminal message both come to this field.
It's avaialbe with IdxStatus.FAILURE status when the SDK run into unhandlable state.
It provides transaction meta (pkce meta, interactionHandle, etc.).
It indicates what features are available based on the app / org policy configuration.
It provides information for available next steps.
The convenience method for starting an authentication flow.
Example (Two factors auth with email authenticator):
Up-Front:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'verificationCode', ... }]
}
} = await authClient.idx.authenticate({
username: 'xxx',
password: 'xxx',
authenticators: [AuthenticatorKey.OKTA_EMAIL /* 'okta_email' */]
});
// submit verification code from email
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.proceed({ verificationCode: 'xxx' });On-Demand:
// start the flow with no param
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'username', ... }, { name: 'password', ... }]
}
} = await authClient.idx.authenticate();
// gather user inputs
const {
status, // IdxStatus.PENDING
nextStep: {
inputs, // [{ name: 'authenticator', ... }]
}
} = await authClient.idx.proceed({ username: 'xxx', password: 'xxx' });
// a list of authenticators is shown and the user selects "email"
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'verificationCode', ... }]
}
} = await authClient.idx.proceed({ authenticator: AuthenticatorKey.OKTA_EMAIL /* 'okta_email' */ });
// gather verification code from email (this call should happen in a separated request)
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.proceed({ verificationCode: 'xxx' });The convenience method for starting a self service registration flow.
Example (Registration with password authenticator enrollment)
Up-Front:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'password', ... }]
}
} = await authClient.idx.register({
firstName: 'xxx',
lastName: 'xxx',
email: 'xxx',
authenticators: [AuthenticatorKey.OKTA_PASSWORD /* 'okta_password' */]
});
// submit password
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.proceed({ password: 'xxx' });On-Demand:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'firstName', ... }, { name: 'lastName', ... }, { name: 'email', ... }]
}
} = await authClient.idx.register();
// submit user inputs and select password authenticator
const {
status, // IdxStatus.PENDING
nextStep: {
inputs, // [{ name: 'authenticator', ... }]
}
} = await authClient.idx.proceed({
firstName: 'xxx',
lastName: 'xxx',
email: 'xxx',
authenticator: 'password'
});
// select authenticator (this call should happen in a separated request)
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'password', ... }]
}
} = await authClient.idx.proceed({ authenticator: AuthenticatorKey.OKTA_PASSWORD /* 'okta_password' */ });
// gather password from user input (this call should happen in a separated request)
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.proceed({ password: 'xxx' });Account activation:
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.register({
activationToken: 'xxxxx',
authenticator: 'okta_password',
password: 'xxx'
});The convenience method for starting a self service password recovery flow. See Email Verify Callback for more detailed information on this flow.
Example (Password recovery with email authenticator verification)
Up-Front:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'verificationCode', ... }]
}
} = await authClient.idx.recoverPassword({
username: 'xxx',
authenticators: [AuthenticatorKey.OKTA_EMAIL /* 'okta_email' */]
});
// submit verification code
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'password', ... }]
}
} = await authClient.idx.proceed({ verificationCode: 'xxx' });
// submit new password
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.proceed({ password: 'xxx' });On-Demand:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'username', ... }]
}
} = await authClient.idx.recoverPassword();
// gather username from user input
const {
status, // IdxStatus.PENDING
nextStep: {
inputs, // [{ name: 'authenticator', ... }]
}
} = await authClient.idx.proceed({ username });
// user sees a list of authenticators and selects "email"
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'verificationCode', ... }]
}
} = await authClient.idx.proceed({ authenticator: AuthenticatorKey.OKTA_EMAIL /* 'okta_email' */ });
// gather verification code from email (this call should happen in a separated request)
const {
status,
nextStep: {
inputs // [{ name: 'password', ...}]
}
} = await authClient.idx.proceed({ verificationCode: 'xxx' });
// submit new password
const {
status, // IdxStatus.SUCCESS
tokens
} = await authClient.idx.proceed({ password: 'xxx' });The convenience method for starting a self service account recovery` flow.
Example (Account unlock with email authenticator verification)
Up-Front:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'verificationCode', ... }]
}
} = await authClient.idx.unlockAccount({
username: 'xxx',
authenticators: [AuthenticatorKey.OKTA_EMAIL /* 'okta_email' */]
});
// submit verification code
const {
status, // IdxStatus.TERMINAL
messages // 'Your Account is now unlocked!'
} = await authClient.idx.proceed({ verificationCode: 'xxx' });On-Demand:
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'username', ... }]
}
} = await authClient.idx.unlockAccount();
// gather username from user input and
// user sees a list of authenticators and selects "email"
const {
status, // IdxStatus.PENDING
nextStep: {
inputs, // [{ name: 'authenticator', ... }]
}
} = await authClient.idx.proceed({ username, authenticator: AuthenticatorKey.OKTA_EMAIL });
// gather verification code from email (this call should happen in a separated request)
const {
status, // IdxStatus.TERMINAL
messages // 'Your Account is now unlocked!'
} = await authClient.idx.proceed({ verificationCode: 'xxx' });Alias for idx.startTransaction
Resolves Start Transaction related fields.
Popular use cases:
- Single entry point when the [flow][] is dynamic
- Provides
metafor Okta Sign-In Widget integration. - Provides
enabledFeaturesandavailableStepsfor dynamic UI rendering.
// start a recoverPassword transaction
await authClient.idx.startTransaction({ flow: 'recoverPassword' })Cancels the in progress idx interaction transaction.
await authClient.idx.cancel();Continues an in-progress idx transaction. If there is no saved transaction, this method will throw. To check for the existence of an in-progress idx transaction, use idx.canProceed. When provided with {step: stepName}, continues transaction at specified remediation (step).
if (authClient.idx.canProceed()) {
await authClient.idx.proceed();
}Returns true if there is a saved in-progress idx transaction. To test against shared browser storage, pass state.
// checks against sessionStorage, can only proceed in same tab
authClient.idx.canProceed();
// will check against shared localStorage, allows proceeding in another tab if the state matches
authClient.idx.canProceed({ state });Resumes saved transaction and fires poll request to Okta API. Performs single poll request by default. Front-end clients can pass { refresh: millisecondsInt } parameter, which enables polling to continue until it is considered complete by server or session times out. refresh value is available through the nextStep property of in-progress transaction.
let idxTransaction = await authClient.idx.register();
// ...
// render view that matches current IDX stage
// ...
const pollOptions = idxTransaction?.nextStep?.poll;
if (pollOptions.required) {
authClient.idx.poll(pollOptions.refresh).then(pollResult => {
// render view matching poll result
});
}Returns the identifier for the currently configured flow.
const flow: FlowIdentifier = authClient.idx.getFlow();The flow is usually set automatically when calling one of the flow entrypoints or startTransaction. This method can be used to manually set the flow:
authClient.idx.setFlow('register');Handles interaction_code from Okta's Identity Engine's callback url. It exchanges the code for tokens and saves them in storage.
try {
// handle interactionCode and save tokens
await authClient.idx.handleInteractionCodeRedirect(callbackUrl);
} catch (err) {
if (authClient.idx.isInteractionRequiredError(err)) {
// need to proceed with required authenticator/s
}
throw err;
}