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

@derek46518/nest-auth-kit

v0.1.0

Published

Reusable NestJS authentication module with local login, JWT cookies, Google OAuth, and CSRF protection

Readme

@derek46518/nest-auth-kit

Reusable NestJS authentication module that bundles:

  • Local username/email + password login
  • JWT issuance with HttpOnly cookie support
  • Google OAuth 2.0 strategy (passport-google-oauth20)
  • CSRF double-submit middleware
  • Password reset flow with pluggable token persistence
  • Optional transactional email notifications

This package extracts the auth stack used in the WebBackend project and exposes extensible interfaces so you can bring your own user store, reset-token repository, and mailer implementation.

Status: experimental & work-in-progress. The API may change before 1.0.

Features

  • Dynamic AuthModule with configurable providers and cookie behaviour
  • Injectable AuthService exposing high-level login/registration helpers
  • Passport strategies (local, jwt, google) and guards ready to plug into controllers
  • CSRF middleware that validates origin + double-submit token
  • DTOs for register/forgot/reset flows using class-validator

Installation

npm install @derek46518/nest-auth-kit passport passport-local passport-jwt passport-google-oauth20

The package declares peer dependencies on core NestJS packages (e.g. @nestjs/common, @nestjs/jwt, @nestjs/passport). Make sure they already exist in your host application.

Usage overview

  1. Implement adapters that satisfy the exported interfaces.
  2. Provide those adapters (and optionally a mailer) in a module the kit can import.
  3. Register the auth kit using AuthModule.registerAsync(...) (or register(...)) and supply configuration (JWT, cookies, Google OAuth, frontend metadata).
  4. Expose auth endpoints using the supplied guards/controller or by building your own controllers that delegate to AuthService.

The following sections walk through each step.

1. Implement adapters

Implement the interfaces exported by the kit (see src/interfaces):

  • AuthUsersService – look up users, validate credentials, create accounts, and update passwords. Must return objects { id, username, email | null, role }.
  • PasswordResetTokenStore – persist password reset tokens (saveToken, findByHash, markUsed).
  • AuthMailer (optional) – send transactional email. Provide isEnabled(): boolean to indicate SMTP availability.

Example Drizzle/Nest adapter:

@Injectable()
export class UsersAuthServiceAdapter implements AuthUsersService {
  constructor(private readonly users: UsersService) {}

  private toAuthUser(user: UsersServiceSafeUser): AuthUser {
    return {
      id: user.id,
      username: user.username,
      email: user.email,
      role: user.role
    };
  }

  async validateCredentials(identifier: string, password: string) {
    const user = await this.users.validatePassword(identifier, password);
    return user ? this.toAuthUser(user) : null;
  }

  async registerUser(username: string, password: string, email: string) {
    return this.toAuthUser(await this.users.create(username, password, email));
  }

  async findByEmail(email: string) {
    const user = await this.users.findByEmail(email);
    return user ? this.toAuthUser(user as any) : null;
  }

  async createOAuthUser(email: string, usernameBase: string) {
    return this.toAuthUser(await this.users.createOAuthUser(email, usernameBase));
  }

  async updatePassword(userId: number, newPassword: string) {
    await this.users.updatePassword(userId, newPassword);
  }
}

Password reset token store example:

@Injectable()
export class PasswordResetTokenStoreAdapter implements PasswordResetTokenStore {
  constructor(@Inject(DRIZZLE) private readonly db: NodePgDatabase) {}

  async saveToken(userId: number, tokenHash: string, expiresAt: Date) {
    await this.db.insert(passwordResetTokens).values({ userId, tokenHash, expiresAt });
  }

  async findByHash(tokenHash: string) {
    const [row] = await this.db
      .select({
        id: passwordResetTokens.id,
        userId: passwordResetTokens.userId,
        tokenHash: passwordResetTokens.tokenHash,
        expiresAt: passwordResetTokens.expiresAt,
        usedAt: passwordResetTokens.usedAt
      })
      .from(passwordResetTokens)
      .where(eq(passwordResetTokens.tokenHash, tokenHash))
      .limit(1);

    if (!row) return null;
    return {
      id: row.id,
      userId: row.userId,
      tokenHash: row.tokenHash,
      expiresAt: row.expiresAt instanceof Date ? row.expiresAt : new Date(row.expiresAt),
      usedAt: row.usedAt ?? null
    };
  }

  async markUsed(id: number, usedAt: Date) {
    await this.db.update(passwordResetTokens).set({ usedAt }).where(eq(passwordResetTokens.id, id));
  }
}

If you want transactional email support, implement AuthMailer (or reuse an existing service) exposing:

isEnabled(): boolean;
send(to: string, subject: string, html: string, text?: string): Promise<void | boolean>;

2. Provide adapters & mailer in modules

Create modules that bind your adapters/mailer to the tokens exported by the kit:

@Module({
  imports: [UsersModule],
  providers: [
    UsersAuthServiceAdapter,
    PasswordResetTokenStoreAdapter,
    { provide: AUTH_USERS_SERVICE, useExisting: UsersAuthServiceAdapter },
    { provide: AUTH_RESET_TOKEN_STORE, useExisting: PasswordResetTokenStoreAdapter }
  ],
  exports: [UsersAuthServiceAdapter, PasswordResetTokenStoreAdapter, AUTH_USERS_SERVICE, AUTH_RESET_TOKEN_STORE]
})
export class AuthAdaptersModule {}

