xiaohongshu
xiaohongshu
better-auth-skill
Expert Better Auth skill with production best practices, session management, security hardening, and deployment optimization. Use with Better Auth MCP server.
Full skill instructions
Expert skill for implementing Better Auth in Next.js applications with production best practices, security hardening, and deployment optimization.
Use this configuration for production deployments (especially on Vercel):
// lib/auth.ts - Production Ready
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
import { Pool } from "pg"; // or your database client
// Create a singleton pool to prevent multiple connections during dev
const globalForPool = globalThis as unknown as { pool: Pool | undefined };
export const pool = globalForPool.pool ?? new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === "production" ? {
rejectUnauthorized: false
} : undefined // No SSL in development unless required
});
if (process.env.NODE_ENV !== "production") globalForPool.pool = pool;
// Ensure the secret is properly set - fail loudly if missing
const authSecret = process.env.BETTER_AUTH_SECRET;
if (!authSecret) {
console.error("BETTER_AUTH_SECRET is not set! This will cause authentication to fail.");
if (process.env.NODE_ENV === "production") {
throw new Error("BETTER_AUTH_SECRET environment variable is required in production");
}
}
export const auth = betterAuth({
secret: authSecret || "dev-secret-for-development-only-change-in-production",
baseURL: process.env.BETTER_AUTH_URL ||
(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` :
process.env.VERCEL_BRANCH_URL ? `https://${process.env.VERCEL_BRANCH_URL}` :
"http://localhost:3000"),
database: pool, // or your database configuration
trustedOrigins: [
"https://your-production-domain.vercel.app", // Production Vercel URL
`https://your-project-git-*vercel.app`, // Vercel preview URLs
"http://localhost:3000", // Local development
"http://127.0.0.1:3000", // Alternative local address
],
advanced: {
useSecureCookies: process.env.NODE_ENV === "production", // Force secure cookies in production
cookiePrefix: "yourapp", // Reduce fingerprinting
session: {
expiresIn: 7 * 24 * 60 * 60, // 7 days in seconds
updateAge: 24 * 60 * 60, // Update session every 24 hours
freshAge: 15 * 60, // 15 minutes for sensitive operations
cookieCache: {
enabled: true,
maxAge: 300, // 5 minutes cache
strategy: "compact", // smallest and fastest
refreshCache: true // Refresh when updateAge threshold reached
}
},
defaultCookieAttributes: {
// Set default attributes for all cookies
httpOnly: true,
secure: process.env.NODE_ENV === "production", // Secure in production
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", // "none" for cross-site in production with secure
path: "/",
},
ipAddress: {
ipAddressHeaders: ["cf-connecting-ip"] // Cloudflare header, adjust for your proxy
}
},
account: {
encryptOAuthTokens: true, // Encrypt OAuth tokens at rest
storeStateStrategy: "database" // Default safe strategy
},
rateLimit: {
enabled: process.env.NODE_ENV === "production" // Enable in production
},
logger: {
level: process.env.NODE_ENV === "production" ? "error" : "debug"
},
plugins: [
nextCookies() // Install this plugin last to ensure Set-Cookie is applied correctly
],
});
# .env.local
BETTER_AUTH_SECRET="your-32-character-secret-key-minimum"
BETTER_AUTH_URL="http://localhost:3000"
NEXT_PUBLIC_BETTER_AUTH_URL="http://localhost:3000"
DATABASE_URL="postgresql://username:password@host:port/database"
# For Vercel deployment
VERCEL_ENV="production" # Will be set automatically by Vercel
VERCEL_URL="your-project-name.vercel.app" # Set automatically by Vercel
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { jwtClient } from "better-auth/client/plugins";
// Determine the correct base URL based on environment
const getBaseURL = () => {
if (typeof window !== "undefined") {
// Browser environment
return process.env.NEXT_PUBLIC_BETTER_AUTH_URL ||
(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000");
} else {
// Server environment
return process.env.BETTER_AUTH_URL ||
(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000");
}
};
export const authClient = createAuthClient({
baseURL: getBaseURL(),
plugins: [
jwtClient()
]
});
export const { signIn, signUp, signOut, useSession } = authClient;
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth.handler);
// app/dashboard/page.tsx
import { redirect } from "next/navigation";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
// Force this page to be dynamic to prevent static generation
export const dynamic = 'force-dynamic';
export default async function DashboardPage() {
const session = await auth.api.getSession({
headers: await headers(), // Always pass headers from server components
});
if (!session) {
redirect("/login");
}
return (
<div>
<h1>Dashboard - Welcome {session.user.name}</h1>
{/* Your protected content */}
</div>
);
}
// lib/actions.ts
"use server";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export async function getUserProfile() {
const session = await auth.api.getSession({
headers: await headers(), // Always pass headers from server actions
});
if (!session) {
throw new Error("Unauthorized");
}
return session.user;
}
// proxy.ts (Next.js 16+)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { auth } from "@/lib/auth";
// Public routes that don't require authentication
const publicRoutes = ["/", "/login", "/register"];
export async function middleware(req: NextRequest) {
// For full validation, use Node.js runtime
// For optimistic redirects, use presence-only check
const session = await auth.api.getSession({
headers: {
cookie: req.headers.get("cookie") || "",
},
});
const isLoggedIn = !!session?.user;
const isOnPublicRoute = publicRoutes.includes(req.nextUrl.pathname);
// If on a protected route without being logged in, redirect to login
if (!isOnPublicRoute && !isLoggedIn) {
return NextResponse.redirect(new URL("/login", req.url));
}
// If logged in and trying to access login/register, redirect to dashboard
if ((req.nextUrl.pathname === "/login" || req.nextUrl.pathname === "/register") && isLoggedIn) {
return NextResponse.redirect(new URL("/dashboard", req.url));
}
return NextResponse.next();
}
// Use Node.js runtime for full session validation
export const config = {
runtime: "nodejs",
matcher: [
/*
* Match all request paths except for:
* - api routes (handled by Better Auth API)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
*/
"/((?!api|_next/static|_next/image|favicon.ico|public).*)",
],
};
// middleware.ts (Edge runtime)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
// Public routes that don't require authentication
const publicRoutes = ["/", "/login", "/register"];
export function middleware(req: NextRequest) {
// Edge runtime cannot make database calls
// Use presence-only check for optimistic redirects
const sessionToken = getSessionCookie(req);
const hasSessionCookie = !!sessionToken;
const isOnPublicRoute = publicRoutes.includes(req.nextUrl.pathname);
// If on a protected route without any session cookie, redirect to login
// NOTE: This is NOT a security check, only for UX
if (!isOnPublicRoute && !hasSessionCookie) {
return NextResponse.redirect(new URL("/login", req.url));
}
// If logged in (has cookie) and trying to access login/register, redirect to dashboard
if ((req.nextUrl.pathname === "/login" || req.nextUrl.pathname === "/register") && hasSessionCookie) {
return NextResponse.redirect(new URL("/dashboard", req.url));
}
return NextResponse.next();
}
export const config = {
matcher: [
/*
* Match all request paths except for:
* - api routes (handled by Better Auth API)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
*/
"/((?!api|_next/static|_next/image|favicon.ico|public).*)",
],
};
// lib/session-utils.ts
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
// Get session with strict validation (forces DB lookup)
export async function getStrictSession() {
const session = await auth.api.getSession({
headers: await headers(),
query: {
disableCookieCache: true // Forces database validation
}
});
return session;
}
// Revoke session (for logout, security operations)
export async function revokeCurrentSession() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (session) {
await auth.api.revokeSession({
sessionId: session.session.id,
headers: await headers(),
});
}
}
// Revoke all other sessions (for password change, security)
export async function revokeOtherSessions() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (session) {
await auth.api.revokeOtherSessions({
userId: session.user.id,
headers: await headers(),
});
}
}
// Additional security measures in auth configuration
advanced: {
// Disable CSRF and origin checks only if you know exactly why (NEVER in production)
// disableCSRFCheck: false, // Default and recommended
// disableOriginCheck: false, // Default and recommended
// Additional security settings
useSecureCookies: process.env.NODE_ENV === "production",
cookiePrefix: "yourapp",
defaultCookieAttributes: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
path: "/",
}
},
rateLimit: {
enabled: process.env.NODE_ENV === "production",
window: 60 * 1000, // 1 minute window
max: 10, // 10 attempts per window
// Stricter limits for sensitive routes
overrides: {
signIn: { max: 5 },
forgotPassword: { max: 3 },
}
},
logger: {
level: process.env.NODE_ENV === "production" ? "warn" : "debug"
}
Use @better-auth:list_files to see available documentation:
// List all knowledge base files
@better-auth:list_files
Use @better-auth:search to find specific topics:
// Search for session configuration
@better-auth:search query="session management configuration"
BETTER_AUTH_SECRET is set in your hosting platform (Vercel, Netlify, etc.)sessions.token, sessions.userId, users.email for performanceexport const dynamic = 'force-dynamic'; to protected pages to prevent static generationdynamic = 'force-dynamic' to the pageBETTER_AUTH_SECRET is the same in all environments-- Create indexes for better performance
CREATE INDEX idx_sessions_token ON sessions(token);
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_accounts_user_id ON accounts(user_id);
CREATE INDEX idx_verifications_identifier ON verifications(identifier);
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience