Overview

This guide provides comprehensive documentation for front-end developers integrating with the Rcoinx MFA (Multi-Factor Authentication) client-side handlers. The MFA system provides enterprise-grade security through multiple authentication methods including TOTP, SMS, Email, and WebAuthn.
This guide focuses on client-side API handlers. For general MFA concepts and flows, see the MFA Overview.

Base URL

All endpoints are prefixed with /api/auth/mfa:
Production: https://api.rcoinx.com/api/auth/mfa
Staging: https://staging-api.rcoinx.com/api/auth/mfa
Development: http://localhost:8080/api/auth/mfa

Core Concepts

MFA Methods

The system supports multiple MFA methods:
  • TOTP (totp): Time-based One-Time Password using authenticator apps (Google Authenticator, Authy, etc.)
  • SMS (sms): SMS-based OTP codes sent to user’s mobile phone
  • Email (email): Email-based OTP codes sent to user’s email address
  • WebAuthn (webauthn): Passwordless authentication using biometrics or security keys
  • Backup Codes (backup_code): One-time use backup codes for account recovery

MFA Status

Users can have three MFA statuses:
  • Disabled: MFA is not enabled
  • Enabled: MFA is enabled but optional
  • Required: MFA is required for certain actions

MFA Sessions

MFA sessions are temporary tokens created for specific actions that require MFA verification. Sessions:
  • Are tied to specific actions (e.g., transfer_funds, change_password)
  • Have expiration times (typically 5 minutes)
  • Track verification progress across multiple MFA methods
  • Can be validated, extended, or cancelled

Authentication

All MFA endpoints require JWT authentication. Include the JWT token in the Authorization header:
Authorization: Bearer <access_token>

API Endpoints

MFA Methods

Get All Available MFA Methods

Retrieve all MFA methods available to users. Response: 200 OK
{
  "code": 200,
  "message": "MFA methods retrieved successfully",
  "data": [
    {
      "id": 1,
      "key": "totp",
      "name": "Authenticator App",
      "description": "Use an authenticator app to generate codes",
      "is_active": true,
      "icon": "authenticator-icon-url"
    },
    {
      "id": 2,
      "key": "sms",
      "name": "SMS",
      "description": "Receive codes via SMS",
      "is_active": true,
      "icon": "sms-icon-url"
    }
  ]
}
async function getMFAMethods(accessToken) {
  const response = await fetch('https://api.rcoinx.com/api/auth/mfa/methods', {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    }
  });
  
  const data = await response.json();
  return data.data; // Array of MFA methods
}

Get Specific MFA Method

Retrieve details for a specific MFA method by ID. Parameters:
  • id (path): MFA method ID
Response: 200 OK
{
  "code": 200,
  "message": "MFA method retrieved successfully",
  "data": {
    "id": 1,
    "key": "totp",
    "name": "Authenticator App",
    "description": "Use an authenticator app to generate codes",
    "is_active": true,
    "icon": "authenticator-icon-url"
  }
}

User MFA Configuration

Get User MFA Status

Retrieve the current MFA configuration and status for the authenticated user. Response: 200 OK
{
  "code": 200,
  "message": "User MFA configuration retrieved successfully",
  "data": {
    "is_enabled": true,
    "is_required": false,
    "status": "enabled",
    "methods": [
      {
        "id": 1,
        "key": "totp",
        "name": "Authenticator App"
      }
    ],
    "last_used_at": "2024-01-15T10:30:00Z",
    "created_at": "2024-01-01T08:00:00Z",
    "updated_at": "2024-01-15T10:30:00Z"
  }
}
async function getUserMFAStatus(accessToken) {
  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();
  return data.data;
}

Enable MFA

Enable a specific MFA method for the user. Note: You must first set up the MFA method (e.g., via TOTP setup, email verification) before enabling it. Request:
{
  "method_key": "totp"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA enabled successfully",
  "data": {
    "success": true,
    "method": "totp"
  }
}

Disable MFA

