Recipes
TOTP enrollment with an inline QR code
const enrollment = mfa.createTOTPEnrollment({
userId: 'u1',
accountName: 'user@example.com',
includeQrCodeUrl: true
});
console.log(enrollment.secret);
console.log(enrollment.otpauthUrl);
console.log(enrollment.qrCodeUrl); // data:image/svg+xml;base64,...
Policy, risk, and step-up hooks
import { QMFAService, type IQMFAPolicyEngine } from '@quik/mfa';
const policyEngine: IQMFAPolicyEngine = {
resolveRequest: ({ request }) => ({ ...request, maxAttempts: 3 }),
evaluateRisk: ({ challenge }) => (challenge.method === 'sms' ? 80 : 20),
resolveStepUp: ({ riskScore }) => {
if ((riskScore ?? 0) >= 70) {
return { method: 'passkey', reason: 'high-risk' };
}
}
};
const mfa = new QMFAService({ policyEngine });
Register a custom code-delivery factor
import { QCodeFactor, registerFactor } from '@quik/mfa';
registerFactor(
new QCodeFactor({
method: 'push',
deliver: async ({ code, userId }) => {
console.log('Send code', code, 'to user', userId);
}
})
);
Register a custom TOTP factor
import { QTOTPFactor, registerFactor } from '@quik/mfa';
registerFactor(
new QTOTPFactor({
getSecret: async (userId) => process.env[`TOTP_SECRET_${userId}`]
})
);
Persist challenges in a database
Replace the default in-memory challenge store with a repository-backed one:
import { QAbstractMFAChallengeStore, type QMFAChallenge } from '@quik/mfa';
class DBChallengeStore extends QAbstractMFAChallengeStore<MyChallengeRepository> {
protected persistChallenge(challenge: QMFAChallenge): QMFAChallenge {
return this.repository.upsert(challenge);
}
protected fetchChallenge(id: string): QMFAChallenge | undefined {
return this.repository.findById(id);
}
protected removeChallenge(id: string): void {
this.repository.deleteById(id);
}
protected clearChallenges(): void {
this.repository.deleteAll();
}
protected removeExpiredOrConsumed(now: number): number {
return this.repository.deleteExpiredOrConsumed(now);
}
}
Activate it with setMFAChallengeStore(new DBChallengeStore(...)) and read the active store with getMFAChallengeStore().
Checklist
- Call
QMFAService.cleanup()periodically (e.g. from a scheduled task) to purge expired or consumed challenges when using the default in-memory store. - Keep
mfa.challenge.maxAttemptslow enough to slow brute-force attempts against short numeric codes.