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.
For comprehensive client-side API handler documentation with detailed examples, see the MFA Client-Side Handlers Guide.

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:
  1. Create an MFA session for the action
  2. Generate and receive an OTP code
  3. Verify the code within the session
  4. 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:
{
  "method_key": "totp"
}
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:
{
  "code": "123456"
}
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:
{
  "code": "123456"
}
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
  }
}
For detailed WebAuthn implementation, see the WebAuthn Guide. For complete client-side handler documentation, see MFA Client-Side Handlers.

Rate Limiting

MFA endpoints implement strict rate limiting:
EndpointLimitWindow
/mfa/sessions/verify5 attempts1 minute
/mfa/send-codeConfigurableConfigurable
General MFA endpoints100 requests1 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;
  }
}