Skip to main content

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.maxAttempts low enough to slow brute-force attempts against short numeric codes.