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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@keyloom/nextjs

v3.1.4

Published

Next.js integration for Keyloom authentication library. Provides seamless authentication for both App Router and Pages Router with middleware support.

Readme

@keyloom/nextjs

Next.js integration for Keyloom authentication library. Provides seamless authentication for both App Router and Pages Router with middleware support.

Features

  • 🚀 App Router & Pages Router - Full support for both routing systems
  • 🛡️ Edge Runtime Middleware - Fast route protection at the edge
  • 🔐 Server Components - RSC-safe authentication helpers
  • 🎯 TypeScript First - Complete type safety
  • 🔒 CSRF Protection - Built-in security for all POST requests
  • Session Management - Database-backed sessions with rolling expiration
  • 🔧 Flexible Configuration - Customizable auth flows and routes

Installation

npm install @keyloom/nextjs @keyloom/core
# or
pnpm add @keyloom/nextjs @keyloom/core
# or
yarn add @keyloom/nextjs @keyloom/core

Quick Start (10 minutes)

1. Create Keyloom Config

// keyloom.config.ts
import { memoryAdapter } from '@keyloom/core'

export default {
  adapter: memoryAdapter(), // Use PrismaAdapter(prisma) in production
  session: {
    strategy: 'database' as const,
    ttlMinutes: 60,
    rolling: true
  },
  secrets: {
    authSecret: process.env.AUTH_SECRET ?? 'dev-secret-change-in-production'
  },
  baseUrl: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
}

2. Add API Routes

App Router:

// app/api/auth/[[...keyloom]]/route.ts
import { createNextHandler } from '@keyloom/nextjs'
import keyloomConfig from '../../../../keyloom.config'

const { GET, POST } = createNextHandler(keyloomConfig)
export { GET, POST }

Pages Router:

// pages/api/auth/[...keyloom].ts
import { createPagesApiHandler } from '@keyloom/nextjs'
import keyloomConfig from '../../../keyloom.config'

export default createPagesApiHandler(keyloomConfig)

3. Add Middleware (Optional but Recommended)

// middleware.ts
import { createAuthMiddleware } from '@keyloom/nextjs/middleware'
import keyloomConfig from './keyloom.middleware' // Edge-safe config

export default createAuthMiddleware(keyloomConfig, {
  publicRoutes: ['/', '/sign-in', '/sign-up'],
  // Optional: verify sessions at edge (slower but more secure)
  verifyAtEdge: false
})

export const config = { 
  matcher: ['/((?!_next|favicon.ico|.*\\..*).*)'] 
}

Edge-safe config for middleware:

// keyloom.middleware.ts (no Node.js imports)
export default {
  baseUrl: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
  session: { strategy: 'database', ttlMinutes: 60, rolling: true },
  secrets: { authSecret: process.env.AUTH_SECRET ?? 'dev-secret' },
}

4. Use in Components

Server Components:

import { getSession, getUser, guard } from '@keyloom/nextjs'
import keyloomConfig from '../keyloom.config'

export default async function Dashboard() {
  // Option 1: Manual check
  const { session, user } = await getSession(keyloomConfig)
  if (!session) redirect('/sign-in')

  // Option 2: Guard helper
  const user = await guard(keyloomConfig) // Throws if not authenticated

  return <div>Welcome {user.email}!</div>
}

Client Components:

'use client'

export function LoginForm() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault()
    
    // Get CSRF token (also sets HttpOnly cookie)
    const csrfRes = await fetch('/api/auth/csrf')
    const { csrfToken } = await csrfRes.json()

    // Login (send token via header for double-submit validation)
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-keyloom-csrf': csrfToken,
      },
      body: JSON.stringify({ email, password })
    })

    if (res.ok) {
      window.location.href = '/dashboard'
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="email" 
        value={email} 
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email" 
        required 
      />
      <input 
        type="password" 
        value={password} 
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password" 
        required 
      />
      <button type="submit">Sign In</button>
    </form>
  )
}

API Reference

createNextHandler(config)

Creates GET and POST handlers for authentication routes:

  • GET /api/auth/session - Get current session
  • GET /api/auth/csrf - Get CSRF token
  • POST /api/auth/login - Login user
  • POST /api/auth/register - Register user
  • POST /api/auth/logout - Logout user
  • GET /api/auth/oauth/:provider/start - Begin OAuth flow (supports ?callbackUrl=)
  • GET /api/auth/oauth/:provider/callback - Complete OAuth code exchange
  • POST /api/auth/oauth/:provider/callback - Complete OAuth when response_mode=form_post

