This guide shows how to implement SMS OTP for MFA. You can follow the same approach for step-up auth or adaptive MFA.

Configure SMS OTP in the Authsignal Portal

  1. Navigate to the Authenticators section and click Manage SMS OTP.
  2. Choose and set up an SMS Provider you want to use in the next screen. Then click Connect Account.

Grab your Authsignal credentials

Head to Settings and grab your Tenant ID, API URL and API secret key. Add them as environment variables in your project:
AUTHSIGNAL_API_URL=your_region_api_url
AUTHSIGNAL_TENANT_ID=your_tenant_id
AUTHSIGNAL_SECRET_KEY=your_secret_key

Implementation

1. Backend - Track an action

When a user performs an action that requires authentication, your backend should track the action. You can use our Server SDK or Server API to track the action. The code snippets in this guide references the SDKs.
import { Authsignal } from '@authsignal/node';

const authsignal = new Authsignal({
  apiSecretKey: process.env.AUTHSIGNAL_SECRET_KEY,
});

// Track the sign-in action
const trackResponse = await authsignal.track({
  userId: 'dc58c6dc-a1fd-4a4f-8e2f-846636dd4833',
  action: 'signIn',
  attributes: { phoneNumber }, // Required for SMS OTP
});

// Handle different action outcomes
if (trackResponse.state === 'CHALLENGE_REQUIRED') {
  // User needs to complete SMS OTP challenge
  return { token: trackResponse.token };
} else if (trackResponse.state === 'ALLOW') {
  // Proceed with the action - no challenge needed
  return { success: true };
} else if (trackResponse.state === 'BLOCK') {
  // Block the action for security reasons
  return { error: 'Action blocked for security reasons' };
}
Understanding action statesWhen you track an action, Authsignal returns one of four possible states:
  • CHALLENGE_REQUIRED - User must complete an authentication challenge (proceed to step 2)
  • ALLOW - Action is permitted without additional authentication
  • BLOCK - Action is blocked for security reasons
  • REVIEW - Action requires manual review
Learn more about action outcomes.

2. Frontend - Challenge the user

If the action state is CHALLENGE_REQUIRED, proceed with the SMS OTP challenge using either our Web SDK, Mobile SDKs or Client API.
import { Authsignal } from '@authsignal/browser';

const authsignal = new Authsignal({
  tenantId: 'YOUR_TENANT_ID',
});

// Set the token from the track response
authsignal.setToken(token);

// Send the SMS OTP challenge
const challengeResponse = await authsignal.sms.challenge();

// After user enters the OTP code in your UI
const verifyResponse = await authsignal.sms.verify({ 
  code: otpCode 
});

// Get the verification token to validate on your backend
if (verifyResponse.data?.isVerified) {
  const verificationToken = verifyResponse.data.token;
}

3. Backend - Validate the challenge

After the user completes the challenge, validate the token on your backend:
import { Authsignal } from '@authsignal/node';

const authsignal = new Authsignal({
  apiSecretKey: process.env.AUTHSIGNAL_SECRET_KEY,
});

// Validate the challenge token
const validationResult = await authsignal.validateChallenge({ token });

if (validationResult.isValid) {
  // Authentication successful - proceed with user session creation
  return { success: true };
}
That’s it! You’ve successfully implemented SMS OTP authentication with Authsignal.

Use WhatsApp as a fallback

WhatsApp can be used as a cost-effective fallback option for SMS delivery, helping reduce messaging costs while maintaining reliable OTP delivery. You can configure it in the Authsignal Portal.
  1. Navigate to the Authenticators section and click Manage SMS OTP.
  2. Scroll down to WhatsApp for Business and enter your WhatsApp Channel ID and Project ID. Then click Connect WhatsApp.

Next steps

  • Adaptive MFA - Set up smart rules to trigger authentication based on risk
  • Email OTP - Add email-based OTP codes as an alternative method
  • Email magic link - Implement passwordless email authentication
  • Passkeys - Offer the most secure and user-friendly passwordless authentication