Complete API reference for Multi-Factor Authentication endpoints in Boundary Framework.
POST /api/auth/mfa/setupPOST /api/auth/mfa/enablePOST /api/auth/mfa/disableGET /api/auth/mfa/statusPOST /api/auth/loginInitialize MFA setup for the authenticated user.
POST /api/auth/mfa/setup HTTP/1.1
Host: localhost:3000
Authorization: Bearer <jwt-token>
Content-Type: application/json
Parameters: None
Authentication: Required (Bearer token)
{
"success?": true,
"secret": "JBSWY3DPEHPK3PXP",
"qr-code-url": "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=otpauth%3A%2F%2Ftotp%2FBoundary%2520Framework%3Auser%40example.com%3Fsecret%3DJBSWY3DPEHPK3PXP%26issuer%3DBoundary%2520Framework",
"backup-codes": [
"3LTW-XRM1-GYVF",
"CN2K-1AWR-GDVT",
"9FHJ-K2LM-PQRS",
"7TUV-W3XY-Z4AB",
"5CDE-F6GH-I8JK",
"2LMN-O9PQ-R1ST",
"8UVW-X4YZ-A5BC",
"6DEF-G7HI-J9KL",
"4MNO-P2QR-S3TU",
"1VWX-Y8ZA-B0CD"
],
"issuer": "Boundary Framework",
"account-name": "user@example.com"
}
| Field | Type | Description |
|---|---|---|
success? | boolean | Always true for successful response |
secret | string | Base32-encoded TOTP secret (32 characters) |
qr-code-url | string | URL to QR code image for scanning with authenticator app |
backup-codes | array[string] | 10 single-use backup codes (12 chars each: XXX-XXXX-XXXX) |
issuer | string | Application name shown in authenticator app |
account-name | string | User's email address shown in authenticator app |
{
"error": "Unauthorized",
"message": "Invalid or missing authentication token"
}
{
"success?": false,
"error": "MFA already enabled",
"message": "User already has MFA enabled. Disable first to re-setup."
}
curl:
curl -X POST http://localhost:3000/api/auth/mfa/setup \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
| jq '.'
JavaScript (fetch):
fetch('http://localhost:3000/api/auth/mfa/setup', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
console.log('Secret:', data.secret);
console.log('QR Code:', data['qr-code-url']);
console.log('Backup Codes:', data['backup-codes']);
});
Python (requests):
import requests
response = requests.post(
'http://localhost:3000/api/auth/mfa/setup',
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
)
data = response.json()
print(f"Secret: {data['secret']}")
print(f"Backup Codes: {data['backup-codes']}")
Enable MFA after setup, requires verification code from authenticator app.
POST /api/auth/mfa/enable HTTP/1.1
Host: localhost:3000
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"secret": "JBSWY3DPEHPK3PXP",
"backupCodes": [
"3LTW-XRM1-GYVF",
"CN2K-1AWR-GDVT",
...
],
"verificationCode": "123456"
}
| Field | Type | Required | Description |
|---|---|---|---|
secret | string | Yes | Secret from setup response |
backupCodes | array[string] | Yes | Backup codes from setup response (all 10) |
verificationCode | string | Yes | Current 6-digit TOTP code from authenticator app |
{
"success?": true
}
{
"success?": false,
"error": "Invalid verification code"
}
{
"success?": false,
"error": "Validation failed",
"details": {
"secret": ["Secret is required"],
"backupCodes": ["Must provide 10 backup codes"],
"verificationCode": ["Verification code must be 6 digits"]
}
}
{
"error": "Unauthorized",
"message": "Invalid or missing authentication token"
}
curl:
# Get current code from authenticator app (e.g., 123456)
curl -X POST http://localhost:3000/api/auth/mfa/enable \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"secret": "JBSWY3DPEHPK3PXP",
"backupCodes": [
"3LTW-XRM1-GYVF",
"CN2K-1AWR-GDVT",
"9FHJ-K2LM-PQRS",
"7TUV-W3XY-Z4AB",
"5CDE-F6GH-I8JK",
"2LMN-O9PQ-R1ST",
"8UVW-X4YZ-A5BC",
"6DEF-G7HI-J9KL",
"4MNO-P2QR-S3TU",
"1VWX-Y8ZA-B0CD"
],
"verificationCode": "123456"
}' \
| jq '.'
JavaScript (fetch):
const setupData = await fetch('/api/auth/mfa/setup', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json());
// User scans QR code and enters 6-digit code
const verificationCode = prompt('Enter code from authenticator app:');
const response = await fetch('/api/auth/mfa/enable', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
secret: setupData.secret,
backupCodes: setupData['backup-codes'],
verificationCode: verificationCode
})
});
const result = await response.json();
if (result['success?']) {
alert('MFA enabled successfully!');
}
Disable MFA for the authenticated user.
POST /api/auth/mfa/disable HTTP/1.1
Host: localhost:3000
Authorization: Bearer <jwt-token>
Content-Type: application/json
Parameters: None
Authentication: Required (Bearer token)
{
"success?": true
}
{
"error": "Unauthorized",
"message": "Invalid or missing authentication token"
}
{
"success?": false,
"error": "MFA not enabled",
"message": "User does not have MFA enabled"
}
curl:
curl -X POST http://localhost:3000/api/auth/mfa/disable \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
| jq '.'
JavaScript (fetch):
fetch('http://localhost:3000/api/auth/mfa/disable', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
if (data['success?']) {
console.log('MFA disabled successfully');
}
});
Check if MFA is enabled and get remaining backup codes count.
GET /api/auth/mfa/status HTTP/1.1
Host: localhost:3000
Authorization: Bearer <jwt-token>
Parameters: None
Authentication: Required (Bearer token)
{
"enabled": true,
"enabled-at": "2024-01-04T10:00:00Z",
"backup-codes-remaining": 10
}
{
"enabled": false,
"enabled-at": null,
"backup-codes-remaining": 0
}
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether MFA is currently enabled |
enabled-at | string/null | ISO 8601 timestamp when MFA was enabled, or null |
backup-codes-remaining | integer | Number of unused backup codes (0-10) |
{
"error": "Unauthorized",
"message": "Invalid or missing authentication token"
}
curl:
curl -X GET http://localhost:3000/api/auth/mfa/status \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
| jq '.'
JavaScript (fetch):
fetch('http://localhost:3000/api/auth/mfa/status', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
if (data.enabled) {
console.log(`MFA enabled since: ${data['enabled-at']}`);
console.log(`Backup codes remaining: ${data['backup-codes-remaining']}`);
} else {
console.log('MFA not enabled');
}
});
Python (requests):
response = requests.get(
'http://localhost:3000/api/auth/mfa/status',
headers={'Authorization': f'Bearer {token}'}
)
data = response.json()
if data['enabled']:
print(f"MFA enabled: {data['enabled-at']}")
print(f"Backup codes: {data['backup-codes-remaining']}")
Authenticate user with email, password, and optional MFA code.
Step 1: Login with password only
POST /api/auth/login HTTP/1.1
Host: localhost:3000
Content-Type: application/json
{
"email": "user@example.com",
"password": "securepassword123"
}
Response (MFA Required):
{
"requires-mfa?": true,
"message": "MFA code required"
}
Step 2: Login with password + MFA code
POST /api/auth/login HTTP/1.1
Host: localhost:3000
Content-Type: application/json
{
"email": "user@example.com",
"password": "securepassword123",
"mfa-code": "123456"
}
Response (Success):
{
"success": true,
"session-id": "550e8400-e29b-41d4-a716-446655440000",
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"mfa-enabled": true
}
}
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User's email address |
password | string | Yes | User's password |
mfa-code | string | No | 6-digit TOTP code or 12-char backup code |
| Field | Type | Description |
|---|---|---|
success | boolean | true for successful login |
session-id | string | UUID session identifier |
user | object | User entity (see below) |
User Object:
| Field | Type | Description |
|-------|------|-------------|
| id | string | User UUID |
| email | string | User's email address |
| name | string | User's display name |
| role | string | User role (e.g., "user", "admin") |
| mfa-enabled | boolean | Whether user has MFA enabled |
{
"error": "Invalid credentials",
"message": "Email or password incorrect"
}
{
"error": "Invalid MFA code",
"message": "The provided MFA code is invalid or expired"
}
{
"error": "Validation failed",
"details": {
"email": ["Email is required", "Email format invalid"],
"password": ["Password is required"]
}
}
{
"error": "Too many requests",
"message": "Maximum login attempts exceeded. Try again in 5 minutes.",
"retry-after": 300
}
curl (Two-Step):
# Step 1: Try password only
RESPONSE=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securepassword123"
}')
echo $RESPONSE | jq '.'
# If requires-mfa? is true:
# Step 2: Login with MFA code
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securepassword123",
"mfa-code": "123456"
}' \
| jq '.'
curl (Using Backup Code):
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securepassword123",
"mfa-code": "3LTW-XRM1-GYVF"
}' \
| jq '.'
JavaScript (Two-Step):
async function login(email, password, mfaCode = null) {
const body = { email, password };
if (mfaCode) {
body['mfa-code'] = mfaCode;
}
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await response.json();
if (data['requires-mfa?']) {
// Prompt user for MFA code
const code = prompt('Enter MFA code from authenticator app:');
return login(email, password, code);
}
if (data.success) {
localStorage.setItem('session-id', data['session-id']);
return data.user;
}
throw new Error(data.error);
}
// Usage
login('user@example.com', 'securepassword123')
.then(user => console.log('Logged in:', user))
.catch(err => console.error('Login failed:', err));
Python (requests):
def login(email, password, mfa_code=None):
payload = {
'email': email,
'password': password
}
if mfa_code:
payload['mfa-code'] = mfa_code
response = requests.post(
'http://localhost:3000/api/auth/login',
json=payload
)
data = response.json()
if data.get('requires-mfa?'):
# Prompt for MFA code
mfa_code = input('Enter MFA code: ')
return login(email, password, mfa_code)
if data.get('success'):
return data['session-id'], data['user']
raise Exception(data.get('error'))
# Usage
try:
session_id, user = login('user@example.com', 'securepassword123')
print(f"Logged in as: {user['email']}")
print(f"Session ID: {session_id}")
except Exception as e:
print(f"Login failed: {e}")
mfa-code fieldRecommendation: Implement rate limiting on MFA-related endpoints to prevent brute-force attacks.
Suggested Limits:
Example nginx config:
limit_req_zone $binary_remote_addr zone=mfa_login:10m rate=5r/m;
location /api/auth/login {
limit_req zone=mfa_login burst=3 nodelay;
proxy_pass http://backend;
}
Development environment only:
Email: test@example.com
Password: password123
MFA Secret: JBSWY3DPEHPK3PXP
Generate TOTP code (development):
# Using oathtool
oathtool --totp -b JBSWY3DPEHPK3PXP
# Or use any authenticator app with the secret
# Run MFA integration tests
clojure -M:test:db/h2 --focus boundary.user.shell.mfa-test
# Test full authentication flow
clojure -M:test:db/h2 --focus boundary.user.authentication-integration-test
Last Updated: 2026-01-04
Version: 1.0.0
Status: Production Ready
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |