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

@devoven/authn

v0.1.1

Published

AuthN module for NestJS — hexagonal architecture

Readme

@devoven/authn

Authentication module for NestJS. Covers registration, login, JWT token issuance and refresh, email verification, and password reset.

Installation

npm install @devoven/authn
# or
pnpm add @devoven/authn

bcrypt is a regular dependency and is installed automatically.

Peer dependencies

This package requires the standard NestJS peer dependencies plus @nestjs/jwt, class-validator, and class-transformer. If you do not already have them:

npm install @nestjs/jwt class-validator class-transformer

Quick Start

import { Module } from '@nestjs/common';
import { AuthNModule } from '@devoven/authn';

// Your implementations of the four required repository ports:
import { PrismaUserRepository } from './auth/prisma-user.repository';
import { PrismaRefreshTokenRepository } from './auth/prisma-refresh-token.repository';
import { PrismaVerificationTokenRepository } from './auth/prisma-verification-token.repository';
import { PrismaResetTokenRepository } from './auth/prisma-reset-token.repository';

@Module({
  imports: [
    AuthNModule.register({
      jwtSecret: process.env.JWT_SECRET,
      userRepository: PrismaUserRepository,
      refreshTokenRepository: PrismaRefreshTokenRepository,
      verificationTokenRepository: PrismaVerificationTokenRepository,
      resetTokenRepository: PrismaResetTokenRepository,
    }),
  ],
})
export class AppModule {}

The four repository options are required. All other options are optional with sensible defaults.

Module Options

interface AuthNModuleOptions {
  // Required
  userRepository: Type<UserRepositoryPort> | UserRepositoryPort;
  refreshTokenRepository: Type<RefreshTokenRepositoryPort> | RefreshTokenRepositoryPort;
  verificationTokenRepository: Type<VerificationTokenRepositoryPort> | VerificationTokenRepositoryPort;
  resetTokenRepository: Type<ResetTokenRepositoryPort> | ResetTokenRepositoryPort;

  // Optional — infrastructure
  jwtSecret?: string;
  tokenProvider?: Type<TokenProviderPort> | TokenProviderPort;
  passwordHasher?: Type<PasswordHasherPort> | PasswordHasherPort;

  // Optional — TTL overrides (seconds)
  accessTokenTtlSeconds?: number;
  refreshTokenTtlSeconds?: number;
  emailVerificationTokenTtlSeconds?: number;
  passwordResetTokenTtlSeconds?: number;
}

| Option | Type | Default | Description | |--------|------|---------|-------------| | userRepository | Class or instance | — | Required. Persistence adapter for users | | refreshTokenRepository | Class or instance | — | Required. Persistence adapter for refresh tokens | | verificationTokenRepository | Class or instance | — | Required. Persistence adapter for email verification tokens | | resetTokenRepository | Class or instance | — | Required. Persistence adapter for password reset tokens | | jwtSecret | string | — | Required when using the default JwtTokenProvider | | tokenProvider | Class or instance | JwtTokenProvider | Override JWT signing/verification | | passwordHasher | Class or instance | BcryptPasswordHasher | Override password hashing | | accessTokenTtlSeconds | number | 900 (15 min) | Access token lifetime | | refreshTokenTtlSeconds | number | 604800 (7 days) | Refresh token lifetime | | emailVerificationTokenTtlSeconds | number | 86400 (24 hours) | Email verification token lifetime | | passwordResetTokenTtlSeconds | number | 1800 (30 min) | Password reset token lifetime |

Async Registration

Use registerAsync when options depend on injected services:

import { AuthNModule } from '@devoven/authn';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    AuthNModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        jwtSecret: config.getOrThrow('JWT_SECRET'),
        userRepository: PrismaUserRepository,
        refreshTokenRepository: PrismaRefreshTokenRepository,
        verificationTokenRepository: PrismaVerificationTokenRepository,
        resetTokenRepository: PrismaResetTokenRepository,
        accessTokenTtlSeconds: config.get('ACCESS_TOKEN_TTL'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

REST API

All endpoints are served under the /auth prefix. The controller applies AuthGuard globally — routes marked @Public() skip token validation.

| Method | Path | Auth | Description | Status | |--------|------|------|-------------|--------| | POST | /auth/register | Public | Register a new user | 204 | | POST | /auth/login | Public | Log in and receive tokens | 200 | | POST | /auth/verify-email | Public | Verify email using a token | 204 | | POST | /auth/reset-password | Public | Set a new password using a reset token | 204 | | POST | /auth/refresh | Public | Exchange a refresh token for a new token pair | 200 | | POST | /auth/change-password | Bearer | Change password for the authenticated user | 204 | | POST | /auth/revoke | Bearer | Revoke a specific refresh token | 204 |

POST /auth/register

Request body:

{
  "identifier": "[email protected]",
  "password": "secret123",
  "metadata": { "name": "Alice" }
}

| Field | Type | Validation | |-------|------|------------| | identifier | string | Required | | password | string | Required, min 8 characters | | metadata | object | Optional |

Response 204 No Content.

POST /auth/login

Request body:

{
  "identifier": "[email protected]",
  "password": "secret123"
}

Response 200 OK:

{
  "accessToken": "<jwt>",
  "refreshToken": "<uuid>"
}

POST /auth/verify-email

Request body:

{ "token": "<verification-token>" }

Response 204 No Content.

POST /auth/reset-password

Request body:

{
  "token": "<reset-token>",
  "newPassword": "newSecret123"
}

Response 204 No Content.

POST /auth/refresh

Request body:

{ "refreshToken": "<uuid>" }

Response 200 OK: same shape as /auth/login.

POST /auth/change-password

Requires Authorization: Bearer <accessToken>.

Request body:

{
  "currentPassword": "secret123",
  "newPassword": "newSecret123"
}

Response 204 No Content.

POST /auth/revoke

Requires Authorization: Bearer <accessToken>.

Request body:

{ "refreshToken": "<uuid>" }

Response 204 No Content.

Guards and Decorators

AuthGuard

AuthGuard validates the Authorization: Bearer <token> header on every request. On success it populates request.user with the decoded token payload (ITokenPayload, which always includes sub: string).

Apply it globally or per-controller:

import { AuthGuard } from '@devoven/authn';

// Global
app.useGlobalGuards(app.get(AuthGuard));

// Per-controller
@UseGuards(AuthGuard)
@Controller('orders')
export class OrdersController {}

AuthGuard is exported from the module and can be injected directly.

@Public()

Skip AuthGuard for a specific route or controller:

import { Public } from '@devoven/authn';

@Public()
@Get('health')
healthCheck() { ... }

@CurrentUser()

Inject the authenticated token payload into a route handler parameter:

import { CurrentUser } from '@devoven/authn';
import type { ITokenPayload } from '@devoven/authn';

@Get('profile')
getProfile(@CurrentUser() user: ITokenPayload) {
  return user.sub; // user ID
}

ITokenPayload is { sub: string; [key: string]: unknown }. Additional claims added during token signing are accessible as extra keys.

Architecture

The module follows hexagonal architecture. authn.module.ts is the composition root — the only place concrete adapters are wired to port tokens.

Driving Port / Token / Use Case Mapping

These are the inbound ports used by the controller and exported for injection in consuming apps:

| Port Interface | DI Token (TOKENS.*) | Use Case | |----------------|----------------------|----------| | RegisterPort | RegisterPort | RegisterUseCase | | LoginPort | LoginPort | LoginUseCase | | IssueTokenPairPort | IssueTokenPairPort | IssueTokenPairUseCase | | RefreshTokenPairPort | RefreshTokenPairPort | RefreshTokenPairUseCase | | RevokeRefreshTokenPort | RevokeRefreshTokenPort | RevokeRefreshTokenUseCase | | ValidateAccessTokenPort | ValidateAccessTokenPort | ValidateAccessTokenUseCase | | RequestEmailVerificationPort | RequestEmailVerificationPort | RequestEmailVerificationUseCase | | RequestPasswordResetPort | RequestPasswordResetPort | RequestPasswordResetUseCase | | ResetPasswordPort | ResetPasswordPort | ResetPasswordUseCase | | VerifyEmailPort | VerifyEmailPort | VerifyEmailUseCase | | ChangePasswordPort | ChangePasswordPort | ChangePasswordUseCase | | LinkAccountPort | LinkAccountPort | LinkAccountUseCase |

Driven Port / Token Mapping

These are the outbound ports that consuming apps must implement:

| Port Interface | DI Token (TOKENS.*) | Default | |----------------|----------------------|---------| | UserRepositoryPort | UserRepositoryPort | — (required) | | RefreshTokenRepositoryPort | RefreshTokenRepositoryPort | — (required) | | VerificationTokenRepositoryPort | VerificationTokenRepositoryPort | — (required) | | ResetTokenRepositoryPort | ResetTokenRepositoryPort | — (required) | | TokenProviderPort | TokenProviderPort | JwtTokenProvider | | PasswordHasherPort | PasswordHasherPort | BcryptPasswordHasher |

Import TOKENS from @devoven/authn to inject any of these by token string.

Custom Adapters

Implementing a User Repository

import { Injectable } from '@nestjs/common';
import { UserRepositoryPort, AuthUserEntity } from '@devoven/authn';

@Injectable()
export class PrismaUserRepository implements UserRepositoryPort {
  constructor(private readonly prisma: PrismaService) {}

  async save(user: AuthUserEntity<Record<string, unknown>>): Promise<void> {
    await this.prisma.user.upsert({ /* ... */ });
  }

  async findByIdentifier(identifier: string) {
    const row = await this.prisma.user.findUnique({ where: { email: identifier } });
    if (!row) return null;
    return AuthUserEntity.reconstitute(
      row.id,
      row.email,
      row.passwordHash,
      row.metadata,
      [],
      row.emailVerifiedAt,
      row.createdAt,
    );
  }

  async findById(id: string) { /* ... */ }
  async findByProvider(provider: string, providerId: string) { /* ... */ }
  async existsByIdentifier(identifier: string) { /* ... */ }
  async existsByProvider(provider: string, providerId: string) { /* ... */ }
}

Use AuthUserEntity.reconstitute(...) to rebuild entities from persistence. Never construct the entity directly.

RefreshTokenRepositoryPort

RefreshTokenRepositoryPort<T> is generic. The type parameter T represents optional extra metadata the consumer can attach to each refresh token (for example, device name, IP address, or any application-specific payload). If you do not need extra metadata, use RefreshTokenRepositoryPort<never> or RefreshTokenRepositoryPort<Record<string, unknown>>.

interface RefreshTokenRepositoryPort<T> {
  save(token: RefreshTokenEntity<T>): Promise<void>;
  findByToken(token: string): Promise<RefreshTokenEntity<T> | null>;
  revokeByToken(token: string): Promise<void>;
  revokeAllByUserId(userId: string): Promise<void>;
}

VerificationTokenRepositoryPort

interface VerificationTokenRepositoryPort {
  save(token: EmailVerificationTokenEntity): Promise<void>;
  findByToken(token: string): Promise<EmailVerificationTokenEntity | null>;
  markUsed(token: string): Promise<void>;
}

ResetTokenRepositoryPort

interface ResetTokenRepositoryPort {
  save(token: PasswordResetTokenEntity): Promise<void>;
  findByToken(token: string): Promise<PasswordResetTokenEntity | null>;
  markUsed(token: string): Promise<void>;
}

PasswordHasherPort

Override the default BcryptPasswordHasher by implementing this interface and passing it via passwordHasher in module options:

interface PasswordHasherPort {
  hash(plaintext: string): Promise<string>;
  compare(plaintext: string, hashed: string): Promise<boolean>;
}

Implementing a Token Provider

Replace JWT with any signing mechanism by implementing TokenProviderPort:

interface TokenProviderPort {
  sign(payload: ITokenPayload, expiresInSeconds: number): Promise<string>;
  verify(token: string): Promise<ITokenPayload>;
}

Example:

import { Injectable } from '@nestjs/common';
import { TokenProviderPort, ITokenPayload } from '@devoven/authn';

@Injectable()
export class CustomTokenProvider implements TokenProviderPort {
  async sign(payload: ITokenPayload, expiresInSeconds: number): Promise<string> { /* ... */ }
  async verify(token: string): Promise<ITokenPayload> { /* ... */ }
}

Pass it via module options:

AuthNModule.register({
  tokenProvider: CustomTokenProvider,
  // jwtSecret is not needed when tokenProvider is overridden
  userRepository: ...,
  /* ... */
})

In-memory repositories (InMemoryUserRepository, InMemoryRefreshTokenRepository, InMemoryVerificationTokenRepository, InMemoryResetTokenRepository) are exported for use in tests.