Disable a specific MFA method for the user. Request:
{
  "method_key": "totp"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA disabled successfully",
  "data": {
    "success": true
  }
}

MFA Sessions

Create MFA Session

Create a new MFA session for a specific action. Request:
{
  "action_key": "transfer_funds"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA session created successfully",
  "data": {
    "session_token": "sess_abc123xyz",
    "expires_at": "2024-01-15T10:35:00Z",
    "action_key": "transfer_funds",
    "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.

Validate MFA Session

Check if an MFA session is valid and not expired. Request:
{
  "session_token": "sess_abc123xyz"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA session validated successfully",
  "data": {
    "is_valid": true,
    "is_expired": false,
    "status": "active",
    "expires_at": "2024-01-15T10:35:00Z",
    "user_id": 123,
    "action_key": "transfer_funds",
    "ip_address": "192.168.1.1",
    "user_agent": "Mozilla/5.0..."
  }
}

Verify MFA Code

Verify an MFA code for a session. This is the core verification step. Request:
{
  "session_token": "sess_abc123xyz",
  "method_key": "totp",
  "code": "123456"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA code verification completed",
  "data": {
    "is_valid": true,
    "method_key": "totp",
    "verified_at": "2024-01-15T10:32:00Z",
    "session_token": "sess_abc123xyz",
    "expires_at": "2024-01-15T10:35:00Z",
    "session_is_completed": true
  }
}
session_is_completed indicates whether all required MFA methods have been verified. Some actions may require multiple MFA methods. Continue verifying codes until session_is_completed is true.
async function verifyMFACode(accessToken, sessionToken, methodKey, code) {
  const response = 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: methodKey,
      code: code
    })
  });
  
  const data = await response.json();
  return data.data;
}

Cancel MFA Session

Cancel an active MFA session. Request:
{
  "session_token": "sess_abc123xyz"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA session cancelled successfully",
  "data": {
    "success": true
  }
}

Get Active MFA Sessions

Retrieve all active MFA sessions for the authenticated user. Response: 200 OK
{
  "code": 200,
  "message": "Active MFA sessions retrieved successfully",
  "data": [
    {
      "id": 1,
      "session_token": "sess_abc123xyz",
      "user_id": 123,
      "action_id": 5,
      "expires_at": "2024-01-15T10:35:00Z",
      "status": "active",
      "ip_address": "192.168.1.1",
      "user_agent": "Mozilla/5.0..."
    }
  ]
}

Send Code to User

Request a code to be sent via SMS or Email for MFA verification. Request:
{
  "method_key": "sms"
}
Response: 200 OK
{
  "code": 200,
  "message": "Code sent to user successfully"
}
This endpoint is rate-limited. You can only request a certain number of codes within a time window.

MFA Validation

Check MFA Requirement

Check if MFA is required for a specific action. Request:
{
  "action_key": "transfer_funds"
}
Response: 200 OK
{
  "code": 200,
  "message": "MFA requirement checked successfully",
  "data": {
    "requires_mfa": true,
    "action_key": "transfer_funds",
    "min_methods": 1,
    "allowed_methods": ["totp", "sms"]
  }
}

MFA Scopes and Actions

Get MFA Scopes

Retrieve all available MFA scopes. Response: 200 OK
{
  "code": 200,
  "message": "MFA scopes retrieved successfully",
  "data": [
    {
      "id": 1,
      "key": "financial",
      "name": "Financial Operations",
      "description": "Actions related to financial transactions"
    },
    {
      "id": 2,
      "key": "security",
      "name": "Security Settings",
      "description": "Actions related to account security"
    }
  ]
}

Get MFA Actions

Retrieve all available MFA actions. Response: 200 OK
{
  "code": 200,
  "message": "MFA actions retrieved successfully",
  "data": [
    {
      "id": 1,
      "key": "transfer_funds",
      "name": "Transfer Funds",
      "description": "Transfer funds to another account",
      "scope_key": "financial",
      "requires_mfa": true,
      "mfa_min_methods": 1
    },
    {
      "id": 2,
      "key": "change_password",
      "name": "Change Password",
      "description": "Change account password",
      "scope_key": "security",
      "requires_mfa": true,
      "mfa_min_methods": 1
    }
  ]
}

