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

@next-nest-auth/nestauth

v2.0.0

Published

Drop-in authentication module for NestJS: local (username/password) login, Google and Facebook OAuth2, and JWT access + refresh token issuance and validation, wired up via one NestAuthModule.forRoot() call. Stateless and API-first — pairs well with Next.j

Readme

NestAuth — JWT, Local, Google & Facebook Authentication for NestJS

npm version npm downloads license

@next-nest-auth/nestauth is a drop-in authentication module for NestJS applications. Register one module, implement one interface, and get a working login, refresh-token, Google OAuth2, and Facebook OAuth2 flow — all issuing short-lived JWT access tokens and long-lived JWT refresh tokens — without hand-wiring Passport strategies, guards, and controllers yourself.

It's built for NestJS REST APIs consumed by any frontend — Next.js, React, Vue, mobile apps, or another backend — since everything it exposes is plain JSON over HTTP.

Table of Contents

Why NestAuth

Features

  • Local login — username/password (or any credential shape you define) via a validateUser() method you implement.
  • JWT access + refresh tokens — short-lived access tokens, long-lived refresh tokens, cryptographically tagged so one can never be used in place of the other.
  • Google OAuth2 and Facebook OAuth2 login, wired up automatically — implement google()/facebook() on your UserService and the redirect/callback routes just work.
  • A ready-made NestAuthJwtGuard to protect any route in your own app with one decorator.
  • Consistent JSON error responses (statusCode, message, path, app) across every auth endpoint, for both Express and Fastify.
  • Multiple isolated auth instances in one app — mount two independent NestAuthModule.forRoot() calls under different route prefixes (e.g. separate customer vs. admin auth) without route or strategy collisions.
  • You own the data layer. NestAuth never touches a database or hashes a password itself — you implement NestAuthInterface against whatever user store and hashing library you already use, so it doesn't fight your existing schema.

