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/oauth

v0.1.0

Published

OAuth 2.0 / OIDC module for NestJS — hexagonal architecture

Readme

@devoven/oauth

OAuth 2.0 / OIDC module for multi-provider authentication in NestJS. Wire up Google, LINE, or any custom provider with a single import.

Installation

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

Peer Dependencies

Install the standard NestJS validation stack if your app does not already have it:

npm install class-validator class-transformer

@nestjs/common, @nestjs/core, rxjs, and reflect-metadata are expected to already be present in any NestJS application.

google-auth-library is bundled with this package and does not need to be installed separately.

Quick Start

import { OAuthModule, GoogleOAuthProvider } from '@devoven/oauth';

@Module({
  imports: [
    OAuthModule.register({
      providers: [
        {
          name: 'google',
          provider: GoogleOAuthProvider,
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
          callbackUrl: 'https://example.com/oauth/google/callback',
        },
      ],
    }),
  ],
})
export class AppModule {}

This registers the OAuthController at /oauth, giving you:

  • GET /oauth/google — redirects the user to Google's consent screen
  • GET /oauth/google/callback — handles the code exchange and returns the resolved profile

Module Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | providers | Array<OAuthProviderOption \| OAuthProviderPort> | — (required) | Provider configs or pre-constructed provider instances | | stateStore | Class or instance of OAuthStateStorePort | InMemoryStateStore | CSRF state store. Default TTL is 5 minutes | | controller | boolean | true | Mount OAuthController. Set to false to handle routes yourself | | callbackSuccessUrl | string | undefined | When set, the callback endpoint redirects to this URL with profile fields as query parameters instead of returning JSON |

OAuthProviderOption

| Field | Type | Description | |-------|------|-------------| | name | string | Key used in the URL path (e.g. 'google'/oauth/google) | | provider | Constructor | A class implementing OAuthProviderPort (e.g. GoogleOAuthProvider) | | clientId | string | OAuth app client ID | | clientSecret | string | OAuth app client secret | | callbackUrl | string | Absolute URL registered with the provider | | scopes | string[] | Optional scope list. Defaults to the provider's built-in defaults |

Alternatively, pass a pre-constructed OAuthProviderPort instance directly in the providers array.

Async Registration

import { OAuthModule, GoogleOAuthProvider, LineOAuthProvider } from '@devoven/oauth';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    OAuthModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        callbackSuccessUrl: config.get('OAUTH_SUCCESS_URL'),
        providers: [
          {
            name: 'google',
            provider: GoogleOAuthProvider,
            clientId: config.get('GOOGLE_CLIENT_ID'),
            clientSecret: config.get('GOOGLE_CLIENT_SECRET'),
            callbackUrl: config.get('GOOGLE_CALLBACK_URL'),
          },
          {
            name: 'line',
            provider: LineOAuthProvider,
            clientId: config.get('LINE_CLIENT_ID'),
            clientSecret: config.get('LINE_CLIENT_SECRET'),
            callbackUrl: config.get('LINE_CALLBACK_URL'),
          },
        ],
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

REST API

All endpoints are served under the /oauth prefix.

Redirect to provider

GET /oauth/:provider

Generates a CSRF state token, stores it, and issues a 302 redirect to the provider's authorization URL.

| Parameter | Where | Description | |-----------|-------|-------------| | provider | path | Provider name as registered in providers (e.g. google, line) |

Response: 302 Found — Location header points to the provider's authorization URL.

Handle callback

GET /oauth/:provider/callback?code=...&state=...

Verifies the state, exchanges the authorization code for tokens, fetches the user profile, and returns it.

| Parameter | Where | Description | |-----------|-------|-------------| | provider | path | Provider name | | code | query | Authorization code from the provider | | state | query | CSRF state value returned by the provider |

Response without callbackSuccessUrl: 200 OK

{
  "provider": "google",
  "providerId": "1234567890",
  "email": "[email protected]",
  "name": "Jane Doe",
  "avatarUrl": "https://example.com/avatar.jpg"
}

Fields email, name, and avatarUrl may be null depending on the scopes granted and the provider's response.

