Overview
The Multi-Factor Authentication (MFA) system provides an additional layer of security for user accounts. It supports multiple authentication methods including TOTP, Email OTP, SMS OTP, and WebAuthn, with flexible action-based requirements and configurable security policies.
Key Concepts
MFA Methods
Available authentication methods:
- TOTP (Time-based One-Time Password): Google Authenticator, Authy, etc.
- Email OTP: Verification codes sent via email
- SMS OTP: Verification codes sent via SMS
- WebAuthn: Passwordless authentication using biometrics or security keys
- Backup Codes: One-time use backup codes for account recovery
Action-Based MFA
MFA can be required for specific sensitive actions:
- Withdrawals: Cryptocurrency or fiat withdrawals
- Large Transactions: Transactions above a certain threshold
- Account Changes: Email/phone/password changes
- Security Settings: MFA enable/disable
- API Key Management: Creating or revoking API keys
MFA Sessions
When performing an MFA-protected action:
- Create an MFA session for the action
- Generate and receive an OTP code
- Verify the code within the session
- Complete the action if verification succeeds
Get User MFA Status
Retrieve the current user’s MFA configuration and status.
Endpoint: GET /api/auth/mfa/user
Alternative: GET /api/auth/mfa/config
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"code": 200,
"message": "User MFA status retrieved successfully",
"data": {
"is_enabled": true,
"is_required": false,
"enabled_methods": [
{
"id": 1,
"user_id": 123,
"method_key": "totp",
"is_primary": true,
"verified": true,
"created_at": "2025-10-01T10:00:00Z"
},
{
"id": 2,
"user_id": 123,
"method_key": "email_otp",
"is_primary": false,
"verified": true,
"created_at": "2025-10-01T11:00:00Z"
}
],
"available_methods": [
{
"id": 1,
"key": "totp",
"name": "TOTP",
"description": "Time-based One-Time Password using authenticator apps",
"is_active": true
},
{
"id": 2,
"key": "email_otp",
"name": "Email OTP",
"description": "One-time password sent to your email",
"is_active": true
},
{
"id": 3,
"key": "sms_otp",
"name": "SMS OTP",
"description": "One-time password sent to your phone",
"is_active": true
}
]
}
}
Example:
async function getMFAStatus() {
const accessToken = localStorage.getItem('access_token');
const response = await fetch('https://api.rcoinx.com/api/auth/mfa/user', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
return data.data;
}
// Usage
try {
const mfaStatus = await getMFAStatus();
console.log('MFA Enabled:', mfaStatus.is_enabled);
console.log('Enabled Methods:', mfaStatus.enabled_methods);
} catch (error) {
console.error('Failed to get MFA status:', error);
}
Enable MFA
Enable MFA for the user account with a specific method.
Endpoint: POST /api/auth/mfa/enable
Alternative: POST /api/auth/mfa/config/enable
Headers:
Authorization: Bearer <access_token>
Request:
Parameters:
method_key: Required, one of: totp, email_otp, sms_otp
Response: 200 OK
{
"code": 200,
"message": "MFA enabled successfully",
"data": {
"success": true,
"method": "totp"
}
}
Before enabling TOTP, users should first set up their authenticator app using the TOTP setup endpoints. See TOTP Setup.
Example:
async function enableMFA(methodKey) {
const accessToken = localStorage.getItem('access_token');
const response = await fetch('https://api.rcoinx.com/api/auth/mfa/enable', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ method_key: methodKey })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
return data.data;
}
// Usage
try {
await enableMFA('totp');
console.log('MFA enabled successfully');
} catch (error) {
console.error('Failed to enable MFA:', error);
}
Disable MFA
Disable MFA for the user account. Requires verification code.
Endpoint: POST /api/auth/mfa/disable
Alternative: POST /api/auth/mfa/config/disable
Headers:
Authorization: Bearer <access_token>
Request:
{
"confirmation_code": "123456"
}
Parameters:
confirmation_code: Required, 6-digit verification code from any enabled MFA method
Response: 200 OK
{
"code": 200,
"message": "MFA disabled successfully",
"data": {
"success": true
}
}
Example:
async function disableMFA(confirmationCode) {
const accessToken = localStorage.getItem('access_token');
const response = await fetch('https://api.rcoinx.com/api/auth/mfa/disable', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ confirmation_code: confirmationCode })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
return data.data;
}
Get Available MFA Methods
Retrieve all available MFA methods in the system.
Endpoint: GET /api/auth/mfa/methods
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"code": 200,
"message": "MFA methods retrieved successfully",
"data": [
{
"id": 1,
"key": "totp",
"name": "TOTP",
"description": "Time-based One-Time Password using authenticator apps",
"is_active": true,
"created_at": "2025-01-01T00:00:00Z"
},
{
"id": 2,
"key": "email_otp",
"name": "Email OTP",
"description": "One-time password sent to your email",
"is_active": true,
"created_at": "2025-01-01T00:00:00Z"
},
{
"id": 3,
"key": "sms_otp",
"name": "SMS OTP",
"description": "One-time password sent to your phone",
"is_active": true,
"created_at": "2025-01-01T00:00:00Z"
}
]
}
MFA Session Flow
1. Create MFA Session
Create an MFA session for a specific action.
Endpoint: POST /api/auth/mfa/sessions
Headers:
Authorization: Bearer <access_token>
Request:
{
"action_key": "withdraw"
}
Parameters:
action_key: Required, the action requiring MFA verification (e.g., withdraw, change_email, api_key_create)
Response: 200 OK
{
"code": 200,
"message": "MFA session created successfully",
"data": {
"session_token": "mfa_session_abc123xyz...",
"expires_at": "2025-10-13T12:35:00Z",
"action_key": "withdraw",
"requires_mfa": true
}
}
MFA sessions expire after 5 minutes by default. If requires_mfa is false, the action doesn’t require MFA and can proceed without verification.
2. Send Verification Code
Request an OTP code to be sent via the specified method.
Endpoint: POST /api/auth/mfa/send-code
Headers:
Authorization: Bearer <access_token>
Request:
{
"method_key": "email_otp"
}
Parameters:
method_key: Required, MFA method to use (totp, email_otp, sms_otp)
Response: 200 OK
{
"code": 200,
"message": "Verification code sent successfully",
"data": {
"success": true,
"method": "email_otp",
"sent_to": "j***@example.com"
}
}
This endpoint is rate-limited. You can only request a certain number of codes within a time window.
3. Verify MFA Code
Verify the OTP code within the MFA session.
Endpoint: POST /api/auth/mfa/sessions/verify
Headers:
Authorization: Bearer <access_token>
Request:
{
"session_token": "mfa_session_abc123xyz...",
"method_key": "email_otp",
"code": "123456"
}
Parameters:
session_token: Required, session token from Create MFA Session
method_key: Required, MFA method used
code: Required, 6-digit verification code
Response: 200 OK
{
"code": 200,
"message": "MFA code verified successfully",
"data": {
"verified": true,
"session_token": "mfa_session_abc123xyz...",
"action_key": "withdraw",
"expires_at": "2025-10-13T12:35:00Z"
}
}
Error Response (Invalid Code): 400 Bad Request
{
"code": 400,
"message": "Invalid verification code",
"error": "The code you entered is incorrect or expired"
}
This endpoint has strict rate limiting (5 attempts per minute). After multiple failed attempts, the user may be temporarily locked out.
4. Validate MFA Session
Check if an MFA session is valid and verified.
Endpoint: POST /api/auth/mfa/sessions/validate
Headers:
Authorization: Bearer <access_token>
Request:
{
"session_token": "mfa_session_abc123xyz..."
}
Response: 200 OK
{
"code": 200,
"message": "Session validated successfully",
"data": {
"is_valid": true,
"is_verified": true,
"action_key": "withdraw",
"expires_at": "2025-10-13T12:35:00Z"
}
}
5. Cancel MFA Session
Cancel an active MFA session.
Endpoint: POST /api/auth/mfa/sessions/cancel
Alternative: DELETE /api/auth/mfa/sessions/:id
Headers:
Authorization: Bearer <access_token>
Request:
{
"session_token": "mfa_session_abc123xyz..."
}
Response: 200 OK
{
"code": 200,
"message": "MFA session cancelled successfully"
}
Get Active MFA Sessions
Retrieve all active MFA sessions for the current user.
Endpoint: GET /api/auth/mfa/sessions/active
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"code": 200,
"message": "Active MFA sessions retrieved successfully",
"data": [
{
"id": 1,
"session_token": "mfa_session_abc123xyz...",
"action_key": "withdraw",
"is_verified": false,
"created_at": "2025-10-13T12:30:00Z",
"expires_at": "2025-10-13T12:35:00Z"
}
]
}
Check MFA Requirement
Check if MFA is required for a specific action.
Endpoint: POST /api/auth/mfa/check-requirement
Headers:
Authorization: Bearer <access_token>
Request:
{
"action_key": "withdraw"
}
Response: 200 OK
{
"code": 200,
"message": "MFA requirement checked successfully",
"data": {
"requires_mfa": true,
"action_key": "withdraw",
"min_methods": 1,
"user_has_mfa": true,
"can_proceed": true
}
}
Get MFA Scopes and Actions
Get MFA Scopes
Endpoint: GET /api/auth/mfa/scopes
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"code": 200,
"message": "MFA scopes retrieved successfully",
"data": [
{
"id": 1,
"key": "transaction",
"name": "Transactions",
"description": "Financial transactions and transfers"
},
{
"id": 2,
"key": "security",
"name": "Security Settings",
"description": "Account security and authentication settings"
}
]
}
Get MFA Actions
Endpoint: GET /api/auth/mfa/actions
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"code": 200,
"message": "MFA actions retrieved successfully",
"data": [
{
"id": 1,
"key": "withdraw",
"name": "Withdraw Funds",
"scope_key": "transaction",
"requires_mfa": true,
"mfa_min_method": 1
},
{
"id": 2,
"key": "change_email",
"name": "Change Email",
"scope_key": "security",
"requires_mfa": true,
"mfa_min_method": 1
}
]
}
Complete MFA Flow Example
Here’s a complete example of implementing MFA-protected withdrawal:
async function performWithdrawal(amount, address) {
const accessToken = localStorage.getItem('access_token');
try {
// Step 1: Create MFA session
const sessionResponse = await fetch('https://api.rcoinx.com/api/auth/mfa/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ action_key: 'withdraw' })
});
const sessionData = await sessionResponse.json();
if (!sessionData.data.requires_mfa) {
// MFA not required, proceed with withdrawal
return await executeWithdrawal(amount, address);
}
const sessionToken = sessionData.data.session_token;
// Step 2: Send verification code
await fetch('https://api.rcoinx.com/api/auth/mfa/send-code', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ method_key: 'email_otp' })
});
// Step 3: Get code from user (show input dialog)
const code = await promptUserForMFACode();
// Step 4: Verify code
const verifyResponse = await fetch('https://api.rcoinx.com/api/auth/mfa/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken,
method_key: 'email_otp',
code: code
})
});
const verifyData = await verifyResponse.json();
if (!verifyData.data.verified) {
throw new Error('MFA verification failed');
}
// Step 5: Proceed with withdrawal (include session token)
return await executeWithdrawal(amount, address, sessionToken);
} catch (error) {
console.error('Withdrawal failed:', error);
throw error;
}
}
async function executeWithdrawal(amount, address, mfaToken) {
// Your withdrawal API call
// Include mfaToken in headers or body as required by your API
}
function promptUserForMFACode() {
return new Promise((resolve) => {
// Show modal/dialog to get code from user
const code = prompt('Enter the verification code sent to your email:');
resolve(code);
});
}
MFA Method Setup
TOTP Setup
Setup TOTP (authenticator app) for MFA.
Endpoint: POST /api/auth/mfa/totp/setup
Headers:
Authorization: Bearer <access_token>
Response: 200 OK
{
"code": 200,
"message": "TOTP setup successful. Scan the QR code with your authenticator app.",
"data": {
"success": true,
"otpauth_url": "otpauth://totp/Rcoinx:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Rcoinx",
"mfa_method": "totp"
}
}
Verify and Enable TOTP
Endpoint: POST /api/auth/mfa/totp/verify
Request:
Response: 200 OK
{
"code": 200,
"message": "TOTP verified and MFA enabled successfully",
"data": {
"success": true,
"mfa_method": "totp",
"verified": true,
"enabled": true
}
}
Verify TOTP Code (for login/verification)
Endpoint: POST /api/auth/mfa/totp/validate
Request:
Response: 200 OK
{
"code": 200,
"message": "TOTP verification completed",
"data": {
"success": true,
"valid": true
}
}
Email MFA Setup
Send Email Verification OTP
Endpoint: POST /api/auth/mfa/email/send-otp
Request:
{
"email": "user@example.com"
}
Response: 200 OK
{
"code": 200,
"message": "Email verification OTP sent successfully",
"data": {
"success": true,
"email": "user@example.com"
}
}
Verify and Enable Email MFA
Endpoint: POST /api/auth/mfa/email/verify
Request:
{
"email": "user@example.com",
"code": "123456"
}
Response: 200 OK
{
"code": 200,
"message": "Email verified and MFA enabled successfully",
"data": {
"success": true,
"email": "user@example.com",
"mfa_method": "email",
"verified": true,
"enabled": true
}
}
SMS MFA Setup
Send Mobile Verification OTP
Endpoint: POST /api/auth/mfa/mobile/send-otp
Request:
{
"phone": "1234567890",
"country_code": "+1"
}
Response: 200 OK
{
"code": 200,
"message": "Mobile verification OTP sent successfully",
"data": {
"success": true,
"phone": "1234567890",
"country_code": "+1"
}
}
Verify and Enable SMS MFA
Endpoint: POST /api/auth/mfa/mobile/verify
Request:
{
"phone": "1234567890",
"country_code": "+1",
"code": "123456"
}
Response: 200 OK
{
"code": 200,
"message": "Mobile verified and MFA enabled successfully",
"data": {
"success": true,
"phone": "1234567890",
"country_code": "+1",
"mfa_method": "sms",
"verified": true,
"enabled": true
}
}
WebAuthn Setup
Setup WebAuthn
Endpoint: POST /api/auth/mfa/webauthn/setup
Response: 200 OK
{
"code": 200,
"message": "WebAuthn setup successful. Complete the registration with your authenticator.",
"data": {
"success": true,
"options": {
"challenge": "base64-encoded-challenge",
"rp": {
"name": "Rcoinx",
"id": "rcoinx.com"
},
"user": {
"id": "base64-user-id",
"name": "user@example.com",
"displayName": "User Name"
},
"pubKeyCredParams": [...],
"authenticatorSelection": {...},
"timeout": 60000
},
"mfa_method": "webauthn"
}
}
Verify and Enable WebAuthn
Endpoint: POST /api/auth/mfa/webauthn/verify
Request:
{
"response": {
"id": "credential-id",
"rawId": "base64-raw-id",
"response": {
"clientDataJSON": "base64-client-data",
"attestationObject": "base64-attestation-object"
},
"type": "public-key"
}
}
Response: 200 OK
{
"code": 200,
"message": "WebAuthn verified and MFA enabled successfully",
"data": {
"success": true,
"mfa_method": "webauthn",
"verified": true,
"enabled": true
}
}
Rate Limiting
MFA endpoints implement strict rate limiting:
| Endpoint | Limit | Window |
|---|
/mfa/sessions/verify | 5 attempts | 1 minute |
/mfa/send-code | Configurable | Configurable |
| General MFA endpoints | 100 requests | 1 minute |
Exceeding rate limits results in 429 Too Many Requests response with lockout duration.
Best Practices
1. Handle MFA Gracefully
async function withMFAProtection(action, actionKey = 'default') {
const mfaStatus = await getMFAStatus();
if (!mfaStatus.is_enabled) {
// Proceed without MFA
return await action();
}
// Implement MFA flow
return await performMFAProtectedAction(action, actionKey);
}
2. Cache MFA Status
const mfaCache = {
status: null,
timestamp: null,
TTL: 5 * 60 * 1000, // 5 minutes
async get() {
const now = Date.now();
if (this.status && this.timestamp && (now - this.timestamp < this.TTL)) {
return this.status;
}
this.status = await getMFAStatus();
this.timestamp = now;
return this.status;
}
};
3. Show Clear UI Feedback
function MFAVerificationDialog({ sessionToken, onVerified }) {
const [code, setCode] = useState('');
const [error, setError] = useState(null);
const [attemptsLeft, setAttemptsLeft] = useState(5);
const handleVerify = async () => {
try {
await verifyMFACode(sessionToken, 'email_otp', code);
onVerified();
} catch (err) {
setAttemptsLeft(prev => prev - 1);
setError(err.message);
if (attemptsLeft <= 1) {
setError('Too many failed attempts. Please try again later.');
}
}
};
return (
<div className="mfa-dialog">
<h3>Verify Your Identity</h3>
<p>Enter the 6-digit code sent to your email</p>
<input
type="text"
maxLength={6}
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="000000"
/>
{error && <div className="error">{error}</div>}
<div className="attempts">Attempts left: {attemptsLeft}</div>
<button onClick={handleVerify}>Verify</button>
</div>
);
}
4. Handle Session Expiration
async function verifyWithRetry(sessionToken, methodKey, code) {
try {
return await verifyMFACode(sessionToken, methodKey, code);
} catch (error) {
if (error.message.includes('expired')) {
// Session expired, create new one
const newSession = await createMFASession(actionKey);
// Inform user to request new code
throw new Error('Session expired. Please request a new code.');
}
throw error;
}
}