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

@kenmon/google-oauth-authenticator

v1.0.0-pre.3

Published

Google OAuth authenticator for Kenmon

Readme

@kenmon/google-oauth-authenticator

Google OAuth authenticator for Kenmon authentication system.

Installation

npm install @kenmon/google-oauth-authenticator kenmon

Setup

1. Get Google OAuth Credentials

  1. Go to Google Cloud Console
  2. Create a new project or select an existing one
  3. Enable the Google+ API
  4. Go to CredentialsCreate CredentialsOAuth 2.0 Client ID
  5. Set up OAuth consent screen
  6. Add authorized redirect URIs (e.g., http://localhost:3000/auth/callback/google)
  7. Copy your Client ID and Client Secret

2. Configure Environment Variables

GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback/google
SESSION_SECRET=your-session-secret-for-jwt-signing

Usage

Create Authenticator Instance

import { KenmonGoogleOAuthAuthenticator } from '@kenmon/google-oauth-authenticator'

export const googleAuth = new KenmonGoogleOAuthAuthenticator({
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  redirectUri: process.env.GOOGLE_REDIRECT_URI!,
  secret: process.env.SESSION_SECRET!, // For signing state tokens
  scopes: ['openid', 'email', 'profile'], // Optional, these are defaults
})

Step 1: Redirect to Google

Choose the intent based on the user's action:

// For sign-in
const authUrl = googleAuth.getAuthUrl('sign-in')
redirect(authUrl)

// For sign-up
const authUrl = googleAuth.getAuthUrl('sign-up')
redirect(authUrl)

Step 2: Handle OAuth Callback

The callback route handles both sign-in and sign-up based on the intent:

// In your OAuth callback route (e.g., /auth/callback/google)
export async function GET(request: Request) {
  const url = new URL(request.url)
  const code = url.searchParams.get('code')
  const state = url.searchParams.get('state')

  if (!code || !state) {
    return redirect('/signin?error=Missing parameters')
  }

  // Verify callback and get intent and identifier
  const result = await googleAuth.verifyCallback(code, state)

  if (!result.success) {
    console.error(result.error)
    return redirect(`/signin?error=${encodeURIComponent(result.error.message)}`)
  }

  const { intent, identifier } = result.data

  // Get request metadata
  const ipAddress = request.headers.get('x-forwarded-for') || undefined
  const userAgent = request.headers.get('user-agent') || undefined

  // Sign in or sign up based on intent
  if (intent === 'sign-in') {
    const signInResult = await auth.signIn(identifier, { ipAddress, userAgent })
    if (!signInResult.success) {
      return redirect(`/signin?error=${encodeURIComponent(signInResult.error.message)}`)
    }
  } else {
    const signUpResult = await auth.signUp(identifier, {}, { ipAddress, userAgent })
    if (!signUpResult.success) {
      return redirect(`/signup?error=${encodeURIComponent(signUpResult.error.message)}`)
    }
  }

  return redirect('/')
}

TypeScript Types

The authenticator exports typed interfaces for better IDE support:

import type { KenmonGoogleOAuthIdentifier } from '@kenmon/google-oauth-authenticator'

// The identifier returned from verifyCallback has this shape:
interface KenmonGoogleOAuthIdentifier {
  type: 'google-oauth'
  value: string // Google user ID
  data: {
    googleId: string         // Unique Google user ID
    email: string            // User's email
    emailVerified: boolean   // Is email verified by Google?
    name: string             // Full name
    givenName: string        // First name
    familyName: string       // Last name
    picture: string          // Profile picture URL
    locale: string           // User's locale (e.g., "en")
  }
}

Security

  • State Token: Uses JWT-signed state tokens for CSRF protection
  • Expiration: State tokens expire in 10 minutes
  • Nonce: Each state token includes a random nonce
  • Stateless: No database required for OAuth flow

Error Handling

The authenticator can return these error reasons:

  • invalid-state - JWT state verification failed
  • expired-state - State token expired
  • invalid-code - OAuth authorization code is invalid or expired
  • token-exchange-failed - Failed to exchange code for tokens
  • profile-fetch-failed - Failed to fetch user profile

Example: Next.js App Router

// app/auth/google/route.ts
import { googleAuth } from '@/lib/auth/authenticators/google'

export async function GET() {
  const authUrl = googleAuth.getAuthUrl('sign-in')
  return Response.redirect(authUrl)
}
// app/auth/callback/google/route.ts
import { googleAuth } from '@/lib/auth/authenticators/google'
import { auth } from '@/lib/auth/auth'

export async function GET(request: Request) {
  const url = new URL(request.url)
  const code = url.searchParams.get('code')
  const state = url.searchParams.get('state')

  if (!code || !state) {
    return Response.redirect('/signin?error=Missing parameters')
  }

  const result = await googleAuth.verifyCallback(code, state)

  if (!result.success) {
    return Response.redirect(`/signin?error=${encodeURIComponent(result.error.message)}`)
  }

  const { intent, identifier } = result.data

  const ipAddress = request.headers.get('x-forwarded-for') || undefined
  const userAgent = request.headers.get('user-agent') || undefined

  if (intent === 'sign-in') {
    await auth.signIn(identifier, { ipAddress, userAgent })
  } else {
    await auth.signUp(identifier, {}, { ipAddress, userAgent })
  }

  return Response.redirect('/')
}

License

MIT