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:
- Setup — Generate a TOTP secret and provisioning URI (for QR code display).
- 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
| Field | Type | Required | Description |
|---|---|---|---|
label | string | No | A human-readable label for this authenticator, shown in status. Defaults to "Authenticator App". |
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"}'
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
{
"data": {
"method_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provisioning_uri": "otpauth://totp/jane%40example.com?secret=BASE32SECRET&issuer=MyApp",
"secret": "JBSWY3DPEHPK3PXP"
}
}
| Field | Description |
|---|---|
method_id | UUID for this MFA method. Required for the confirm step. |
provisioning_uri | OTPAuth URI. Pass this to a QR code library for display. |
secret | The 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):
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
| Field | Type | Required | Description |
|---|---|---|---|
method_id | string (UUID) | Yes | The method_id returned from the setup endpoint. |
code | string | Yes | A 6-digit TOTP code from the authenticator app. |
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"
}'
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
{
"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 Status | Error Code | Description |
|---|---|---|
| 422 | MFA_INVALID_CODE | The 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:
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
challenge_token | string | Yes | The token returned from the login endpoint. |
code | string | Yes | A 6-digit TOTP code or a backup code (up to 12 characters). |
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"
}'
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:
{
"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 Status | Error Code | Description |
|---|---|---|
| 401 | AUTH_INVALID_MFA_CODE | The TOTP code or backup code is incorrect. |
| 410 | AUTH_MFA_CHALLENGE_EXPIRED | The challenge token has expired (5-minute window). Start login again. |
| 429 | AUTH_MFA_LOCKED | Too 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.
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
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
password | string | Yes | The user's current password for re-authentication. |
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
{
"data": {
"backup_codes": [
"AAAA-BBBB-CCCC",
"DDDD-EEEE-FFFF"
],
"message": "New backup codes generated. Previous codes are now invalid."
}
}
Error Responses
| HTTP Status | Error Code | Description |
|---|---|---|
| 422 | INVALID_PASSWORD | The provided password is incorrect. |
| 400 | MFA_NOT_ENABLED | MFA 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
| Field | Type | Required | Description |
|---|---|---|---|
password | string | Yes | The user's current password for re-authentication. |
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 Status | Error Code | Description |
|---|---|---|
| 422 | INVALID_PASSWORD | The provided password is incorrect. |