MFA Enrollment

Check MFA status and enroll an AuthOS user with a TOTP authenticator application

AuthOS release 0.8.2 TypeScript SDK 0.8.0 Latest-only documentation
Updated Jul 15, 2026 Edit this page
On this page

Complete the MFA prerequisites before beginning enrollment. AuthOS MFA is Beta: test the exact authenticator applications and recovery policies you support because public interoperability and abuse-case evidence is still incomplete.

Checking MFA Status

Before enabling or managing MFA, check whether the authenticated user already has it enabled.

Basic Status Check

import { SsoClient } from '@drmhse/sso-sdk';

const sso = new SsoClient({
  baseURL: 'https://sso.example.com',
  token: accessToken
});

async function checkMfaStatus() {
  try {
    const status = await sso.user.mfa.getStatus();

    console.log('MFA enabled:', status.enabled);

    if (status.enabled) {
      console.log('MFA is currently active');
    } else {
      console.log('MFA is not enabled');
    }

    return status.enabled;
  } catch (error) {
    console.error('Failed to check MFA status:', error);
    return false;
  }
}

React Component for MFA Status

import { useState, useEffect } from 'react';
import { SsoClient } from '@drmhse/sso-sdk';

interface MfaStatusProps {
  sso: SsoClient;
}

export function MfaStatus({ sso }: MfaStatusProps) {
  const [enabled, setEnabled] = useState<boolean | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    checkStatus();
  }, []);

  const checkStatus = async () => {
    try {
      const status = await sso.user.mfa.getStatus();
      setEnabled(status.enabled);
    } catch (error) {
      console.error('Failed to check MFA status:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return <div>Loading MFA status...</div>;
  }

  return (
    <div className="mfa-status">
      <h3>Two-Factor Authentication</h3>
      {enabled ? (
        <div className="status-enabled">
          <span className="badge success">Enabled</span>
          <p>Your account is protected with 2FA</p>
        </div>
      ) : (
        <div className="status-disabled">
          <span className="badge warning">Disabled</span>
          <p>Add an extra layer of security to your account</p>
        </div>
      )}
    </div>
  );
}

Setting Up TOTP MFA

The setup sequence has three required steps:

  1. Generate a TOTP secret and QR code.
  2. Ask the user to scan the QR code or enter the secret manually.
  3. Verify a current code before treating MFA as active.

Step 1: Generate QR Code

async function startMfaSetup() {
  try {
    const setup = await sso.user.mfa.setup();

    console.log('TOTP Secret:', setup.secret);
    console.log('QR Code SVG:', setup.qr_code_svg);
    console.log('QR Code URI:', setup.qr_code_uri);

    return {
      secret: setup.secret,
      qrCodeSvg: setup.qr_code_svg,
      qrCodeUri: setup.qr_code_uri
    };
  } catch (error) {
    console.error('Failed to setup MFA:', error);
    throw error;
  }
}

Treat secret, qr_code_svg, and qr_code_uri as credentials. Do not log them in production or persist them in analytics, support traces, or browser storage.

Step 2: Display QR Code to User

This complete enrollment component retains the generated secret only for the active setup flow, requires code verification, and shows recovery codes once.

import { useState } from 'react';
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';

interface MfaSetupProps {
  sso: SsoClient;
  onComplete: () => void;
}

export function MfaSetupFlow({ sso, onComplete }: MfaSetupProps) {
  const [step, setStep] = useState<'init' | 'scan' | 'verify'>('init');
  const [qrCode, setQrCode] = useState('');
  const [secret, setSecret] = useState('');
  const [code, setCode] = useState('');
  const [backupCodes, setBackupCodes] = useState<string[]>([]);
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  const startSetup = async () => {
    setLoading(true);
    setError('');

    try {
      const setup = await sso.user.mfa.setup();
      setQrCode(setup.qr_code_svg);
      setSecret(setup.secret);
      setStep('scan');
    } catch (err) {
      if (err instanceof SsoApiError) {
        setError(err.message);
      } else {
        setError('Failed to setup MFA. Please try again.');
      }
    } finally {
      setLoading(false);
    }
  };

  const verifySetup = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError('');

    try {
      const result = await sso.user.mfa.verify(code);
      setBackupCodes(result.backup_codes);
      setStep('verify');
    } catch (err) {
      if (err instanceof SsoApiError) {
        setError('Invalid code. Please try again.');
      } else {
        setError('Verification failed. Please try again.');
      }
      setLoading(false);
    }
  };

  if (step === 'init') {
    return (
      <div className="mfa-setup-init">
        <h2>Enable Two-Factor Authentication</h2>
        <p>
          Protect your account with an extra layer of security. You'll need an
          authenticator app like Google Authenticator, Authy, or 1Password.
        </p>
        <div className="benefits">
          <h3>Benefits of 2FA:</h3>
          <ul>
            <li>Protect against password theft</li>
            <li>Secure access even if your password is compromised</li>
            <li>Support operator-defined control requirements</li>
          </ul>
        </div>
        <button onClick={startSetup} disabled={loading}>
          {loading ? 'Setting up...' : 'Get Started'}
        </button>
      </div>
    );
  }

  if (step === 'scan') {
    return (
      <div className="mfa-setup-scan">
        <h2>Scan QR Code</h2>
        <div className="instructions">
          <p><strong>Step 1:</strong> Open your authenticator app</p>
          <p><strong>Step 2:</strong> Scan this QR code</p>
        </div>
        <div className="qr-code" dangerouslySetInnerHTML={{ __html: qrCode }} />
        <div className="manual-entry">
          <p>Can't scan? Enter this code manually:</p>
          <code>{secret}</code>
          <button onClick={() => navigator.clipboard.writeText(secret)}>
            Copy Code
          </button>
        </div>
        <form onSubmit={verifySetup}>
          <div className="form-group">
            <label htmlFor="code">
              <strong>Step 3:</strong> Enter the 6-digit code from your app
            </label>
            <input
              id="code"
              type="text"
              value={code}
              onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
              maxLength={6}
              placeholder="000000"
              required
              autoFocus
            />
          </div>
          {error && <div className="error">{error}</div>}
          <button type="submit" disabled={loading || code.length !== 6}>
            {loading ? 'Verifying...' : 'Verify and Enable'}
          </button>
        </form>
      </div>
    );
  }

  return (
    <div className="mfa-setup-complete">
      <h2>Two-Factor Authentication Enabled!</h2>
      <div className="backup-codes-intro">
        <p className="warning">
          <strong>Important:</strong> Save these backup codes in a secure place.
          You can use them to access your account if you lose your authenticator device.
        </p>
      </div>
      <div className="backup-codes">
        <h3>Your Backup Codes</h3>
        <div className="codes-grid">
          {backupCodes.map((code, index) => <code key={index}>{code}</code>)}
        </div>
        <button onClick={() => navigator.clipboard.writeText(backupCodes.join('\n'))}>
          Copy All Codes
        </button>
        <button onClick={() => {
          const blob = new Blob([backupCodes.join('\n')], { type: 'text/plain' });
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = 'backup-codes.txt';
          a.click();
          URL.revokeObjectURL(url);
        }}>
          Download Codes
        </button>
      </div>
      <button onClick={onComplete} className="primary">Done</button>
    </div>
  );
}

Authenticator App Integration

AuthOS is intended to interoperate with TOTP authenticator apps such as those below, but this list is illustrative rather than a published compatibility matrix:

  • Google Authenticator (iOS/Android)
  • Authy (iOS/Android/Desktop)
  • Microsoft Authenticator (iOS/Android)
  • 1Password (iOS/Android/Desktop/Browser)
  • Bitwarden (iOS/Android/Desktop/Browser)
  • LastPass Authenticator (iOS/Android)

QR Code Format

The generated QR code contains an otpauth:// URL in this format:

otpauth://totp/SSO:user@example.com?secret=SECRETKEY&issuer=SSO
  • type: totp (Time-based OTP)
  • label: Service name and user identifier
  • secret: Base32-encoded TOTP secret
  • issuer: Platform name for identification in the app

Manual Entry

If users cannot scan the QR code:

  1. Open the authenticator app.
  2. Select Enter a setup key or its equivalent.
  3. Enter an account name such as SSO - user@example.com.
  4. Enter the displayed secret key.
  5. Select Time-based as the type.

Continue to Verification to activate the factor and test the login challenge.