@Module({
  providers: [MailService, { provide: AUTH_MAILER, useExisting: MailService }],
  exports: [MailService, AUTH_MAILER]
})
export class AuthMailerModule {}

3. Register the auth kit

In your root auth module, configure and import the kit:

const authKitModule = AuthKitModule.registerAsync({
  imports: [AuthAdaptersModule, AuthMailerModule, ConfigModule],
  useFactory: (cfg: ConfigService): AuthModuleOptions => ({
    imports: [AuthAdaptersModule, AuthMailerModule, ConfigModule],
    userServiceToken: UsersAuthServiceAdapter,
    resetTokenStoreToken: PasswordResetTokenStoreAdapter,
    mailerToken: MailService,
    jwt: {
      secret: cfg.get<string>('JWT_SECRET')!,
      expiresIn: cfg.get<string>('JWT_EXPIRES_IN') ?? '15m'
    },
    cookies: {
      useCookies: cfg.get<string>('AUTH_USE_COOKIE') !== 'false',
      cookieDomain: cfg.get<string>('COOKIE_DOMAIN') || undefined,
      crossSite: cfg.get<string>('COOKIE_CROSS_SITE') === 'true'
    },
    google: cfg.get<string>('GOOGLE_CLIENT_ID') && cfg.get<string>('GOOGLE_CLIENT_SECRET')
      ? {
          clientID: cfg.get<string>('GOOGLE_CLIENT_ID')!,
          clientSecret: cfg.get<string>('GOOGLE_CLIENT_SECRET')!,
          callbackURL: cfg.get<string>('GOOGLE_CALLBACK_URL')!
        }
      : undefined,
    frontend: {
      origins: (cfg.get<string>('FRONTEND_ORIGINS') ?? '')
        .split(',')
        .map((s) => s.trim())
        .filter(Boolean),
      defaultRedirectUrl: cfg.get<string>('FRONTEND_ORIGIN') ?? 'http://localhost:3000',
      resetUrlBase: cfg.get<string>('FRONTEND_RESET_URL') ?? ''
    }
  }),
  inject: [ConfigService]
});

@Module({
  imports: [AuthAdaptersModule, AuthMailerModule, ConfigModule, authKitModule],
  exports: [authKitModule, AuthAdaptersModule, AuthMailerModule]
})
export class AuthModule {}

The kit exports AuthService, strategies, guards, middleware, and DTOs. Inject AuthService wherever you need to issue tokens manually.

4. Controllers & guards

Use the supplied guards in your own controllers or expose the built-in controller from the kit. Example custom controller:

@Controller('auth')
export class AuthController {
  constructor(private readonly auth: AuthService) {}

  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
    const user = req.user as { id: number; username: string; role?: string | null };
    const token = await this.auth.issueAccessToken(user);
    res.cookie('accessToken', token, { httpOnly: true, sameSite: 'lax' });
    return { user, accessToken: token };
  }
}

Required environment variables

| Variable | Description | | --- | --- | | DATABASE_URL | Connection string for your user/password-reset persistence | | JWT_SECRET | Secret used to sign access tokens (min 16 chars recommended) | | JWT_EXPIRES_IN | Token lifetime (15m, 1h, etc.) | | AUTH_USE_COOKIE | true to set JWT in an HttpOnly cookie | | COOKIE_CROSS_SITE | true when API and frontend are on different domains (sets SameSite=None) | | COOKIE_DOMAIN | Optional domain for cookies (e.g. .example.com) | | FRONTEND_ORIGIN / FRONTEND_ORIGINS | Allowed origins for CORS/CSRF (comma separated) | | FRONTEND_RESET_URL | Base URL for password reset link (e.g. https://app/reset?token=) | | GOOGLE_CLIENT_ID | Google OAuth client ID (optional if Google login disabled) | | GOOGLE_CLIENT_SECRET | Google OAuth client secret | | GOOGLE_CALLBACK_URL | OAuth callback URL pointing back to your API | | SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, EMAIL_FROM | SMTP configuration for password reset email (optional) |

Google OAuth set-up

  1. In Google Cloud Console, create an OAuth client ID (Web application).
  2. Add your API callback URL (e.g. https://api.example.com/auth/google/callback).
  3. Add your frontend origins (https://app.example.com).
  4. Populate GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_CALLBACK_URL in your environment.
  5. Expose the /auth/google and /auth/google/callback routes using the guards provided by the kit or the default controller.

Frontend integration

Default endpoints (assuming the packaged controller is used):

| Method | Route | Description | | --- | --- | --- | | POST | /auth/login | Local login; returns { user, accessToken, csrfToken } (cookie set when enabled) | | POST | /auth/register | Register a user and issue access token | | POST | /auth/logout | Clear cookies (if cookie mode on) | | GET | /auth/csrf | Obtain CSRF token for double-submit scheme | | GET | /auth/google | Redirect to Google OAuth | | GET | /auth/google/callback | Callback; redirects with state or sets cookies | | POST | /auth/password/forgot | Trigger password reset flow | | POST | /auth/password/reset | Complete password reset |

Frontend tips:

  • When using cookies, issue requests with credentials: 'include'.
  • In SPA flows, parse the redirected URL for accessToken/csrfToken when cookie mode is off.
  • Retrieve /auth/csrf before protected POST requests to include the double-submit token in headers.

Testing

  • Unit-test your adapters to ensure they meet the contract.
  • Add e2e tests that exercise local login, Google OAuth (or stub strategy), and password-reset flows.
  • Mock SMTP or the mailer when running tests to avoid external calls.

License

MIT © Derek