Skip to main content

Multi-Factor Authentication

YorAuth supports TOTP (Time-Based One-Time Password) MFA, compatible with any standard authenticator app including Google Authenticator, Authy, and 1Password. When enabled, users must provide a 6-digit code from their authenticator app to complete login.

All MFA management endpoints require a valid JWT access token and the authenticated user must own the {userId} in the path.

Setup Flow

Enabling TOTP for a user is a two-step process:

  1. Setup — Generate a TOTP secret and provisioning URI (for QR code display).
  2. Confirm — Verify a code from the authenticator app to activate MFA. Returns backup codes.

Step 1: Start TOTP Setup

Generate a TOTP secret and provisioning URI. The method is not active until confirmed.

Endpoint: POST /api/v1/applications/{applicationId}/users/{userId}/mfa/totp/setup

Authentication: JWT Bearer token required.

Request Body

FieldTypeRequiredDescription
labelstringNoA human-readable label for this authenticator, shown in status. Defaults to "Authenticator App".
bash
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/550e8400-e29b-41d4-a716-446655440000/mfa/totp/setup \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"label": "My Phone"}'
javascript
const response = await fetch(
  `https://api.yorauth.com/api/v1/applications/your-application-id/users/${userId}/mfa/totp/setup`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken}`
    },
    body: JSON.stringify({ label: 'My Phone' })
  }
);
const data = await response.json();

Response 200 OK

json
{
  "data": {
    "method_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "provisioning_uri": "otpauth://totp/jane%40example.com?secret=BASE32SECRET&issuer=MyApp",
    "secret": "JBSWY3DPEHPK3PXP"
  }
}
FieldDescription
method_idUUID for this MFA method. Required for the confirm step.
provisioning_uriOTPAuth URI. Pass this to a QR code library for display.
secretThe raw TOTP secret. Show this as a fallback for manual entry.

Display the provisioning_uri as a QR code so users can scan it with their authenticator app. Also show the secret in plain text for users who cannot scan QR codes.

Displaying the QR code (JavaScript):

javascript
import QRCode from 'qrcode';

const canvas = document.getElementById('qr-code');
await QRCode.toCanvas(canvas, data.data.provisioning_uri);

Step 2: Confirm TOTP Setup

Verify a code from the authenticator app to activate MFA. If successful, MFA is enabled and backup codes are returned.

Endpoint: POST /api/v1/applications/{applicationId}/users/{userId}/mfa/totp/confirm

Authentication: JWT Bearer token required.

Request Body

FieldTypeRequiredDescription
method_idstring (UUID)YesThe method_id returned from the setup endpoint.
codestringYesA 6-digit TOTP code from the authenticator app.
bash
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/550e8400-e29b-41d4-a716-446655440000/mfa/totp/confirm \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{
    "method_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "code": "123456"
  }'
javascript
const response = await fetch(
  `https://api.yorauth.com/api/v1/applications/your-application-id/users/${userId}/mfa/totp/confirm`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken}`
    },
    body: JSON.stringify({
      method_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
      code: '123456'
    })
  }
);
const data = await response.json();

Response 201 Created

json
{
  "data": {
    "message": "MFA has been enabled successfully.",
    "backup_codes": [
      "ABCD-EFGH-IJKL",
      "MNOP-QRST-UVWX",
      "YZ12-3456-7890",
      "AAAA-BBBB-CCCC",
      "DDDD-EEEE-FFFF",
      "GGGG-HHHH-IIII",
      "JJJJ-KKKK-LLLL",
      "MMMM-NNNN-OOOO",
      "PPPP-QQQQ-RRRR",
      "SSSS-TTTT-UUUU"
    ]
  }
}

Backup codes are only shown once. Store them immediately. Each code can only be used once to log in if the authenticator app is unavailable.

Error Responses

HTTP StatusError CodeDescription
422MFA_INVALID_CODEThe TOTP code is incorrect or expired.

MFA Challenge During Login

When a user with MFA enabled logs in with email/password, the login response returns a challenge instead of tokens:

json
{
  "data": {
    "mfa_required": true,
    "challenge_token": "mfa_challenge_xyz789abc",
    "mfa_methods": ["totp"]
  }
}

The challenge_token expires after 5 minutes. Submit it along with the TOTP code to complete login.

Endpoint: POST /api/v1/applications/{applicationId}/users/mfa/verify

This endpoint does not require authentication — authentication is completed by this call.

Request Body

FieldTypeRequiredDescription
challenge_tokenstringYesThe token returned from the login endpoint.
codestringYesA 6-digit TOTP code or a backup code (up to 12 characters).
bash
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/mfa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "challenge_token": "mfa_challenge_xyz789abc",
    "code": "123456"
  }'
javascript
const response = await fetch(
  'https://api.yorauth.com/api/v1/applications/your-application-id/users/mfa/verify',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      challenge_token: 'mfa_challenge_xyz789abc',
      code: '123456'
    })
  }
);
const data = await response.json();

Response 200 OK

On success, returns the same token pair as a standard login:

json
{
  "data": {
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "ref_a1b2c3d4e5f6...",
    "token_type": "Bearer",
    "expires_in": 900,
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "jane@example.com",
      "name": "Jane Doe",
      "email_verified": true,
      "created_at": "2026-02-25T10:00:00Z",
      "metadata": null
    }
  }
}

Error Responses

HTTP StatusError CodeDescription
401AUTH_INVALID_MFA_CODEThe TOTP code or backup code is incorrect.
410AUTH_MFA_CHALLENGE_EXPIREDThe challenge token has expired (5-minute window). Start login again.
429AUTH_MFA_LOCKEDToo many incorrect attempts on this challenge.

Get MFA Status

Retrieve the MFA status for a user, including active methods and remaining backup code count.

Endpoint: GET /api/v1/applications/{applicationId}/users/{userId}/mfa/status

Authentication: JWT Bearer token required.

bash
curl https://api.yorauth.com/api/v1/applications/your-application-id/users/550e8400-e29b-41d4-a716-446655440000/mfa/status \
  -H "Authorization: Bearer <access_token>"

Response 200 OK

json
{
  "data": {
    "mfa_enabled": true,
    "methods": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "type": "totp",
        "label": "My Phone",
        "is_primary": true,
        "verified_at": "2026-02-25T10:05:00Z",
        "last_used_at": "2026-02-25T14:30:00Z"
      }
    ],
    "backup_codes_remaining": 8
  }
}

Regenerate Backup Codes

Generate a new set of 10 backup codes. All previously issued backup codes are immediately invalidated. Requires the user's current password.

Endpoint: POST /api/v1/applications/{applicationId}/users/{userId}/mfa/backup-codes/regenerate

Authentication: JWT Bearer token required.

Request Body

FieldTypeRequiredDescription
passwordstringYesThe user's current password for re-authentication.
bash
curl -X POST https://api.yorauth.com/api/v1/applications/your-application-id/users/550e8400-e29b-41d4-a716-446655440000/mfa/backup-codes/regenerate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"password": "Str0ng!Passw0rd"}'

Response 200 OK

json
{
  "data": {
    "backup_codes": [
      "AAAA-BBBB-CCCC",
      "DDDD-EEEE-FFFF"
    ],
    "message": "New backup codes generated. Previous codes are now invalid."
  }
}

Error Responses

HTTP StatusError CodeDescription
422INVALID_PASSWORDThe provided password is incorrect.
400MFA_NOT_ENABLEDMFA must be enabled before generating backup codes.

Disable TOTP

Remove TOTP MFA from a user's account. Requires the user's current password.

Endpoint: DELETE /api/v1/applications/{applicationId}/users/{userId}/mfa/totp

Authentication: JWT Bearer token required.

Request Body

FieldTypeRequiredDescription
passwordstringYesThe user's current password for re-authentication.
bash
curl -X DELETE https://api.yorauth.com/api/v1/applications/your-application-id/users/550e8400-e29b-41d4-a716-446655440000/mfa/totp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"password": "Str0ng!Passw0rd"}'

Response 204 No Content

No body is returned on success.

Error Responses

HTTP StatusError CodeDescription
422INVALID_PASSWORDThe provided password is incorrect.