Skip to main content
Authsignal SDKs can be used to implement email OTP challenges in two scenarios.
  1. Sign-in. Use our Server SDKs to authenticate users with email OTP as the 1st factor. This integration only requires an email address to initiate.
  2. Adaptive MFA. Use Server SDKs together with Client SDKs to authenticate users with email OTP as a secondary factor. This integration requires a user ID to initiate and assumes the user has already been authenticated with a primary factor.

Portal setup

  1. Navigate to Authenticators in the Authsignal Portal, find email OTP, and click Set up.
  2. Choose an email provider. You can choose Authsignal for development but it’s recommended to use an alternative provider in production for more control over templates and delivery.

SDK setup

Server SDK

Initialize the SDK using your secret key from the API keys page and the API URL for your region.
import { Authsignal } from "@authsignal/node";

const authsignal = new Authsignal({
  apiSecretKey: "YOUR_SECRET_KEY",
  apiUrl: "YOUR_API_URL",
});

Client SDK

Initialize the Web SDK or Mobile SDK using your tenant ID from the API keys page and your API URL.
import { Authsignal } from "@authsignal/browser";

const authsignal = new Authsignal({
  tenantId: "YOUR_TENANT_ID",
  baseUrl: "YOUR_API_URL",
});

Sign-in

Scenario - Let users sign-in with email OTP as the 1st factor.
Our Server SDKs include methods to initiate and verify an OTP challenge for a given email address. These methods are well-suited for passwordless sign-in scenarios where you need to authenticate a user with email OTP as a primary factor.

1. Initiate challenge

Call Initiate Challenge to send an OTP to an email address.
const request = {
  verificationMethod: "EMAIL_OTP",
  action: "signInWithEmail",
  email: "jane.smith@authsignal.com",
};

const response = await authsignal.challenge(request);

const challengeId = response.challengeId;
You can choose a value for the action here which best describes what the user is doing in your app (e.g. signing in with email). It will be used to track user activity in the Authsignal Portal.

2. Verify challenge

Once the user inputs the OTP code, call Verify Challenge to verify it.
const request = {
  challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
  verificationCode: "123456",
};

const response = await authsignal.verify(request);

const isVerified = response.isVerified;

3. Claim challenge

Now that the challenge has been verified, you can lookup the user in your IdP or DB based on their email. For passwordless flows with a combined sign-up and sign-in UX, you may need to create the user at this point if no account exists. Then claim the challenge once you know the primary user ID associated with the email.
const request = {
  challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
};

const response = await authsignal.claimChallenge(request);
This final step is important because it attributes activity to the correct user and ensures observability in the Authsignal Portal.

Adaptive MFA

Scenario - Challenge users with email OTP as a 2nd factor and use rules to decide when and where in your app to trigger the challenge.
The following steps demonstrate how to implement adaptive MFA with email OTP - either at sign-in or as step-up authentication when the user performs a sensitive action in your app (e.g. making a payment). In this scenario we assume the user has already been identified by authenticating with another method (e.g. username and password) and is being prompted to complete an email OTP challenge as a 2nd factor.

1. Track action

Use a Server SDK to track an action in your backend. This step can apply rules to determine if a challenge is required.
  • Custom UI
  • Pre-built UI
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  action: "signIn",
  attributes: {
    email: "jane.smith@authsignal.com",
  },
};

const response = await authsignal.track(request);

if (response.state === "CHALLENGE_REQUIRED") {
  // Return token to your frontend to present challenge
  const token = response.token;
}
You can choose a value for the action here which best describes what the user is doing in your app (e.g. signIn or createPayment). Each action can have its own set of rules. To learn more about using rules and handling different action states refer to our documentation on actions and rules.

2. Present challenge

If the action state is CHALLENGE_REQUIRED then you can present an email OTP challenge using the Web SDK or Mobile SDK.
  • Custom UI
  • Pre-built UI
// Set token from the track response
authsignal.setToken("eyJhbGciOiJ...");

// Send the user an email OTP code
// You can call this multiple times via a 'resend' button
await authsignal.email.challenge();

// Verify the inputted code matches the original code
const response = await authsignal.email.verify({ code: "123456" });

// Obtain a new token
const token = response.token;

3. Validate action

Use the new token obtained from the client SDK to validate the action on your backend.
const response = await authsignal.validateChallenge({
  action: "signIn",
  token: "eyJhbGciOiJIUzI....",
});

if (response.state === "CHALLENGE_SUCCEEDED") {
  // User completed challenge successfully
}
If the action state shows that the email OTP challenge was completed successfully, you can let the user proceed with the action.

Enrollment

Scenario - Enroll users in email OTP while they’re authenticated so it can be used later as a method for adaptive MFA.
To use email OTP for adaptive MFA, users must be enrolled with email OTP as an authentication method. This means their email address has previously been verified and can be trusted. The following steps demonstrate how to implement an enrollment flow using a Server SDK.

1. Initiate challenge

Call Initiate Challenge to send an OTP to an email address.
const request = {
  verificationMethod: "EMAIL_OTP",
  action: "enrollEmail",
  email: "jane.smith@authsignal.com",
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  scope: "add:authenticators",
};

const response = await authsignal.challenge(request);

const challengeId = response.challengeId;
The add:authenticators scope is required to enroll a new email OTP authenticator for an existing user. This scope should only be used when the user is in an already authenticated state. For more information on using scopes safely refer to our documentation on authenticator binding.

2. Verify challenge

Once the user inputs the OTP code, call Verify Challenge to verify it.
const request = {
  challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
  verificationCode: "123456",
};

const response = await authsignal.verify(request);

const isVerified = response.isVerified;

Update email

Scenario - Let users update their email address while they’re authenticated, completing an OTP challenge to verify the new email.
The following steps demonstrate how to implement an update email address flow using a Server SDK.

1. Initiate challenge

Call Initiate Challenge to send an OTP to the user’s new email address.
const request = {
  verificationMethod: "EMAIL_OTP",
  action: "updateEmail",
  email: "jane.smith@authsignal.com",
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  scope: "update:authenticators",
};

const response = await authsignal.challenge(request);

const challengeId = response.challengeId;
The update:authenticators scope is required to update a user’s existing email OTP authenticator to change the email address. This scope should only be used when the user is in an already authenticated state. For more information on using scopes safely refer to our documentation on authenticator binding.

2. Verify challenge

Once the user inputs the OTP code, call Verify Challenge to verify it.
const request = {
  challengeId: "3a991a14-690c-492b-a5e5-02b9056a4b7d",
  verificationCode: "123456",
};

const response = await authsignal.verify(request);

const isVerified = response.isVerified;

Verified emails

Scenario - Enroll or update an email OTP authenticator for a user when you’ve already verified their email address in another system, so it can be used later as a method for adaptive MFA.
In some cases you may have already verified a user’s email address using another system. This means the user can be enrolled without having to complete an email OTP challenge.
const request = {
  userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
  attributes: {
    verificationMethod: "EMAIL_OTP",
    email: "jane.smith@authsignal.com",
  },
};

const response = authsignal.enrollVerifiedAuthenticator(request);
This same call can also be used to update a verified email address to a new value which has also been verified externally.

Next steps

  • Pre-built UI - Rapidly deploy email OTP challenges using our pre-built UI
  • Web SDK - Implement email OTP challenges while building your own UI
  • Mobile SDK - Implement email OTP challenges in native mobile apps
  • Adaptive MFA - Set up smart rules to trigger authentication based on risk
  • Passkeys - Offer the most secure and user-friendly passwordless authentication
I