Next.js integration for Keyloom authentication library. Provides seamless authentication for both App Router and Pages Router with middleware support.
npm install @keyloom/nextjsNext.js integration for Keyloom authentication library. Provides seamless authentication for both App Router and Pages Router with middleware support.
- 🚀 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
``bash`
npm install @keyloom/nextjs @keyloom/coreor
pnpm add @keyloom/nextjs @keyloom/coreor
yarn add @keyloom/nextjs @keyloom/core
`typescript
// 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',
}
`
App Router:
`typescript
// 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:
`typescript
// pages/api/auth/[...keyloom].ts
import { createPagesApiHandler } from '@keyloom/nextjs'
import keyloomConfig from '../../../keyloom.config'
export default createPagesApiHandler(keyloomConfig)
`
`typescript
// 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:
`typescript`
// 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' },
}
Server Components:
`typescript
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
Client Components:
`typescript
'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 (
)
}
`API Reference
$3
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$3
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$3
-
getSession(config) - Get current session and user
- getUser(config) - Get current user (null if not authenticated)
- guard(config) - Throws if not authenticated, returns userApp 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 patternThe 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.$3
`ts
// 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 (
)
}
`$3
`ts
// 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|.\\..).*)'] }
`$3
`ts
// 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
Admin area
}
return res as unknown as JSX.Element
}
`$3
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$3
`ts
// Example server action wrapper
import { withRole } from '@keyloom/nextjs/rbac'export async function actionForMembers(fn: () => Promise) {
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:
`typescript
import type {
NextKeyloomConfig,
SessionData,
AuthMiddlewareOptions
} from '@keyloom/nextjs'
``MIT