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

@nathapp/nestjs-oauth

v1.1.0

Published

Reusable OAuth2 Authorization Server + OpenID Connect Provider for NestJS

Readme

@nathapp/nestjs-oauth

A reusable, ORM-agnostic OAuth2 Authorization Server + OpenID Connect Provider for the @nathapp/nestjs-platform monorepo.

Ported and hardened from the proven logic in repos/nestjs-auth-api, restructured to platform conventions: interface-first core, separate Prisma adapter, DI-token seams, tenant-scoped data, and RS256/ES256 signing via database-backed signing keys (PrismaSigningKeyProvider).

Core Features

  • OAuth2 Authorization Code flow with PKCE (S256)
  • OpenID Connect Provider with id_token issuance and JWKS discovery
  • Refresh token rotation with reuse detection
  • Client Credentials flow for service-to-service auth
  • Token introspection & revocation (RFC 7662 / RFC 7009)
  • Token cleanup service for consumer-scheduled expiry purging
  • Rate limiting via @nathapp/nestjs-throttler (opt-in)
  • Multi-tenant isolation via TenantContext (column-based)
  • Admin CRUD endpoints for OAuth clients (guard-agnostic)
  • Spec-compliant redirect flows (real HTTP 302 responses)

Module wiring (Phase 2)

import { OAuthModule } from '@nathapp/nestjs-oauth';
import { OAuthPrismaModule } from '@nathapp/nestjs-oauth-prisma';
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [
    EventEmitterModule.forRoot(),       // required — OAuth services emit lifecycle events
    TenantModule.register({ /* provides TENANT_CONTEXT */ }),
    OAuthPrismaModule,                 // provides OAUTH_*_REPOSITORY tokens
    OAuthModule.register({}),          // client + scope-validation services + admin controller
  ],
})
export class AppModule {}

The admin controller (oauth/admin/clients) is guard-agnostic — protect it with your own auth guard, or disable it with OAuthModule.register({ registerAdminController: false }). A created confidential client returns its plaintext secret once, in the create response.

Introspection & Revocation

The library ships POST /oauth/introspect (RFC 7662) and POST /oauth/revoke (RFC 7009) endpoints. Both require client authentication (HTTP Basic or body credentials) and are enabled by default. Disable via:

OAuthModule.register({
  registerIntrospectionController: false,
  registerRevocationController: false,
});

Override the default services via DI tokens:

{ provide: OAUTH_INTROSPECTION_SERVICE, useClass: MyIntrospectionService }
{ provide: OAUTH_REVOCATION_SERVICE, useClass: MyRevocationService }

Caveat: Revoking an access token does not automatically revoke its associated refresh token (access tokens carry no back-link to refresh tokens per the data model). If you need linked revocation, override DefaultRevocationService.

Token Cleanup

TokenCleanupService (injectable via OAUTH_TOKEN_CLEANUP_SERVICE) exposes cleanupExpired(now?: Date) which purges expired authorization codes, access tokens, and refresh tokens. Scheduling is the consumer's responsibility — no @nestjs/schedule dependency is pulled in.

import { Cron } from '@nestjs/schedule';

@Injectable()
class MyScheduler {
  constructor(
    @Inject(OAUTH_TOKEN_CLEANUP_SERVICE) private readonly cleanup: TokenCleanupService,
  ) {}

  @Cron('0 3 * * *')
  async purgeTokens() {
    const { codes, accessTokens, refreshTokens } = await this.cleanup.cleanupExpired();
    this.logger.log(`Purged ${codes} codes, ${accessTokens} access, ${refreshTokens} refresh`);
  }
}

Rate Limiting

Sensitive endpoints (/oauth/token, /oauth/authorize, /oauth/introspect, /oauth/revoke) carry @Throttle decorators with conservative defaults. These are metadata only — they activate when the consumer registers ThrottlerModule and applies DefaultThrottlerGuard globally:

import { ThrottlerModule } from '@nathapp/nestjs-throttler';
import { APP_GUARD } from '@nestjs/core';

@Module({
  imports: [ThrottlerModule.forRoot({ ttl: 60, limit: 100 })],
  providers: [{ provide: APP_GUARD, useClass: DefaultThrottlerGuard }],
})

Alternatively, apply your own rate limiting at the gateway/reverse-proxy level for these routes.

auth_time Claim

To populate the auth_time claim in the id_token, the consumer's CURRENT_USER_RESOLVER must return authTime alongside userId. If omitted, the claim is not included in the id_token.

{
  provide: CURRENT_USER_RESOLVER,
  useFactory: (authService: AuthService) => ({
    resolve: async (req: any) => {
      const session = await authService.getSession(req);
      if (!session) return null;
      return { userId: session.userId, authTime: session.authenticatedAt };
    },
  }),
  inject: [AuthService],
}

Security

Consent CSRF

The library enforces that granted scopes never exceed the client's registered set (server-side, via DefaultScopeValidationService), but it does not issue its own CSRF token for the consent POST. The consuming application MUST:

  • CSRF-protect POST /oauth/authorize/consent (e.g. session-bound CSRF token validated before the handler runs)
  • Ensure the consent UI submits only the scopes that were displayed to the user