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:
- Generate a TOTP secret and QR code.
- Ask the user to scan the QR code or enter the secret manually.
- 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
Recommended Authenticator Apps
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 identifiersecret: Base32-encoded TOTP secretissuer: Platform name for identification in the app
Manual Entry
If users cannot scan the QR code:
- Open the authenticator app.
- Select Enter a setup key or its equivalent.
- Enter an account name such as
SSO - user@example.com. - Enter the displayed secret key.
- Select Time-based as the type.
Continue to Verification to activate the factor and test the login challenge.