Skip to main content

Prerequisites

Before you can add MFA to a custom auth flow, you’ll need to complete the following steps:
1

Build out primary authentication

Before integrating MFA, you need to already have a primary authentication flow built out. You can do so by following one of the integration guides:
2

Configure enforced MFA for an Organization

To configure MFA, you’ll need to first toggle on “Require MFA” for at least one Organization in the Stytch Dashboard, or call the Update Organization API with mfa_policy set to REQUIRED_FOR_ALL.Each Organization can specify which mfa_methods are allowed for Members in their Organization: either ALL_ALLOWED or RESTRICTED. If RESTRICTED, Members can only use MFA methods specified in the allowed_mfa_methods array.For example, if an Organization requires Members to use TOTP MFA:
{
    "mfa_policy": "REQUIRED_FOR_ALL",
    "mfa_methods": "RESTRICTED",
    // Optional, not enforced if mfa_methods is ALL_ALLOWED
    "allowed_mfa_methods": ["totp"]
}

Integrating MFA

Stytch’s headless and backend SDKs provide a flexible way to integrate MFA into your authentication flow.

Required MFA enrollment

When an Organization’s MFA policy is REQUIRED_FOR_ALL but the Member is not currently enrolled in MFA, you’ll need to handle MFA enrollment after primary authentication.
1

Detect when MFA enrollment required

The following authentication methods will return a Member Session if the Member is not enrolled in MFA OR an intermediate_session_token and an mfa_required object that indicates MFA is required in order to be granted a Session for the Organization:If the Member is not yet enrolled in MFA, the response will look as follows:
{
    "intermediate_session_token": "oNJB3foIA79dn_uNVMNghG_MGkKSLHnR65NsKXv0gZzY",
    "mfa_required": {
        "member_options": null,
        "secondary_auth_initiated": null
    },
    "member_authenticated": false,
    "organization": {
        "mfa_methods": "REQUIRED_FOR_ALL",
        "allowed_mfa_methods": ["sms_otp", "totp"]
    }
    ...
}
Indicating that member_authenticated: false due to mfa_required — but no member_options are present. The Stytch SDK will automatically store the intermediate_session_token and handle passing that with subsequent calls to tie the user’s primary and secondary authentication together.For the primary authentication endpoint(s) you’ve already integrated add handling to detect when MFA enrollment is required by checking to see if the organization.mfa_methods is ALL_ALLOWED or RESTRICTED. If RESTRICTED use the organization.allowed_mfa_methods to surface only allowed MFA methods to the user.Check the organization.allowed_mfa_methods and based on the available options, prompt the user to enroll in MFA.
2

Start MFA enrollment

For SMS MFA, prompt the user for their phone number and call the SMS Send method with the number. Make sure the mfa_phone_number is in the E.164 format, e.g. “+14155551234”.
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';

export const SendOtp = () => {
  const stytchClient = useStytchB2BClient();
  const [phoneNumber, setPhoneNumber] = useState('');

  const trigger = useCallback(() => {
    stytchClient.otps.sms.send({
      organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
      member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c2cfb6e1bd7d',
      mfa_phone_number: phoneNumber,
    });
  }, [stytchClient, phoneNumber]);

  return (
    <div>
      <input
        type="tel"
        placeholder="Enter phone number"
        value={phoneNumber}
        onChange={(e) => setPhoneNumber(e.target.value)}
      />
      <button onClick={trigger}>Send OTP</button>
    </div>
  );
};
3

Authenticate to complete enrollment

Prompt the user to input their OTP or TOTP code in order to complete the MFA enrollment and authenticate.
import { useCallback, useState } from 'react';
import { useStytchB2BClient } from '@stytch/react/b2b';

export const Authenticate = () => {
  const stytch = useStytchB2BClient();
  const [code, setCode] = useState('');

  const authenticate = useCallback(
    (e) => {
      e.preventDefault();
      stytch.otps.sms.authenticate({
        member_id: 'member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f',
        organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
        code: code,
        session_duration_minutes: 60,
      });
    },
    [stytch, code],
  );

  const handleChange = useCallback((e) => {
    setCode(e.target.value);
  }, []);

  return (
    <form>
      <label htmlFor="otp-input">Enter code</label>
      <input id="otp-input" autoComplete="one-time-code" inputMode="numeric" pattern="[0-9]*" onChange={handleChange} />
      <button onClick={authenticate} type="submit">
        Submit
      </button>
    </form>
  );
};
4

Test it out

Run your application and attempt to login to your Organization with the REQUIRED_FOR_ALL MFA policy to test out required enrollment in MFA.