Next.js 16 Proxy (middleware) and handler API reference.
The Next.js integration provides zero-config authentication for Next.js 16+ applications using the new Proxy system (formerly Middleware). Import from @warpy-auth-sdk/core/next.
Zero-config authentication proxy for Next.js 16 (Clerk-like ergonomics)
authMiddleware(configOrOptions?: AuthConfig | AuthMiddlewareOptions, maybeOptions?: AuthMiddlewareOptions): (request: NextRequest) => Promise<Response>configOrOptionsAuthConfig | AuthMiddlewareOptionsAuthConfig or middleware options (auto-detects from env if omitted)
maybeOptionsAuthMiddlewareOptionsMiddleware options (when first param is AuthConfig)
(request: NextRequest) => Promise<Response>// proxy.ts (Next.js 16 Proxy file at app root)
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { authMiddleware } from '@warpy-auth-sdk/core/next';
import { google } from '@warpy-auth-sdk/core';
// With explicit config
const handler = authMiddleware(
{
secret: process.env.AUTH_SECRET!,
provider: google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: process.env.GOOGLE_REDIRECT_URI!,
}),
callbacks: {
async user(u) {
// Resolve/upsert user from your DB
return { id: '1', email: u.email, name: u.name, picture: u.picture };
},
jwt: (t) => t,
session: (s) => s,
},
},
{
basePath: '/api/auth',
successRedirect: '/dashboard',
errorRedirect: '/login',
}
);
export function proxy(request: NextRequest) {
const pathname = request.nextUrl.pathname;
if (pathname.startsWith('/api/auth')) {
return handler(request);
}
return NextResponse.next();
}
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
};Create Next.js route handler for manual route setup
createNextAuthHandler(config: AuthConfig, options?: NextAuthHandlerOptions): (request: Request) => Promise<Response>configAuthConfigrequiredAuthentication configuration
optionsNextAuthHandlerOptionsHandler options
(request: Request) => Promise<Response>// app/api/auth/[...nextauth]/route.ts
import { createNextAuthHandler } from '@warpy-auth-sdk/core/next';
import { google } from '@warpy-auth-sdk/core';
const handler = createNextAuthHandler(
{
secret: process.env.AUTH_SECRET!,
provider: google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: process.env.GOOGLE_REDIRECT_URI!,
}),
},
{
basePath: '/api/auth',
successRedirect: '/dashboard',
errorRedirect: '/login',
}
);
export { handler as GET, handler as POST };interface AuthMiddlewareOptions extends NextAuthHandlerOptions {
// Base path for auth routes (default: "/auth")
basePath?: string;
// Redirect after successful auth (default: "/")
successRedirect?: string;
// Redirect on auth error (default: "/login")
errorRedirect?: string;
// Public routes (always accessible)
publicRoutes?: string[];
// Protected routes (require authentication)
protectedRoutes?: string[];
}interface NextAuthHandlerOptions {
// Base path for auth routes (default: "/auth")
basePath?: string;
// Redirect after successful auth (default: "/dashboard")
successRedirect?: string;
// Redirect on auth error (default: "/login")
errorRedirect?: string;
}The middleware can auto-detect configuration from environment variables:
# .env.local
AUTH_SECRET=your-secret-key-min-32-chars
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google// proxy.ts (minimal setup)
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { authMiddleware } from '@warpy-auth-sdk/core/next';
// Zero-config: auto-detects from env
const handler = authMiddleware({
basePath: '/api/auth',
successRedirect: '/dashboard',
errorRedirect: '/login',
});
export function proxy(request: NextRequest) {
const pathname = request.nextUrl.pathname;
if (pathname.startsWith('/api/auth')) {
return handler(request);
}
return NextResponse.next();
}
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
};The middleware/handler automatically creates the following routes under basePath:
// Session management
GET {basePath}/session // Get current session JSON
POST {basePath}/signout // Sign out user
// OAuth flows
GET {basePath}/signin/:provider // Start OAuth (redirects to provider)
GET {basePath}/callback/:provider // OAuth callback (sets session cookie)
// Email magic links
POST {basePath}/signin/email // Send magic link
Body: { email: string, captchaToken?: string }
GET {basePath}/callback/email // Verify magic link token
Query: ?token=<jwt>&email=<email>
// Two-factor authentication
GET {basePath}/signin/twofa // Send/verify 2FA code
Query: ?email=<email> (send) or ?code=<code>&identifier=<id> (verify)The proxy.ts file is Next.js 16's replacement for middleware.ts:
proxy function that returns a Responsematcher config to specify which routes to handleexport const runtime (always Node)basePath must match your OAuth callback URLssecret must be at least 32 characters for securityTo support multiple authentication providers, you'll need to handle provider selection in your UI and use separate route handlers or conditional logic:
// For multiple providers, use separate instances or conditional logic
// This is a limitation - consider using multiple route handlers instead
// app/api/auth/google/[...nextauth]/route.ts
const googleHandler = createNextAuthHandler({
secret: process.env.AUTH_SECRET!,
provider: google({ /* ... */ }),
});
export { googleHandler as GET, googleHandler as POST };
// app/api/auth/github/[...nextauth]/route.ts
const githubHandler = createNextAuthHandler({
secret: process.env.AUTH_SECRET!,
provider: github({ /* ... */ }),
});
export { githubHandler as GET, githubHandler as POST };const handler = authMiddleware({
secret: process.env.AUTH_SECRET!,
provider: google({ /* ... */ }),
callbacks: {
// Resolve user (called after OAuth or email verification)
async user(profile, context) {
console.log('Provider:', context?.provider);
// Upsert user in your database
const user = await db.user.upsert({
where: { email: profile.email },
create: {
email: profile.email,
name: profile.name,
picture: profile.picture,
},
update: {
name: profile.name,
picture: profile.picture,
lastLogin: new Date(),
},
});
return user;
},
// Modify JWT claims before signing
jwt(token) {
token.role = 'admin';
token.tenantId = 'tenant-123';
return token;
},
// Modify session before returning to client
session(session) {
session.customField = 'value';
return session;
},
},
});import { createCaptchaProvider } from '@warpy-auth-sdk/core/captcha';
const handler = authMiddleware({
secret: process.env.AUTH_SECRET!,
provider: email({ /* ... */ }),
captcha: {
provider: createCaptchaProvider({
type: 'recaptcha-v3',
secretKey: process.env.RECAPTCHA_SECRET_KEY!,
minimumScore: 0.5, // reCAPTCHA v3 score threshold
}),
enforce: {
email: true, // Require CAPTCHA for email sign-in
twofa: true, // Require CAPTCHA for 2FA
oauth: false, // Don't require for OAuth (handled by provider)
},
},
});