Multi-Factor Authentication

AuthGate supports multi-factor authentication (MFA) with TOTP authenticator apps, SMS codes, and backup codes. MFA adds a second verification step after primary authentication to protect user accounts.


Overview

MFA in AuthGate works as a second step after primary authentication (OAuth, email, SMS, or magic link). When MFA is enabled for a user, the primary auth step returns a challenge token instead of a JWT. The user must then complete the MFA challenge to receive their JWT and refresh token.

Key characteristics:

  • Three MFA methods — TOTP (authenticator apps), SMS codes, and backup codes
  • Per-project policies — off, optional, or required
  • Challenge-based flow — primary auth returns a challenge, MFA verification returns the JWT
  • Backward-compatible — when MFA is "off", existing JWT-only clients continue working unchanged
  • Rate-limited — MFA verification is limited to 5 attempts per 15 minutes per IP

MFA policies

MFA policy is configured per project and determines whether users are required to set up MFA.

  • Name
    off
    Type
    string
    Description

    MFA is disabled for the project. Primary authentication returns a JWT directly. This is the default policy and is fully backward-compatible with existing clients.

  • Name
    optional
    Type
    string
    Description

    Users may choose to enable MFA for their account. If enabled, they are challenged on sign-in. If not enabled, primary auth returns a JWT directly.

  • Name
    required
    Type
    string
    Description

    All users must set up MFA before they can complete authentication. After first sign-in, users are prompted to enroll in at least one MFA method.

When the policy is optional or required, the authentication response changes depending on whether the user has MFA enrolled:

  • No MFA enrolled (optional policy): returns { token, refresh_token, user } as normal
  • MFA enrolled: returns { challenge, methods } instead of a JWT
  • No MFA enrolled (required policy): returns { challenge, enrollment_required: true } to prompt setup

TOTP setup flow

TOTP (Time-based One-Time Password) works with authenticator apps like Google Authenticator, Authy, or 1Password. The setup flow has three steps:

1. Generate TOTP secret

Call the TOTP setup endpoint with a valid JWT to generate a secret and QR code.

curl -X POST https://auth.example.com/api/proxy/mfa/totp/setup \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

The response includes everything needed to configure the authenticator app:

{
  "secret": "JBSWY3DPEHPK3PXP",
  "qr_code": "data:image/png;base64,iVBORw0KGgo...",
  "uri": "otpauth://totp/AuthGate:jane@example.com?secret=JBSWY3DPEHPK3PXP&issuer=AuthGate"
}

Display the qr_code as an image for the user to scan, or show the secret for manual entry.

2. Verify setup

After the user scans the QR code and enters the code from their authenticator app, verify it:

curl -X POST https://auth.example.com/api/proxy/mfa/totp/verify-setup \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
{
  "success": true
}

This confirms the TOTP secret is correctly configured and enrolls the user in TOTP MFA.

3. Generate backup codes

After TOTP is verified, generate backup codes for the user:

curl -X POST https://auth.example.com/api/proxy/mfa/backup-codes/generate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
{
  "codes": [
    "a1b2c3d4",
    "e5f6g7h8",
    "i9j0k1l2",
    "m3n4o5p6",
    "q7r8s9t0",
    "u1v2w3x4",
    "y5z6a7b8",
    "c9d0e1f2",
    "g3h4i5j6",
    "k7l8m9n0"
  ]
}

Challenge flow

When a user with MFA enrolled signs in, the primary authentication step returns a challenge instead of a JWT. The challenge must be verified with an MFA code to complete authentication.

Step 1: Primary authentication

The user signs in with their primary method (e.g., email + password):

curl -X POST https://auth.example.com/api/proxy/email/signin \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "password": "securepassword",
    "callback_url": "https://myapp.com/auth/callback"
  }'

Instead of a JWT, the response indicates that MFA is required:

{
  "mfa_required": true,
  "challenge": "ch_a1b2c3d4e5f6...",
  "methods": ["totp", "sms", "backup_codes"]
}
  • Name
    mfa_required
    Type
    boolean
    Description

    Always true when MFA verification is needed.

  • Name
    challenge
    Type
    string
    Description

    An opaque challenge token. Pass this to the MFA verify endpoint. Expires in 5 minutes.

  • Name
    methods
    Type
    string[]
    Description

    The MFA methods the user has enrolled. Use this to display the appropriate UI.

Step 2: MFA verification

Submit the challenge token and the user's MFA code:

curl -X POST https://auth.example.com/api/proxy/mfa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "challenge": "ch_a1b2c3d4e5f6...",
    "code": "123456",
    "method": "totp"
  }'

On success, the response includes the JWT, refresh token, and user info:

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "rt_a1b2c3d4e5f6...",
  "user": {
    "id": "user_abc123",
    "email": "jane@example.com",
    "name": "Jane Doe",
    "email_verified": true
  }
}

The complete flow is: primary auth -> MFA challenge -> MFA verify -> JWT + refresh token.


SMS fallback

If a user has SMS as an MFA method, they can receive a verification code via SMS during the challenge flow.

Send SMS code

After receiving the challenge, request an SMS code:

curl -X POST https://auth.example.com/api/proxy/mfa/sms/send-code \
  -H "Content-Type: application/json" \
  -d '{"challenge": "ch_a1b2c3d4e5f6..."}'
{
  "success": true
}

The user receives a 6-digit code via SMS. They then verify it using the standard MFA verify endpoint with "method": "sms".

Enable SMS MFA

Users can enable SMS as an MFA method if they have a verified phone number on their account:

curl -X POST https://auth.example.com/api/proxy/mfa/sms/enable \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
{
  "success": true
}

Backup codes

Backup codes are 10 single-use codes that allow authentication when other MFA methods are unavailable (e.g., lost phone).

  • 10 codes generated at a time
  • Single-use — each code is consumed on use
  • Regeneration invalidates all existing codes
  • Use method "backup_codes" with the MFA verify endpoint
curl -X POST https://auth.example.com/api/proxy/mfa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "challenge": "ch_a1b2c3d4e5f6...",
    "code": "a1b2c3d4",
    "method": "backup_codes"
  }'

Dashboard configuration

MFA is configured per project in the AuthGate dashboard under Settings > Security > Multi-Factor Authentication.

Available settings

  • Name
    MFA policy
    Type
    off | optional | required
    Description

    Controls whether MFA is disabled, opt-in, or mandatory for all users. Defaults to "off".

  • Name
    Allowed methods
    Type
    string[]
    Description

    Which MFA methods are available to users. Choose from TOTP, SMS, and backup codes. Backup codes are always enabled when any other method is active.

Changing policies

  • off -> optional: No impact on existing users. Users can opt in to MFA at their own pace.
  • off -> required: Existing users will be prompted to enroll in MFA on their next sign-in.
  • required -> optional: Users who already enrolled keep their MFA. Users who haven't enrolled are no longer forced to.
  • required -> off: MFA is disabled for all users. Existing enrollments are preserved but not enforced.

Was this page helpful?