Response with callbackSuccessUrl: 302 Found — redirects to callbackSuccessUrl with the profile fields appended as query parameters (provider, providerId, email, name, avatarUrl).

Built-in Providers

GoogleOAuthProvider

Default scopes: openid profile email

Verifies the id_token using the Google auth library when present; falls back to the UserInfo endpoint otherwise.

LineOAuthProvider

Default scopes: openid profile

Verifies the id_token via LINE's /oauth2/v2.1/verify endpoint when present; falls back to the UserInfo endpoint otherwise.

Both classes accept a custom scopes array to override the defaults.

Architecture

Port / Token Mapping

| DI Token | Interface | Default Implementation | Purpose | |----------|-----------|------------------------|---------| | 'GetAuthorizationUrlPort' | GetAuthorizationUrlPort | GetAuthorizationUrlUseCase | Generate a provider authorization URL with a fresh state token | | 'HandleCallbackPort' | HandleCallbackPort | HandleCallbackUseCase | Verify state, exchange code, return OAuthProfile | | 'OAuthProviderRegistry' | OAuthProviderRegistry (Map<string, OAuthProviderPort>) | Built from providers option | Registry of provider name → provider instance | | 'OAuthStateStorePort' | OAuthStateStorePort | InMemoryStateStore | Generate and verify CSRF state tokens |

GetAuthorizationUrlPort and HandleCallbackPort are exported from the module.

Domain Model

OAuthProfile

| Property | Type | Description | |----------|------|-------------| | provider | string | Provider name (e.g. 'google') | | providerId | string | Provider-scoped user ID | | email | string \| null | User email, if available | | name | string \| null | Display name, if available | | avatarUrl | string \| null | Profile picture URL, if available | | id | string | Composite key: provider:providerId |

OAuthToken

| Property | Type | Description | |----------|------|-------------| | accessToken | string | Bearer token for API calls | | refreshToken | string \| null | Refresh token, if issued | | expiresAt | Date \| null | Computed from expiresIn when not provided directly | | idToken | string \| null | OIDC ID token, if present | | isExpired | boolean | true when expiresAt is in the past |

Custom Adapters

Custom state store

Provide a class or instance that implements OAuthStateStorePort for persistent CSRF state (e.g. Redis):

import { Injectable } from '@nestjs/common';
import { OAuthStateStorePort } from '@devoven/oauth';

@Injectable()
export class RedisStateStore implements OAuthStateStorePort {
  async generate(): Promise<string> {
    const state = crypto.randomUUID();
    await redis.set(`oauth:state:${state}`, '1', 'EX', 300);
    return state;
  }

  async verify(state: string): Promise<boolean> {
    const key = `oauth:state:${state}`;
    const exists = await redis.del(key);
    return exists === 1;
  }
}
OAuthModule.register({
  providers: [...],
  stateStore: RedisStateStore,
})

Custom provider

Implement OAuthProviderPort to add any OAuth 2.0 / OIDC provider:

import { OAuthProviderPort, OAuthProviderConfig, OAuthProfile, OAuthToken } from '@devoven/oauth';

export class GitHubOAuthProvider implements OAuthProviderPort {
  readonly name = 'github';

  constructor(private readonly config: OAuthProviderConfig) {}

  async getAuthorizationUrl(state: string): Promise<string> { /* ... */ }
  async exchangeCode(code: string): Promise<OAuthToken> { /* ... */ }
  async getProfile(token: OAuthToken): Promise<OAuthProfile> { /* ... */ }
}

Pass it either as a config object (the module will construct it) or as an already-instantiated object:

// Config object — module constructs the instance
OAuthModule.register({
  providers: [
    {
      name: 'github',
      provider: GitHubOAuthProvider,
      clientId: '...',
      clientSecret: '...',
      callbackUrl: 'https://example.com/oauth/github/callback',
    },
  ],
})

// Pre-constructed instance
OAuthModule.register({
  providers: [new GitHubOAuthProvider({ clientId: '...', clientSecret: '...', callbackUrl: '...', scopes: [] })],
})