createAuthMiddleware(config, options)

Creates middleware for route protection with options:

  • publicRoutes - Array of public route patterns
  • verifyAtEdge - Verify sessions at edge (default: false)
  • afterAuth - Custom logic after auth check

Server Helpers

  • getSession(config) - Get current session and user
  • getUser(config) - Get current user (null if not authenticated)
  • guard(config) - Throws if not authenticated, returns user

App Router vs Pages Router

Both routing systems are fully supported with the same API:

App Router uses the [[...keyloom]] catch-all route pattern Pages Router uses the [...keyloom] catch-all route pattern

The middleware and server helpers work identically in both systems.

Security Features

  • CSRF Protection - All POST requests require CSRF tokens
  • Secure Cookies - HttpOnly, Secure, SameSite cookies
  • Session Validation - Database-backed session verification
  • Edge Runtime - Fast middleware execution at the edge

RBAC & Organizations

Keyloom supports organization-based RBAC out of the box. Enable/disable RBAC via rbac.enabled in your Keyloom config; when disabled, org/role checks are skipped by middleware/guards.

Organization Switching UI

// app/(auth)/orgs/page.tsx (Server Component)
import { getUser } from '@keyloom/nextjs'
import keyloomConfig from '../../keyloom.config'
import { cookies } from 'next/headers'
import { setActiveOrgCookie } from '@keyloom/nextjs/rbac'

export default async function OrgsPage() {
  const user = await getUser(keyloomConfig)
  if (!user) return null
  const orgs = await keyloomConfig.adapter.getOrganizationsByUser!(user.id)

  async function switchOrg(orgId: string) {
    'use server'
    cookies().set(setActiveOrgCookie(orgId))
  }

  return (
    <form action={switchOrg}>
      <select name="orgId">
        {orgs.map((o: any) => <option key={o.id} value={o.id}>{o.name}</option>)}
      </select>
      <button type="submit">Use organization</button>
    </form>
  )
}

Middleware route protection with org required

// middleware.ts
import { createAuthMiddleware } from '@keyloom/nextjs/middleware'
import cfg from './keyloom.middleware'

export default createAuthMiddleware(cfg, {
  publicRoutes: ['/', '/sign-in', '/sign-up'],
  verifyAtEdge: false,
  // Example: require org for app routes
  afterAuth: ({ request, next, getCookie, config }) => {
    const pathname = new URL(request.url).pathname
    if (pathname.startsWith('/app') && config?.rbac?.enabled !== false) {
      const hasOrg = Boolean(getCookie('__keyloom_org'))
      if (!hasOrg) return Response.redirect(new URL('/orgs', request.url))
    }
    return next()
  }
})

export const config = { matcher: ['/((?!_next|favicon.ico|.*\\..*).*)'] }

Server guard with roles/permissions

// app/app/admin/page.tsx
import { withRole, getActiveOrgId } from '@keyloom/nextjs/rbac'
import { guard } from '@keyloom/nextjs'
import keyloomConfig from '../../../keyloom.config'

export default async function AdminPage() {
  const user = await guard(keyloomConfig)
  const orgId = getActiveOrgId()
  const res = await withRole(async () => {
    return { ok: true } as any
  }, {
    requiredRoles: ['owner', 'admin'],
    getUser: async () => user,
    adapter: keyloomConfig.adapter,
    orgId,
    rbacEnabled: keyloomConfig.rbac?.enabled !== false,
    onDenied: () => new Response('forbidden', { status: 403 }),
  })
  if ((res as any).ok) {
    return <div>Admin area</div>
  }
  return res as unknown as JSX.Element
}

Typical workflow

  1. User logs in via Keyloom routes
  2. If no org cookie, redirect to /orgs to choose an organization
  3. On selection, set __keyloom_org using setActiveOrgCookie()
  4. Protect admin/member routes with middleware and withRole

Admin-only and org-member routes

// Example server action wrapper
import { withRole } from '@keyloom/nextjs/rbac'

export async function actionForMembers(fn: () => Promise<Response>) {
  return withRole(fn, {
    requiredRoles: ['owner','admin','member'],
    getUser: async () => /* fetch user */ null,
    adapter: /* your adapter */ null as any,
    rbacEnabled: true,
  })
}

TypeScript Support

Full TypeScript support with comprehensive type definitions:

import type { 
  NextKeyloomConfig,
  SessionData,
  AuthMiddlewareOptions 
} from '@keyloom/nextjs'

License

MIT