Who this is for

  • Teams building a NestJS REST/GraphQL backend for a separate frontend (Next.js, React Native, a SPA) that need email/password login plus "Sign in with Google/Facebook" without assembling Passport strategies by hand.
  • Projects that need refresh-token rotation semantics (access vs. refresh tokens that can't be swapped for one another) out of the box.
  • Multi-tenant or multi-surface backends that want two separately configured auth realms (e.g. /nestauth for end users, /admin-auth for staff) in the same Nest application.

Prerequisites

  • Node.js >= 18
  • A NestJS application (v11+) with @nestjs/common, @nestjs/config, @nestjs/jwt, and @nestjs/passport installed — these are peerDependencies, so your app supplies them.

Installation

1. Install NestJS (if you don't already have an app)

npm i -g @nestjs/cli
nest new nestauth-app
cd nestauth-app

2. Install @next-nest-auth/nestauth

npm install @next-nest-auth/nestauth

Quick Start

1. Register NestAuthModule

// app.module.ts
import { Module } from "@nestjs/common";
import { NestAuthModule } from "@next-nest-auth/nestauth";
import { UserModule } from "./user.module";
import { UserService } from "./user.service";

@Module({
    imports: [
        NestAuthModule.forRoot({
            UserModule: UserModule,
            UserService: UserService,
            jwtSecret: process.env.JWT_SECRET, // required — throws on startup if missing
            jwtExpiresIn: "15m",
            jwtRefreshTokenExpiresIn: "7d",
            routePrefix: "nestauth",
        }),
    ],
})
export class AppModule {}

2. Implement NestAuthInterface on your UserService

validateUser() and getUserById() are required. google()/facebook() are optional — implement them only if you enable those login methods; calling the corresponding endpoint without an implementation returns a 501 Not Implemented instead of crashing.

// user.service.ts
import { Injectable } from "@nestjs/common";
import {
    JwtPayloadType,
    NestAuthInterface,
    GoogleProfileType,
    FacebookProfileType,
} from "@next-nest-auth/nestauth";

@Injectable()
export class UserService implements NestAuthInterface {
    async validateUser(params: { username: string; password: string }): Promise<JwtPayloadType> {
        // Look up the user and verify the password (e.g. with bcrypt) against your own store.
        // Return null to reject the login attempt.
        return { sub: 1, name: "John Doe", email: "[email protected]", role: "user" };
    }

    async getUserById(id: number | string): Promise<JwtPayloadType> {
        // Used to re-hydrate the user when a refresh token is redeemed.
        return { sub: id, name: "John Doe", email: "[email protected]", role: "user" };
    }

    // Optional — only needed if you use Google login.
    async google(profile: GoogleProfileType): Promise<JwtPayloadType> {
        // Find-or-create a user from profile.email, return the JWT payload for them.
        return { sub: profile.id, name: profile.firstName, email: profile.email };
    }

    // Optional — only needed if you use Facebook login.
    async facebook(profile: FacebookProfileType): Promise<JwtPayloadType> {
        return { sub: profile.id, name: profile.firstName, email: profile.email };
    }
}

3. Log in

curl -X POST http://localhost:3000/nestauth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"john","password":"secret"}'

Whatever body you send is passed straight to validateUser() — use any field names your app needs (username/password, email/OTP, mobile/PIN, etc.).

{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsInJvbGUiOiJ1c2VyIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc4Njk0OTI5MCwiZXhwIjoxNzg2OTUwMTkwfQ.V6itfqLh6hB1-Yj6S88ozBfLp6iTESVhC0Vyroe-Ghw",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsInJvbGUiOiJ1c2VyIiwidHlwZSI6InJlZnJlc2giLCJpYXQiOjE3ODY5NDkyOTAsImV4cCI6MTc4NzU1NDA5MH0.Wt2zC32nO1e16Wa_FrJeyNDCnggwhgc9l840DUpE7rk",
    "accessTokenExpiresIn": "15m",
    "refreshTokenExpiresIn": "7d"
}

Decoded, the access token's payload is:

{ "sub": 1, "name": "John Doe", "email": "[email protected]", "role": "user", "type": "access", "iat": 1786949290, "exp": 1786950190 }

The refresh token carries the same claims with "type": "refresh" — the type claim is what keeps the two from being interchangeable (see Security Notes).

Configuration Reference

Options passed to NestAuthModule.forRoot(options):

| Option | Type | Required | Description | | --- | --- | --- | --- | | UserModule | Type<any> | Yes | The Nest module that provides your UserService. | | UserService | Type<NestAuthInterface> | Yes | Your class implementing NestAuthInterface. | | jwtSecret | string | Yes | Secret used to sign/verify all tokens. forRoot() throws if omitted — there is no default. | | jwtExpiresIn | string \| number | No | Access token lifetime (e.g. "15m"). Defaults to 15m. | | jwtRefreshTokenExpiresIn | string \| number | No | Refresh token lifetime (e.g. "7d"). Defaults to 7d. | | routePrefix | string | No | Mounts the auth controller at <routePrefix>/nestauth instead of /nestauth, and also namespaces DI tokens and Passport strategy names — see Multiple Auth Instances. |

API Reference

All routes below are mounted under /nestauth by default, or /<routePrefix>/nestauth if routePrefix is set.

| Method | Path | Guard | Purpose | | --- | --- | --- | --- | | POST | /login | local | Authenticate with validateUser(); returns an access + refresh token pair. | | POST | /refresh-token | none (token itself is the credential) | Exchange a refresh token for a new access + refresh token pair. | | GET | /google | Google OAuth | Starts the Google login redirect. | | GET | /google-redirect | Google OAuth | Google's callback target; calls UserService.google() and returns tokens. | | GET | /facebook | Facebook OAuth | Starts the Facebook login redirect. | | GET | /facebook-redirect | Facebook OAuth | Facebook's callback target; calls UserService.facebook() and returns tokens. | | GET | /logout | JWT (access token) | Stateless logout — confirms the token is valid and tells the client to discard it. | | ALL | / | none | Returns a welcome string; useful as a health check for the auth routes. |

Local Login

POST /nestauth/login
Content-Type: application/json

{ "username": "john", "password": "secret" }

The request body is whatever validateUser() expects — NestAuth doesn't impose a schema on it.

Refresh Token

POST /nestauth/refresh-token
Content-Type: application/json

{ "refreshToken": "your-refresh-token" }

The legacy key refresh_token is also accepted. Only tokens issued with type: "refresh" are accepted — presenting an access token here fails, and vice versa on protected routes.

On success: a new { accessToken, refreshToken, accessTokenExpiresIn, refreshTokenExpiresIn } pair, same shape as login.

On failure:

{
    "statusCode": 401,
    "message": "Invalid or expired refresh token",
    "path": "/nestauth/refresh-token",
    "app": "nestauth"
}

Google OAuth

Send the browser to GET /nestauth/google to start the flow. On success, Google redirects to GET /nestauth/google-redirect, which calls UserService.google(profile) and returns the same token pair shape as login. Requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and BASE_URL to be set (see Environment Variables), and google() implemented on your UserService — otherwise this returns 501 Not Implemented.

Facebook OAuth

Same shape as Google: GET /nestauth/facebook starts the flow, GET /nestauth/facebook-redirect is the callback, and it calls UserService.facebook(profile). Requires FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, BASE_URL, and a facebook() implementation.

Logout

GET /nestauth/logout
Authorization: Bearer <accessToken>

NestAuth is stateless — there's no server-side session or token store to invalidate. This endpoint just validates the token and returns { "message": "Logged out. Discard the access and refresh tokens on the client." }; your client is responsible for discarding both tokens. If you need server-side revocation (e.g. for a "log out everywhere" feature), you'll need to add a token blacklist or a tokenVersion check in getUserById() yourself.

Protecting Your Own Routes

Use the exported NestAuthJwtGuard on any route in your own application:

import { Controller, Get, UseGuards, Request } from "@nestjs/common";
import { NestAuthJwtGuard } from "@next-nest-auth/nestauth";

@Controller("user")
export class AppController {
    @Get("/profile")
    @UseGuards(NestAuthJwtGuard)
    getProfile(@Request() req) {
        return req.user; // the JWT payload, minus iat/exp/type
    }
}

Error Response Shape

Every error from a NestAuth route (login, refresh, OAuth, logout) is normalized to:

{ "statusCode": 401, "message": "...", "path": "/nestauth/...", "app": "nestauth" }

This normalization only applies to NestAuth's own controller. Errors from NestAuthJwtGuard used on your own routes use Nest's default exception format unless you apply your own filter.

Multiple Auth Instances

Because routePrefix namespaces both the mounted path and the internal DI tokens/Passport strategy names, you can register NestAuthModule.forRoot() more than once in the same app for independent auth realms:

@Module({
    imports: [
        NestAuthModule.forRoot({
            UserModule: CustomerModule,
            UserService: CustomerAuthService,
            jwtSecret: process.env.CUSTOMER_JWT_SECRET,
            routePrefix: "customer",
        }),
        NestAuthModule.forRoot({
            UserModule: AdminModule,
            UserService: AdminAuthService,
            jwtSecret: process.env.ADMIN_JWT_SECRET,
            routePrefix: "admin",
        }),
    ],
})
export class AppModule {}

This mounts routes at /customer/nestauth/... and /admin/nestauth/..., each with its own local/Google/Facebook strategies (namespaced per instance) and its own login/refresh logic.

Known limitation: NestAuthJwtGuard/NestAuthJwtStrategy are not namespaced per instance — Passport registers strategies in a single global registry keyed by name, and every forRoot() call registers its JWT strategy under the same name ("jwt"). With two instances, whichever one is instantiated last silently wins that registry entry, and NestAuthJwtGuard app-wide ends up validating tokens against only that instance's secret. Don't rely on two different jwtSecrets being independently enforced by NestAuthJwtGuard today — use distinct route prefixes for organizing endpoints, not for cryptographic isolation between realms, until this is namespaced (tracked in the changelog as a known issue, not yet fixed).

Environment Variables

jwtSecret is passed explicitly via forRoot() — how you source it (env var, secrets manager) is up to you. A typical .env:

JWT_SECRET=replace-with-a-long-random-secret

If you use the Google or Facebook strategies, also set:

BASE_URL=https://your-app.example.com
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
FACEBOOK_APP_ID=...
FACEBOOK_APP_SECRET=...

Security Notes

  • Rate limiting is your responsibility. /login has no built-in throttling; pair it with @nestjs/throttler or an upstream WAF/gateway to mitigate credential stuffing.
  • Password hashing is your responsibility. validateUser() receives the raw request body — hash and compare passwords yourself (e.g. with bcrypt) before returning a payload.
  • No built-in refresh-token revocation. Refresh tokens are stateless JWTs valid until they expire; there is no server-side blacklist. If you need immediate revocation, check a tokenVersion/sessionId claim against your user store inside getUserById().
  • Validate your own request bodies. NestAuth doesn't apply a ValidationPipe or DTOs to validateUser()'s input — add your own validation if the body shape matters for your app's security.

Changelog

See CHANGELOG.md for release notes, including breaking changes.

License

MIT — see LICENSE.