MFAService
Package: @nauth-toolkit/core
Type: Service
Central registry service for managing MFA (Multi-Factor Authentication) providers and coordinating MFA operations.
- NestJS
- Express
- Fastify
import { MFAService } from '@nauth-toolkit/nestjs';
import { MFAService } from '@nauth-toolkit/core';
// Access via nauth.mfaService after NAuth.create()
import { MFAService } from '@nauth-toolkit/core';
// Access via nauth.mfaService after NAuth.create()
Overview
The MFAService acts as a registry and orchestrator for MFA provider services (TOTP, SMS, Email, Passkey). It routes MFA operations to the appropriate provider and manages user MFA devices.
Auto-injected by framework. No manual instantiation required.
Methods
adminGetMfaStatus()
Get comprehensive MFA status for a target user (admin operation).
async adminGetMfaStatus(dto: AdminGetMFAStatusDTO): Promise<GetMFAStatusResponseDTO>
Parameters
dto-AdminGetMFAStatusDTO
Returns
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails | { validationErrors: Record<string, string[]> } |
NOT_FOUND | User not found | undefined |
Throws NAuthException with the codes listed above.
Example (NestJS)
@Post('admin/mfa/status')
async adminGetStatus(@Body() dto: AdminGetMFAStatusDTO) {
return await this.mfaService.adminGetMfaStatus(dto);
}
adminGetUserDevices()
Get all active MFA devices for a specific user (admin operation).
async adminGetUserDevices(dto: AdminGetUserDevicesDTO): Promise<GetUserDevicesResponseDTO>
Parameters
dto-AdminGetUserDevicesDTO- Containssub(target user's UUID)
Returns
GetUserDevicesResponseDTO-{ devices: MFADeviceResponseDTO[] }
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails | { validationErrors: Record<string, string[]> } |
USER_NOT_FOUND | User not found | { sub: string } |
Throws NAuthException with the codes listed above.
Example
- NestJS
- Express
- Fastify
@Get('admin/users/:sub/mfa/devices')
@UseGuards(AdminAuthGuard)
async adminGetUserDevices(@Param() dto: AdminGetUserDevicesDTO): Promise<GetUserDevicesResponseDTO> {
return await this.mfaService.adminGetUserDevices(dto);
}
app.get('/admin/users/:sub/mfa/devices', requireAdminAuth(), async (req, res) => {
const result = await nauth.mfaService.adminGetUserDevices({ sub: req.params.sub });
res.json(result);
});
fastify.get(
'/admin/users/:sub/mfa/devices',
{ preHandler: requireAdminAuth() },
nauth.adapter.wrapRouteHandler(async (req) => {
return nauth.mfaService.adminGetUserDevices({ sub: req.params.sub });
}),
);
adminRemoveDevice()
Remove a single MFA device by device ID (admin operation). Does not require user context.
async adminRemoveDevice(dto: AdminRemoveDeviceDTO): Promise<RemoveDeviceResponseDTO>
Parameters
dto-AdminRemoveDeviceDTO- ContainsdeviceId
Returns
RemoveDeviceResponseDTO-{ removedDeviceId: number, removedMethod: string, mfaDisabled: boolean }
Errors
| Code | When | Details |
|---|---|---|
NOT_FOUND | Device not found | { deviceId: number } |
Example (NestJS)
@Delete('admin/mfa/devices/:deviceId')
@UseGuards(AdminAuthGuard)
async adminRemoveDevice(@Param() dto: AdminRemoveDeviceDTO): Promise<RemoveDeviceResponseDTO> {
return await this.mfaService.adminRemoveDevice(dto);
}
adminSetPreferredDevice()
Set a specific device as preferred for a user (admin operation).
async adminSetPreferredDevice(dto: AdminSetPreferredDeviceDTO): Promise<AdminSetPreferredDeviceResponseDTO>
Parameters
dto-AdminSetPreferredDeviceDTO- ContainssubanddeviceId
Returns
AdminSetPreferredDeviceResponseDTO-{ message: string }
Errors
| Code | When | Details |
|---|---|---|
NOT_FOUND | User or device not found | { sub?: string, deviceId?: number } |
Example (NestJS)
@Post('admin/users/:sub/mfa/devices/:deviceId/preferred')
@UseGuards(AdminAuthGuard)
async adminSetPreferredDevice(@Param() dto: AdminSetPreferredDeviceDTO): Promise<SetPreferredDeviceResponseDTO> {
return await this.mfaService.adminSetPreferredDevice(dto);
}
getAvailableMethods()
Get available MFA methods for a user. Returns all registered and allowed methods that can be set up.
async getAvailableMethods(dto: GetAvailableMethodsDTO): Promise<GetAvailableMethodsResponseDTO>
Parameters
dto-GetAvailableMethodsDTO
Returns
GetAvailableMethodsResponseDTO-{ availableMethods: string[] }
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails | { validationErrors: Record<string, string[]> } |
NOT_FOUND | User not found | undefined |
Throws NAuthException with the codes listed above.
VALIDATION_FAILED details
When DTO validation fails, details includes:
{
"validationErrors": {
"sub": ["User sub must be a valid UUID v4 format"]
}
}
Example
- NestJS
- Express
- Fastify
@Get('mfa/methods')
async getMethods(@CurrentUser() user: IUser) {
return await this.mfaService.getAvailableMethods({ sub: user.sub });
}
app.get('/mfa/methods', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.getAvailableMethods({ sub: req.user.sub });
res.json(result);
});
fastify.get(
'/mfa/methods',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async () => {
const user = nauth.helpers.getCurrentUser();
return nauth.mfaService.getAvailableMethods({ sub: user.sub });
}),
);
getChallengeData()
Get MFA challenge data during MFA_REQUIRED challenge. Currently only used for passkey authentication to get WebAuthn options. For passkey method, stores challenge in session metadata for verification.
async getChallengeData(dto: GetChallengeDataDTO): Promise<GetChallengeDataResponseDTO>
Parameters
dto-GetChallengeDataDTO
Returns
GetChallengeDataResponseDTO-{ challengeData: Record<string, unknown> }
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails, challenge not MFA_REQUIRED, no user in session, provider not registered, or method doesn't support challenge data | { validationErrors: Record<string, string[]> } or undefined |
CHALLENGE_INVALID | Challenge session not found or invalid | undefined |
CHALLENGE_EXPIRED | Challenge session expired | undefined |
CHALLENGE_ALREADY_COMPLETED | Challenge session already completed | undefined |
CHALLENGE_MAX_ATTEMPTS | Maximum challenge attempts exceeded | undefined |
NOT_FOUND | No MFA device registered for method | { deviceType: 'sms' | 'email' | 'passkey' } |
Throws NAuthException with the codes listed above.
VALIDATION_FAILED details
When DTO validation fails, details includes:
{
"validationErrors": {
"session": ["Session token must be a valid UUID v4 format"],
"method": ["Method must be: passkey"]
}
}
When validation fails for other reasons (challenge type, user, provider not registered, method doesn't support challenge data), details is undefined.
NOT_FOUND details
When no device is registered for the method, details includes:
{
"deviceType": "passkey"
}
Example
- NestJS
- Express
- Fastify
@Get('mfa/challenge')
async getChallenge(@Query('session') session: string) {
return await this.mfaService.getChallengeData({ session, method: 'passkey' });
}
app.get('/mfa/challenge', async (req, res) => {
const result = await nauth.mfaService.getChallengeData({
session: req.query.session,
method: 'passkey',
});
res.json(result);
});
fastify.get(
'/mfa/challenge',
{ preHandler: nauth.helpers.public() },
nauth.adapter.wrapRouteHandler(async (req) => {
const result = await nauth.mfaService.getChallengeData({
session: req.query.session as string,
method: 'passkey',
});
return result;
}),
);
getMfaStatus()
Get comprehensive MFA status for the current authenticated user including enabled status, configured methods, available methods, backup codes, and exemption information.
async getMfaStatus(): Promise<GetMFAStatusResponseDTO>
Returns
Errors
| Code | When | Details |
|---|---|---|
FORBIDDEN | Not authenticated (no user in context) | undefined |
Throws NAuthException with the codes listed above.
Example
- NestJS
- Express
- Fastify
@Get('mfa/status')
async getStatus() {
return await this.mfaService.getMfaStatus();
}
app.get('/mfa/status', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.getMfaStatus();
res.json(result);
});
fastify.get(
'/mfa/status',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async () => {
return nauth.mfaService.getMfaStatus();
}),
);
getSetupData()
Get MFA setup data during MFA_SETUP_REQUIRED challenge. Returns provider-specific setup data (QR code for TOTP, options for Passkey, etc.).
async getSetupData(dto: GetSetupDataDTO): Promise<GetSetupDataResponseDTO>
Parameters
dto-GetSetupDataDTO
Returns
GetSetupDataResponseDTO-{ setupData: Record<string, unknown> }
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails, challenge not MFA_SETUP_REQUIRED, no user in session, or provider not registered | { validationErrors: Record<string, string[]> } or undefined |
CHALLENGE_INVALID | Challenge session not found | undefined |
CHALLENGE_EXPIRED | Challenge session expired | undefined |
CHALLENGE_ALREADY_COMPLETED | Challenge session already completed | undefined |
CHALLENGE_MAX_ATTEMPTS | Maximum challenge attempts exceeded | undefined |
PHONE_REQUIRED | SMS method requires phone number | undefined |
Throws NAuthException with the codes listed above.
VALIDATION_FAILED details
When DTO validation fails, details includes:
{
"validationErrors": {
"session": ["Session token must be a valid UUID v4 format"],
"method": ["Method must be one of: sms, email, totp, passkey"]
}
}
Example
- NestJS
- Express
- Fastify
@Get('mfa/setup')
async getSetup(@Query('session') session: string, @Query('method') method: string) {
return await this.mfaService.getSetupData({ session, method: method as MFAMethod });
}
app.get('/mfa/setup', async (req, res) => {
const result = await nauth.mfaService.getSetupData({
session: req.query.session,
method: req.query.method,
setupData: req.body.setupData,
});
res.json(result);
});
fastify.get(
'/mfa/setup',
{ preHandler: nauth.helpers.public() },
nauth.adapter.wrapRouteHandler(async (req) => {
const result = await nauth.mfaService.getSetupData({
session: req.query.session as string,
method: req.query.method as MFAMethod,
setupData: req.body?.setupData,
});
return result;
}),
);
getUserDevices()
Get all active MFA devices for the current authenticated user. User is obtained from the authenticated context.
async getUserDevices(dto?: GetUserDevicesDTO): Promise<GetUserDevicesResponseDTO>
Parameters
dto-GetUserDevicesDTO(optional, empty DTO - user obtained from context)
Returns
GetUserDevicesResponseDTO-{ devices: MFADeviceResponseDTO[] }
Each device contains:
| Property | Type | Description |
|---|---|---|
id | number | Device ID |
type | MFADeviceMethod | Device type (totp, sms, email, passkey) |
name | string | Device name |
isPreferred | boolean | Whether this is the preferred device |
isActive | boolean | Whether the device is active |
createdAt | Date | Device creation timestamp |
Errors
| Code | When | Details |
|---|---|---|
FORBIDDEN | Not authenticated (no user in context) | undefined |
Throws NAuthException with the codes listed above.
Example
- NestJS
- Express
- Fastify
@Get('mfa/devices')
async getDevices(): Promise<GetUserDevicesResponseDTO> {
return await this.mfaService.getUserDevices({});
}
app.get('/mfa/devices', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.getUserDevices({});
res.json(result);
});
fastify.get(
'/mfa/devices',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async () => {
return nauth.mfaService.getUserDevices({});
}),
);
hasProvider()
Check if an MFA provider is registered.
hasProvider(dto: HasProviderDTO): HasProviderResponseDTO
Parameters
dto-HasProviderDTO
Returns
HasProviderResponseDTO-{ hasProvider: boolean }
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails | { validationErrors: Record<string, string[]> } |
Throws NAuthException with the codes listed above.
VALIDATION_FAILED details
When DTO validation fails, details includes:
{
"validationErrors": {
"methodName": ["Method name must be a string"]
}
}
Example
- NestJS
- Express
- Fastify
const result = this.mfaService.hasProvider({ methodName: 'totp' });
if (result.hasProvider) {
// TOTP provider is available
}
const result = nauth.mfaService.hasProvider({ methodName: 'totp' });
if (result.hasProvider) {
// TOTP provider is available
}
const result = nauth.mfaService.hasProvider({ methodName: 'totp' });
if (result.hasProvider) {
// TOTP provider is available
}
listProviders()
Get all registered provider method names.
listProviders(): ListProvidersResponseDTO
Parameters
None.
Returns
ListProvidersResponseDTO-{ providers: string[] }
Errors
None. This method does not throw errors.
Example
- NestJS
- Express
- Fastify
const result = this.mfaService.listProviders();
// Returns: { providers: ['totp', 'sms', 'passkey'] }
const result = nauth.mfaService.listProviders();
// Returns: { providers: ['totp', 'sms', 'passkey'] }
const result = nauth.mfaService.listProviders();
// Returns: { providers: ['totp', 'sms', 'passkey'] }
removeDevice()
Remove a single MFA device by deviceId (recommended when users can enroll multiple devices for the same method).
async removeDevice(dto: RemoveDeviceDTO): Promise<RemoveDeviceResponseDTO>
Parameters
dto-RemoveDeviceDTO
Returns
RemoveDeviceResponseDTO-{ removedDeviceId: number, removedMethod: string, mfaDisabled: boolean }
Errors
| Code | When |
|---|---|
USER_NOT_FOUND | Device not found or doesn't belong to authenticated user |
Example (NestJS)
@Delete('mfa/devices/:deviceId')
async removeDevice(@Param('deviceId') deviceId: string) {
return await this.mfaService.removeDevice({ deviceId: Number(deviceId) });
}
setMFAExemption()
Grant or revoke a user's exemption from multi-factor authentication requirements. Admin-only operation.
async setMFAExemption(dto: SetMFAExemptionDTO): Promise<SetMFAExemptionResponseDTO>
Parameters
dto-SetMFAExemptionDTO
Returns
SetMFAExemptionResponseDTO-{ mfaExempt: boolean, mfaExemptReason: string | null, mfaExemptGrantedAt: Date | null }
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails | { validationErrors: Record<string, string[]> } |
NOT_FOUND | User not found | undefined |
Throws NAuthException with the codes listed above.
VALIDATION_FAILED details
When DTO validation fails, details includes:
{
"validationErrors": {
"sub": ["User sub must be a valid UUID v4 format"],
"exempt": ["Exempt must be a boolean"],
"reason": ["Reason must not exceed 500 characters"],
"grantedBy": ["Granted by must not exceed 255 characters"]
}
}
Example
- NestJS
- Express
- Fastify
@Post('mfa/exemption')
async setExemption(@Body() dto: SetMFAExemptionDTO) {
return await this.mfaService.setMFAExemption(dto);
}
app.post('/mfa/exemption', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.setMFAExemption(req.body);
res.json(result);
});
fastify.post(
'/mfa/exemption',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async (req, reply) => {
const result = await nauth.mfaService.setMFAExemption(req.body);
return result;
}),
);
setPreferredDevice()
Set a specific MFA device as preferred by deviceId. This updates both the device's preferred status and the user's preferred method.
async setPreferredDevice(dto: SetPreferredDeviceDTO): Promise<SetPreferredDeviceResponseDTO>
Parameters
dto-SetPreferredDeviceDTO- ContainsdeviceId
Returns
SetPreferredDeviceResponseDTO-{ message: string }
Example (NestJS)
@Post('mfa/devices/:deviceId/preferred')
async setPreferredDevice(@Param() dto: SetPreferredDeviceDTO) {
return await this.mfaService.setPreferredDevice(dto);
}
setup()
Setup MFA device using appropriate provider. Returns provider-specific setup data that varies by method type.
async setup(dto: SetupMFADTO): Promise<SetupMFAResponseDTO>
Parameters
dto-SetupMFADTO
Returns
SetupMFAResponseDTO-{ setupData: Record<string, unknown> }
Response structure varies by method:
TOTP Response:
{
setupData: {
secret: string; // Base32-encoded TOTP secret
qrCode: string; // QR code as data URL (data:image/png;base64,...)
manualEntryKey: string; // Formatted secret for manual entry (e.g., 'ABCD EFGH IJKL MNOP')
issuer: string; // Issuer name from config
accountName: string; // Account name (typically user's email)
}
}
SMS Response:
// If phone already verified (auto-completed):
{
setupData: {
deviceId: number;
autoCompleted: true;
}
}
// If phone not verified (code sent):
{
setupData: {
maskedPhone: string; // Masked phone number (e.g., '***-***-7890')
}
}
Email Response:
// If email already verified (auto-completed):
{
setupData: {
deviceId: number;
autoCompleted: true;
}
}
// If email not verified (code sent):
{
setupData: {
maskedEmail: string; // Masked email address (e.g., 'u***r@example.com')
}
}
Passkey Response:
{
setupData: {
options: {
challenge: string;
rp: { name: string; id: string };
user: { id: string; name: string; displayName: string };
pubKeyCredParams: Array<{ type: 'public-key'; alg: number }>;
timeout: number;
attestation: 'none' | 'indirect' | 'direct';
authenticatorSelection?: {
authenticatorAttachment?: 'platform' | 'cross-platform';
requireResidentKey?: boolean;
userVerification?: 'required' | 'preferred' | 'discouraged';
};
excludeCredentials?: Array<{ id: string; type: 'public-key'; transports?: string[] }>;
};
}
}
Setup Data by Method:
- TOTP: No
setupDatarequired (or empty object{}) - SMS:
{ phoneNumber: string, deviceName?: string }- Phone number in E.164 format (e.g.,'+1234567890') - Email:
{ email: string, deviceName?: string }- Email address - Passkey: No
setupDatarequired (or empty object{})
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails, provider not registered, method not enabled, service unavailable, or email required | { validationErrors: Record<string, string[]> } or undefined |
NOT_FOUND | User not found | undefined |
PHONE_REQUIRED | SMS method requires phone number | undefined |
Throws NAuthException with the codes listed above.
VALIDATION_FAILED details
When DTO validation fails, details includes:
{
"validationErrors": {
"sub": ["User sub must be a valid UUID v4 format"],
"methodName": ["Method name must be one of: totp, sms, email, passkey"]
}
}
When provider not registered, method not enabled, service unavailable, or email required, details is undefined.
Example - TOTP Setup
- NestJS
- Express
- Fastify
@Post('mfa/setup/totp')
async setupTOTP(@CurrentUser() user: IUser) {
const result = await this.mfaService.setup({
methodName: 'totp',
// setupData not required for TOTP
});
// result.setupData contains: { secret, qrCode, manualEntryKey, issuer, accountName }
return result;
}
app.post('/mfa/setup/totp', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.setup({
methodName: 'totp',
});
res.json(result);
});
fastify.post(
'/mfa/setup/totp',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async () => {
return nauth.mfaService.setup({
methodName: 'totp',
});
}),
);
Example - SMS Setup
- NestJS
- Express
- Fastify
@Post('mfa/setup/sms')
async setupSMS(@CurrentUser() user: IUser, @Body() body: { phoneNumber: string; deviceName?: string }) {
const result = await this.mfaService.setup({
methodName: 'sms',
setupData: {
phoneNumber: body.phoneNumber, // E.164 format: '+1234567890'
deviceName: body.deviceName, // Optional: 'My iPhone'
},
});
// If phone verified: result.setupData = { deviceId, autoCompleted: true }
// If phone not verified: result.setupData = { maskedPhone: '***-***-7890' }
return result;
}
app.post('/mfa/setup/sms', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.setup({
methodName: 'sms',
setupData: {
phoneNumber: req.body.phoneNumber,
deviceName: req.body.deviceName,
},
});
res.json(result);
});
fastify.post(
'/mfa/setup/sms',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async (req) => {
const user = nauth.helpers.getCurrentUser();
return nauth.mfaService.setup({
methodName: 'sms',
setupData: {
phoneNumber: req.body.phoneNumber,
deviceName: req.body.deviceName,
},
});
}),
);
Example - Email Setup
- NestJS
- Express
- Fastify
@Post('mfa/setup/email')
async setupEmail(@CurrentUser() user: IUser, @Body() body: { email?: string; deviceName?: string }) {
const result = await this.mfaService.setup({
methodName: 'email',
setupData: {
email: body.email || user.email, // Optional if user.email exists
deviceName: body.deviceName, // Optional: 'My Email'
},
});
// If email verified: result.setupData = { deviceId, autoCompleted: true }
// If email not verified: result.setupData = { maskedEmail: 'u***r@example.com' }
return result;
}
app.post('/mfa/setup/email', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.setup({
methodName: 'email',
setupData: {
email: req.body.email || req.user.email,
deviceName: req.body.deviceName,
},
});
res.json(result);
});
fastify.post(
'/mfa/setup/email',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async (req) => {
const user = nauth.helpers.getCurrentUser();
return nauth.mfaService.setup({
methodName: 'email',
setupData: {
email: req.body.email || user.email,
deviceName: req.body.deviceName,
},
});
}),
);
Example - Passkey Setup
- NestJS
- Express
- Fastify
@Post('mfa/setup/passkey')
async setupPasskey(@CurrentUser() user: IUser) {
const result = await this.mfaService.setup({
methodName: 'passkey',
// setupData not required for Passkey
});
// result.setupData.options contains WebAuthn registration options
// Pass to navigator.credentials.create({ publicKey: result.setupData.options })
return result;
}
app.post('/mfa/setup/passkey', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.setup({
methodName: 'passkey',
});
res.json(result);
});
fastify.post(
'/mfa/setup/passkey',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async () => {
const user = nauth.helpers.getCurrentUser();
return nauth.mfaService.setup({
methodName: 'passkey',
});
}),
);
verifyCode()
Verify MFA code using appropriate provider. Routes verification to the correct provider based on method name.
async verifyCode(dto: VerifyMFACodeDTO): Promise<VerifyMFACodeResponseDTO>
Parameters
| Property | Type | Required | Description |
|---|---|---|---|
sub | string | Yes | User sub (UUID v4) |
methodName | string | Yes | MFA method name. Must be one of: totp, sms, email, passkey, backup |
code | string | Record<string, unknown> | Yes | Verification code or credential. For totp/sms/email/backup: string code. For passkey: object with credential and expectedChallenge |
deviceId | number | No | Optional device ID to verify against specific device |
Returns
| Property | Type | Description |
|---|---|---|
valid | boolean | True if verification succeeds |
Errors
| Code | When | Details |
|---|---|---|
VALIDATION_FAILED | DTO validation fails, provider not registered, or backup code verification unavailable | { validationErrors: Record<string, string[]> } or undefined |
NOT_FOUND | User not found | undefined |
VERIFICATION_CODE_INVALID | Invalid verification code (SMS/Email methods) | { attemptsRemaining: number } or undefined |
VERIFICATION_CODE_EXPIRED | Verification code has expired (SMS/Email methods) | undefined |
VERIFICATION_TOO_MANY_ATTEMPTS | Too many verification attempts (SMS/Email methods) | { maxAttempts: number, currentAttempts: number } or undefined |
Example
- NestJS
- Express
- Fastify
@Post('mfa/verify')
async verify(@CurrentUser() user: IUser, @Body() body: { method: string; code: string }) {
return await this.mfaService.verifyCode({
sub: user.sub,
methodName: body.method,
code: body.code
});
}
app.post('/mfa/verify', requireAuth(), async (req, res) => {
const result = await nauth.mfaService.verifyCode({
sub: req.user.sub,
methodName: req.body.method,
code: req.body.code,
deviceId: req.body.deviceId,
});
res.json(result);
});
fastify.post(
'/mfa/verify',
{ preHandler: nauth.helpers.requireAuth() },
nauth.adapter.wrapRouteHandler(async (req) => {
const user = nauth.helpers.getCurrentUser();
return nauth.mfaService.verifyCode({
sub: user.sub,
methodName: req.body.method,
code: req.body.code,
deviceId: req.body.deviceId,
});
}),
);
Error Handling
- MFA Packages - MFA provider packages
- Challenge System - MFA challenge flows
- NAuthException - Error handling
- DTOs Overview - All available DTOs