TOTP Setup and Verification

Setup TOTP

Generate a TOTP secret and get the QR code URL for setup. 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"
  }
}
async function setupTOTP(accessToken) {
  const response = await fetch('https://api.rcoinx.com/api/auth/mfa/totp/setup', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    }
  });
  
  const data = await response.json();
  return data.data;
}

// Display QR code using a QR code library
function displayQRCode(otpauthURL) {
  const canvas = document.getElementById('qr-code');
  QRCode.toCanvas(canvas, otpauthURL, function (error) {
    if (error) console.error(error);
  });
}

Verify and Enable TOTP

Verify a TOTP code from the authenticator app and enable TOTP MFA. 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

Verify a TOTP code during MFA verification (e.g., during login or action verification). Request:
{
  "code": "123456"
}
Response: 200 OK
{
  "code": 200,
  "message": "TOTP verification completed",
  "data": {
    "success": true,
    "valid": true
  }
}

Email MFA Setup and Verification

Send Email Verification OTP

Send an OTP code to the user’s email address for MFA setup. 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

Verify the email OTP code and enable email MFA. 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 and Verification

Send Mobile Verification OTP

Send an OTP code to the user’s mobile phone for MFA setup. 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

Verify the mobile OTP code and enable SMS MFA. 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 and Verification

Setup WebAuthn

Begin WebAuthn registration ceremony and get credential creation options. 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"
  }
}
For detailed WebAuthn implementation, see the WebAuthn Guide.

Complete Integration Examples

Example 1: Complete TOTP Setup Flow

