Two-factor authentication

Add a second factor in two calls.

Set up in:
Cursor

Two-factor authentication adds a second proof on top of a password: a one-time code on a channel the user controls. With Bird Verify it's a send call at the moment of login and a check call when the user types the code back: SMS, WhatsApp, or email today, per-country channel config, and nothing to store between the two.

verify.ts
200 · pending
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({
  apiKey: process.env.BIRD_API_KEY!,
});

// Send the code, then check it by recipient.
await bird.verify.verifications.create({
  to: { phone_number: "+15551234567" },
}).safe();

const { data } = await bird.verify.verifications.check({
  to:   { phone_number: "+15551234567" },
  code: userInput,
}).safe();

2FA is the verification flow, pointed at login.

Under the hood, 2FA is the same flow the Bird Verify API runs everywhere: create a verification for the user's phone or email when they sign in, then check the code they enter. Because the check is by recipient, your login handler keeps no per-attempt state, and the channel a given user gets is decided by your per-country configuration, not hard-coded in the flow. Drop the password in front of it and the same flow becomes passwordless login.

What you wire up for 2FA.

Two calls and a configuration.

  1. 01

    Send on login, check on submit.

    Create a verification when the user authenticates with their first factor; check the code when they enter it. That's the whole integration.

  2. 02

    SMS, WhatsApp, or email as the factor.

    Use the channel you already have for the user: a phone number over SMS or WhatsApp, an email over email. Voice is rolling out for more options.

  3. 03

    Per-app configuration.

    Keep a separate configuration for login versus signup versus high-value actions, each with its own code rules and channel plan.

  4. 04

    Brute force is bounded.

    Attempt lockout and per-recipient send caps come built in, so a second factor doesn't become a new attack surface.

The login-time flow.

Send the code once the password checks out; verify it when the user submits. A wrong code is a result you branch on, not an exception to catch.

two-factor.ts
200
// Send the second factor once the password checks out.
await bird.verify.verifications.create({
  to: { phone_number: user.phone },
}).safe();

const { data } = await bird.verify.verifications.check({
  to:   { phone_number: user.phone },
  code: submitted,
}).safe();

if (data.result) grantSession(user);

Two-factor authentication, on the same API as your other channels.

Bird Verify is the code-based factor for your login and signup: SMS, email, and WhatsApp now, voice rolling out.

Start with one channel.
Add the others when you're ready.

A test API key is yours immediately. Production unlocks when you add a payment method and verify a sender.

Using Claude Code, Cursor, or Codex? Copy a setup prompt and your agent installs the Bird CLI and skills for you. Pick yours:

Cursor