npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@enteocode/nestjs-mfa

v1.0.2

Published

Implementation agnostic RFC-compliant Multi-Factor Authentication (2FA/MFA) module for NestJS with recovery code support

Readme

NestJS 2FA/MFA Module

Build Status License Coverage

RFC‑compliant Multi‑Factor Authentication (2FA/MFA) module for NestJS, built on otplib. Pluggable storage, QR codes, recovery codes, and event‑driven architecture.

Table of Contents

Requirements and Peer Dependencies

Features

  • Context-aware: HTTP, RPC, WebSocket, etc.
  • Services: Registration, validation, QR generation, recovery
  • Security: Encrypted storage
  • Extensible: Pluggable extractors, decorators, and event hooks
  • Optimized: Streamable QR codes, low memory footprint

Installation

npm i -S @enteocode/nestjs-mfa

Module Registration

Configure using forRoot or forRootAsync, according to NestJS standards:

app.module.ts

import { Module } from '@nestjs/common';
import { MfaModule } from '../src';
import { createKeyv } from '@keyv/redis';

@Module({
    imports: [
        MfaModule.forRoot({
            // Displayed in authenticator apps
            issuer: 'My Application',
            
            // Persistent key-value adapter for KeyV
            store: createKeyv(/* ... */),
            
            // AES-256-GCM
            cipher: 'my-32-byte-encryption-key'
        })
    ]
})
export class AppModule {}

Important: The EventEmitterModule must be registered.

Configuration

| Name | Type | Required | Default | Description | |--------------|-----------------------|----------|---------|-------------------------------------| | issuer | string | Yes | – | App name for authenticator display | | store | Keyv | Yes | – | Persistent key-value store | | ttl | number | No | 30 | TOTP token lifetime (seconds) | | serializer | SerializerInterface | No | V8 | Data serialization (JSON, V8, etc.) |
| cipher | string | No | – | AES-256 key for encrypted storage |

Hint: Provide a cipher to protect user secrets according to NIS2 and GDPR standards

Store

Persistent storage is required.

Supported Keyv adapters: Etcd, Memcache, Mongo, MySQL, Postrgres, Redis, SQLite, Valkey.

Usage

After registration, you can use the MfaService to implement it to your setup by injecting it to your service or controller.

HTTP

mfa.controller.ts

import { Body, Controller, InternalServerErrorException, ParseUUIDPipe, Post, StreamableFile } from '@nestjs/common';
import { Format, MfaService, TokenType } from '@enteocode/nestjs-mfa';
import { TokenVerificationRequest } from './mfa.token-verification.request.ts';

@Controller('mfa')
class MfaController {
    constructor(private readonly mfa: MfaService) {}

    @Post('enable/:user')
    public async enable(@Param('user', ParseUUIDPipe) user: string): Promise<StreamableFile> {
        const secret = await this.mfa.enable(user);

        if (!secret) {
            throw new InternalServerErrorException('Cannot enable MFA for user');
        }
        return this.mfa.generate(user, TokenType.AUTHENTICATOR, Format.PNG);
    }

    @Post('verify')
    public async verify(@Body() { user, token }: TokenVerificationRequest): Promise<boolean> {
        return this.mfa.verify(user, token);
    }
}

Validation DTO

token-verification.request.ts

import { IsUUID } from 'class-validator';
import { IsToken, Token } from '@enteocode/nestjs-mfa';
 
// ValidationPipe based flow
 
export class TokenVerificationRequest {
    @IsUUID()
    user: string;
    
    @IsToken() // Built-in validator
    token: Token;
}

Microservices

mfa.controller.ts

import { Controller } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';

@Controller()
class MfaController {
    constructor(private readonly mfa: MfaService) {}
    
    @MessagePattern({ cmd: 'mfa.enable '})
    public async enable(@Payload() user: string): Promise<string> {
        /* ... */
    }
}

QR Code

The QR code contains a Key URI, originally defined by Google for its Authenticator app and later adopted by other authenticator applications. This URI encodes the secret along with key properties (such as account name and issuer) and is used to register the authenticator with a user’s device.

Conceptually, it's similar to asymmetric cryptography: the secret acts like a private key, and the app uses it to generate public one-time codes.

You can choose to generate the QR code on the client side or the backend side:

  • Client-side generation reduces backend load and cost.
  • Backend-side generation is handled by the module using streamed rendering to keep memory and CPU usage minimal.

QR codes are only required for the initial pairing with the authenticator app.

Example

mfa.authenticator.controller.ts

import { Controller, Get, Param, ParseUUIDPipe, StreamableFile } from '@nestjs/common';
import { MfaService, TokenType } from '@enteocode/nestjs-mfa';