class MFAService {
  constructor(apiBaseUrl, accessToken) {
    this.apiBaseUrl = apiBaseUrl;
    this.accessToken = accessToken;
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.apiBaseUrl}${endpoint}`;
    const headers = {
      'Authorization': `Bearer ${this.accessToken}`,
      'Content-Type': 'application/json',
      ...options.headers
    };
    
    const response = await fetch(url, {
      ...options,
      headers
    });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'Request failed');
    }
    
    return response.json();
  }
  
  async setupTOTP() {
    try {
      // Step 1: Setup TOTP and get QR code
      const setupResponse = await this.request('/totp/setup', {
        method: 'POST'
      });
      
      const { otpauth_url } = setupResponse.data;
      
      // Step 2: Display QR code to user
      this.displayQRCode(otpauth_url);
      
      // Step 3: Wait for user to scan and enter code
      const code = await this.promptUserForCode();
      
      // Step 4: Verify and enable TOTP
      const verifyResponse = await this.request('/totp/verify', {
        method: 'POST',
        body: JSON.stringify({ code })
      });
      
      if (verifyResponse.data.enabled) {
        // Step 5: Enable MFA method
        await this.request('/enable', {
          method: 'POST',
          body: JSON.stringify({ method_key: 'totp' })
        });
        
        return { success: true, message: 'TOTP MFA enabled successfully' };
      }
      
      throw new Error('TOTP verification failed');
    } catch (error) {
      console.error('TOTP setup error:', error);
      throw error;
    }
  }
  
  displayQRCode(otpauthURL) {
    const canvas = document.getElementById('qr-code-canvas');
    QRCode.toCanvas(canvas, otpauthURL, (error) => {
      if (error) {
        console.error('QR code generation failed:', error);
      }
    });
  }
  
  async promptUserForCode() {
    return new Promise((resolve) => {
      const code = prompt('Enter the 6-digit code from your authenticator app:');
      resolve(code);
    });
  }
}

Example 2: Action-Based MFA Verification Flow

class ActionMFAService {
  constructor(apiBaseUrl, accessToken) {
    this.apiBaseUrl = apiBaseUrl;
    this.accessToken = accessToken;
  }
  
  async performActionWithMFA(actionKey, actionData) {
    try {
      // Step 1: Check if MFA is required
      const requirement = await this.checkMFARequirement(actionKey);
      
      if (!requirement.requires_mfa) {
        // No MFA required, proceed with action
        return await this.executeAction(actionKey, actionData);
      }
      
      // Step 2: Create MFA session
      const session = await this.createMFASession(actionKey);
      
      // Step 3: Verify MFA codes
      const verificationComplete = await this.verifyMFAForSession(
        session.session_token,
        requirement.allowed_methods
      );
      
      if (!verificationComplete) {
        throw new Error('MFA verification incomplete');
      }
      
      // Step 4: Execute action with session token
      return await this.executeAction(actionKey, actionData, session.session_token);
      
    } catch (error) {
      console.error('Action MFA error:', error);
      throw error;
    }
  }
  
  async checkMFARequirement(actionKey) {
    const response = await this.request('/check-requirement', {
      method: 'POST',
      body: JSON.stringify({ action_key: actionKey })
    });
    return response.data;
  }
  
  async createMFASession(actionKey) {
    const response = await this.request('/sessions', {
      method: 'POST',
      body: JSON.stringify({ action_key: actionKey })
    });
    return response.data;
  }
  
  async verifyMFAForSession(sessionToken, allowedMethods) {
    // For each required method, get code from user and verify
    for (const method of allowedMethods) {
      let code;
      
      if (method === 'sms' || method === 'email') {
        // Request code to be sent
        await this.request('/send-code', {
          method: 'POST',
          body: JSON.stringify({ method_key: method })
        });
        
        // Wait for user to receive and enter code
        code = await this.promptUserForCode(method);
      } else if (method === 'totp') {
        // User enters code from authenticator app
        code = await this.promptUserForCode(method);
      }
      
      // Verify code
      const verifyResponse = await this.request('/sessions/verify', {
        method: 'POST',
        body: JSON.stringify({
          session_token: sessionToken,
          method_key: method,
          code: code
        })
      });
      
      if (verifyResponse.data.session_is_completed) {
        return true;
      }
    }
    
    return false;
  }
  
  async promptUserForCode(method) {
    return new Promise((resolve) => {
      const methodName = {
        'totp': 'authenticator app',
        'sms': 'SMS',
        'email': 'email'
      }[method] || method;
      
      const code = prompt(`Enter the code from your ${methodName}:`);
      resolve(code);
    });
  }
  
  async executeAction(actionKey, actionData, sessionToken) {
    // Include session token in headers if provided
    const headers = {};
    if (sessionToken) {
      headers['X-MFA-Session-Token'] = sessionToken;
    }
    
    // Make the actual action request
    const response = await fetch(`${this.apiBaseUrl}/actions/${actionKey}`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.accessToken}`,
        'Content-Type': 'application/json',
        ...headers
      },
      body: JSON.stringify(actionData)
    });
    
    return response.json();
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.apiBaseUrl}${endpoint}`;
    const headers = {
      'Authorization': `Bearer ${this.accessToken}`,
      'Content-Type': 'application/json',
      ...options.headers
    };
    
    const response = await fetch(url, {
      ...options,
      headers
    });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'Request failed');
    }
    
    return response.json();
  }
}

Example 3: React Hook for MFA Management

import { useState, useCallback } from 'react';

function useMFA(apiBaseUrl, accessToken) {
  const [mfaStatus, setMFAStatus] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const request = useCallback(async (endpoint, options = {}) => {
    const url = `${apiBaseUrl}${endpoint}`;
    const headers = {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      ...options.headers
    };
    
    const response = await fetch(url, {
      ...options,
      headers
    });
    
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.message || 'Request failed');
    }
    
    return response.json();
  }, [apiBaseUrl, accessToken]);
  
  const getMFAStatus = useCallback(async () => {
    setLoading(true);
    setError(null);
    
    try {
      const response = await request('/user');
      setMFAStatus(response.data);
      return response.data;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [request]);
  
  const setupTOTP = useCallback(async () => {
    setLoading(true);
    setError(null);
    
    try {
      const setupResponse = await request('/totp/setup', { method: 'POST' });
      return setupResponse.data;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [request]);
  
  const verifyAndEnableTOTP = useCallback(async (code) => {
    setLoading(true);
    setError(null);
    
    try {
      const verifyResponse = await request('/totp/verify', {
        method: 'POST',
        body: JSON.stringify({ code })
      });
      
      if (verifyResponse.data.enabled) {
        await request('/enable', {
          method: 'POST',
          body: JSON.stringify({ method_key: 'totp' })
        });
        
        await getMFAStatus();
      }
      
      return verifyResponse.data;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [request, getMFAStatus]);
  
  const disableMFA = useCallback(async (methodKey) => {
    setLoading(true);
    setError(null);
    
    try {
      await request('/disable', {
        method: 'POST',
        body: JSON.stringify({ method_key: methodKey })
      });
      
      await getMFAStatus();
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [request, getMFAStatus]);
  
  return {
    mfaStatus,
    loading,
    error,
    getMFAStatus,
    setupTOTP,
    verifyAndEnableTOTP,
    disableMFA
  };
}

Error Handling

Common Error Responses

All endpoints return errors in a consistent format:
{
  "code": 400,
  "message": "Error message",
  "error": "Detailed error information"
}

HTTP Status Codes

Status CodeDescription
200Success
400Bad Request - Invalid parameters or request format
401Unauthorized - Invalid or missing JWT token
403Forbidden - Action not allowed
404Not Found - Resource not found
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Server error

Error Handling Example

async function handleMFARequest(endpoint, options) {
  try {
    const response = await fetch(endpoint, options);
    
    if (!response.ok) {
      const errorData = await response.json();
      
      // Handle specific error codes
      switch (response.status) {
        case 401:
          // Token expired, redirect to login
          window.location.href = '/login';
          break;
        case 429:
          // Rate limited, show retry message
          alert('Too many requests. Please try again later.');
          break;
        case 400:
          // Bad request, show validation errors
          alert(`Error: ${errorData.message}`);
          break;
        default:
          alert(`An error occurred: ${errorData.message}`);
      }
      
      throw new Error(errorData.message);
    }
    
    return await response.json();
  } catch (error) {
    console.error('MFA request error:', error);
    throw error;
  }
}

Best Practices

1. Security

  • Never store MFA secrets or codes in localStorage or sessionStorage
  • Always use HTTPS in production
  • Validate all user inputs on the client side before sending
  • Implement rate limiting on the client side to prevent abuse
  • Clear sensitive data from memory after use

2. User Experience

  • Provide clear instructions for each MFA method
  • Show progress indicators during MFA setup and verification
  • Display helpful error messages
  • Allow users to cancel MFA operations
  • Provide fallback options if primary MFA method fails

3. Error Handling

  • Always handle network errors gracefully
  • Provide retry mechanisms for failed requests
  • Show user-friendly error messages
  • Log errors for debugging but don’t expose sensitive information

4. Session Management

  • Validate session tokens before use
  • Handle session expiration gracefully
  • Cancel unused sessions to free resources
  • Store session tokens securely (not in localStorage)

Troubleshooting

Common Issues

”User ID not found in token” Error

Problem: JWT token is invalid or expired. Solution:
  • Check if the token is valid
  • Refresh the token if it’s expired
  • Ensure the token is being sent in the Authorization header

”MFA session expired” Error

Problem: MFA session has expired (default 5 minutes). Solution:
  • Create a new MFA session
  • Complete verification within the session timeout

”Invalid MFA code” Error

Problem: The provided code is incorrect or expired. Solution:
  • For TOTP: Ensure the device time is synchronized
  • For SMS/Email: Request a new code if the previous one expired
  • Check if the code format is correct (6 digits for most methods)

Rate Limiting

Problem: Too many requests in a short time. Solution:
  • Implement exponential backoff
  • Show user-friendly message about rate limiting
  • Wait for the rate limit window to reset