@Controller('mfa/generate')
class MfaAuthenticatorController {
    constructor(private readonly mfa: MfaService) {}

    @Get(':user/qr')
    public generateQrCode(@Param('user', ParseUUIDPipe) user: string): Promise<StreamableFile> {
        return this.mfa.generate(user, TokenType.AUTHENTICATOR, Format.WEBP)
    }

    @Get(':user/uri')
    public generateKeyUri(@Param('user', ParseUUIDPipe) user: string): Promise<string> {
        return this.mfa.generate(user, TokenType.AUTHENTICATOR)
    }
    
    @Get(':user/token')
    public generateToken(@Param('user', ParseUUIDPipe) user: string): Promise<string> {
        // Manual token creation instead of authenticator
        // Useful to generate one-time password for email based second factor
        
        return this.mfa.generate(user, TokenType.TIMEOUT, { step: 5 * 60, digits: 8 })
    }
}

Formats

| Format | Avg. Size | MIME Type | |--------|-----------|--------------| | WebP | 242B | image/webp | | PNG | 406B | image/png | | AVIF | 674B | image/avif | | JPEG | 3.7KB | image/jpeg |

Note: Sizes measured with test data, actual results may vary

Advanced Usage

Credential Extractors

To avoid manually verifying tokens across multiple endpoints, you can implement credential extractors. These provide a context-specific mechanism for extracting credentials (such as user ID and token) and feeding them into a parameter decorator. This makes the solution reusable, streamlined, and transport-agnostic (HTTP, RPC, WebSockets, etc.).

Hint: you can create multiple extractors and the first one will be ued which supports the actual ExecutionContext.

Example

An example of a HTTP extractor, that gets credentials from the header, with the pattern:

HTTP Header

X-MFA: otp://user:012345@authenticator

mfa.http.credentials-extractor.ts

import { ExecutionContext, Injectable } from '@nestjs/common';
import { FastifyRequest } from 'fastify';
import { MfaCredentialsExtractor, MfaCredentialsExtractorInterface } from '@enteocode/nestjs-mfa';

@MfaCredentialsExtractor('http')
@Injectable()
class MfaHttpCredentialsExtractor implements MfaCredentialsExtractorInterface {
    public supports(context: ExecutionContext): boolean {
        return Boolean(this.getHeader(context));
    }

    public getUserIdentifier(context: ExecutionContext): Identifier {
        return URL.parse(this.getHeader(context)).username;
    }

    public getToken(context: ExecutionContext): Token {
        return URL.parse(this.getHeader(context)).password;
    }
    
    // Helper
    
    private getHeader(context: ExecutionContext, header: string = 'x-mfa'): string {
        return context.switchToHttp().getRequest<FastifyRequest>().headers[header];
    }
}

Hint: If you leave the MfaCredentialsExtractor decorator's parameter empty, it will be called to check support on every context.

You have to add this extractor to your list of providers to be part of the Dependency Injection container in order to be detectable.
You are ready to use:

mfa.controller.ts

import { Controller, Get } from '@nestjs/common';
import { MfaCredentials, MfaCredentialsInterface } from '@enteocode/nestjs-mfa';

@Controller()
class MfaController {
    @Get()
    public async login(@MfaCredentials({ required: true, validate: true }) credentials: MfaCredentialsInterface) {
        // credentials.user     [Identifier]    The unique identifier of the user (email, UUID, etc.)
        // credentials.token    [Token]         The 6-digit token
    }
}

Hint: All options are optional, credentials will be automatically validated if validate is true

Events

Authentication Events

| Event | Payload | Description | |----------------|------------------------------------|------------------------| | mfa.enabled | { user: string, secret: string } | MFA activated for user | | mfa.disabled | { user: string } | MFA deactivated | | mfa.failed | { user: string, token: string } | Invalid token provided |

Recovery Events

| Event | Payload | Description | |-------------------------|---------------------------------------|-------------------------------| | mfa.recovery.enabled | { user: string, codes: Set<string>} | Backup codes generated | | mfa.recovery.disabled | { user: string } | Backup codes deleted | | mfa.recovery.used | { user: string, code: string } | Backup code consumed | | mfa.recovery.failed | { user: string, code: string } | Backup code validation failed |

Example

app.event-listener.ts

import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AuthenticationFailedEvent, EventType } from '@enteocode/nestjs-mfa';

@Injectable()
class AppEventListener {
    @OnEvent(EventType.AUTHENTICATION_FAILED)
    onMfaAuthenticationFailed(event: AuthenticationFailedEvent) {
        // Block user after 5 attempts
    }
}

Compliance

License

MIT © 2025, Ádám Székely