Authentication
This template includes a complete authentication system built with NextAuth.js v5, supporting email/password authentication and OAuth providers.
Overview
The authentication system provides:
- Email/Password Authentication with secure password hashing
- OAuth Providers (GitHub, Google, etc.)
- Email Verification for new accounts
- Password Reset functionality
- Role-Based Access Control (RBAC)
- Session Management with JWT tokens
Quick Start
Email/Password Registration
Users can register with email and password at /register:
import { register } from "~/server/auth/users";
const user = await register({
email: "user@example.com",
password: "securepassword123",
name: "John Doe",
});Login
Users can log in at /login:
import { signIn } from "next-auth/react";
await signIn("credentials", {
email: "user@example.com",
password: "securepassword123",
redirectTo: "/dashboard",
});Get Current User
Access the authenticated user in Server Components:
import { auth } from "~/server/auth";
export default async function Page() {
const session = await auth();
if (!session?.user) {
return <div>Please log in</div>;
}
return <div>Welcome, {session.user.name}!</div>;
}Protect API Routes
Protect API routes with authentication:
import { auth } from "~/server/auth";
import { NextResponse } from "next/server";
export async function GET() {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Your protected logic here
return NextResponse.json({ data: "Protected data" });
}Configuration
Environment Variables
Configure authentication in .env:
# Required
AUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"
# Optional OAuth providers
AUTH_GITHUB_ID="your-github-client-id"
AUTH_GITHUB_SECRET="your-github-client-secret"
AUTH_GOOGLE_ID="your-google-client-id"
AUTH_GOOGLE_SECRET="your-google-client-secret"Generate AUTH_SECRET:
openssl rand -base64 32NextAuth Configuration
The auth configuration is in src/server/auth/config.ts:
export const authConfig = {
adapter: DrizzleAdapter(db),
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: "/login",
signOut: "/",
error: "/login",
verifyRequest: "/login",
},
providers: [credentialsProvider, githubProvider, googleProvider],
callbacks: {
// Custom callbacks
},
};OAuth Providers
GitHub
-
Create a GitHub OAuth App:
- Go to GitHub Developer Settings
- Click New OAuth App
- Set callback URL:
http://localhost:3000/api/auth/callback/github
-
Add credentials to
.env:
AUTH_GITHUB_ID="your-client-id"
AUTH_GITHUB_SECRET="your-client-secret"- The provider is configured in src/server/auth/providers/github.ts
-
Create Google OAuth credentials:
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google
-
Add credentials to
.env:
AUTH_GOOGLE_ID="your-client-id"
AUTH_GOOGLE_SECRET="your-client-secret"- The provider is configured in src/server/auth/providers/google.ts
Add Custom Provider
Create a new provider file in src/server/auth/providers/:
// src/server/auth/providers/discord.ts
import Discord from "next-auth/providers/discord";
import { env } from "~/env";
export const discordProvider = Discord({
clientId: env.AUTH_DISCORD_ID,
clientSecret: env.AUTH_DISCORD_SECRET,
});Add to src/server/auth/config.ts:
import { discordProvider } from "./providers/discord";
export const authConfig = {
// ... other config
providers: [
credentialsProvider,
githubProvider,
googleProvider,
discordProvider, // Add here
],
};Email Verification
Enable Email Verification
Email verification is enabled by default for new registrations.
Verification Flow
- User registers with email
- Verification email is sent
- User clicks link in email
- Email is marked as verified
- User can log in
Configure Email Sending
Email configuration in .env:
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_USER="your-email@gmail.com"
SMTP_PASSWORD="your-app-password"
SMTP_FROM="noreply@yourdomain.com"Email templates are in src/server/email/templates/
Password Reset
Request Password Reset
Users can request password reset at /forgot-password:
import { sendPasswordResetEmail } from "~/server/auth/password";
await sendPasswordResetEmail("user@example.com");Reset Password
Reset password with token:
import { resetPassword } from "~/server/auth/password";
await resetPassword({
token: "reset-token",
newPassword: "newpassword123",
});Role-Based Access Control
User Roles
The system supports multiple user roles:
- user - Default role for registered users
- admin - Full access to admin panel
- moderator - Limited admin access
Check User Role
In Server Components:
import { auth } from "~/server/auth";
export default async function AdminPage() {
const session = await auth();
if (session?.user?.role !== "admin") {
return <div>Access denied</div>;
}
return <div>Admin Panel</div>;
}In API Routes:
import { auth } from "~/server/auth";
export async function POST() {
const session = await auth();
if (session?.user?.role !== "admin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
// Admin-only logic
}Assign Roles
Make a user admin:
npm run make-admin -- user@example.comOr programmatically:
import { db } from "~/server/db";
import { users } from "~/server/db/schema";
import { eq } from "drizzle-orm";
await db
.update(users)
.set({ role: "admin" })
.where(eq(users.email, "user@example.com"));Middleware Protection
Protect routes with middleware in middleware.ts:
import { auth } from "~/server/auth";
import { NextResponse } from "next/server";
export default auth((req) => {
const isAuthenticated = !!req.auth;
const isAuthPage = req.nextUrl.pathname.startsWith("/login");
if (isAuthPage && isAuthenticated) {
return NextResponse.redirect(new URL("/dashboard", req.url));
}
if (!isAuthPage && !isAuthenticated) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
});
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};Session Management
Session Duration
Configure in src/server/auth/config.ts:
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
updateAge: 24 * 60 * 60, // Update every 24 hours
}Refresh Session
Client-side session refresh:
"use client";
import { useSession } from "next-auth/react";
export function SessionRefresh() {
const { data: session, update } = useSession();
const refreshSession = async () => {
await update();
};
return <button onClick={refreshSession}>Refresh Session</button>;
}Sign Out
"use client";
import { signOut } from "next-auth/react";
export function SignOutButton() {
return <button onClick={() => signOut()}>Sign Out</button>;
}Security Best Practices
1. Password Hashing
Passwords are hashed using bcrypt with appropriate salt rounds:
import { hash } from "~/server/auth/hash";
const hashedPassword = await hash("plaintext-password");2. CSRF Protection
NextAuth.js includes built-in CSRF protection for all auth routes.
3. Secure Cookies
Session cookies are configured securely:
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
},
},
}4. Rate Limiting
Implement rate limiting for auth endpoints:
import { rateLimit } from "~/server/rate-limit";
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for") ?? "unknown";
const { success } = await rateLimit.limit(ip);
if (!success) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
// Handle request
}Troubleshooting
”Configuration” Error
If you see configuration errors:
- Verify
AUTH_SECRETis set in.env - Ensure
NEXTAUTH_URLmatches your domain - Restart dev server
OAuth Callback Errors
For OAuth callback issues:
- Check redirect URIs in provider settings
- Verify client IDs and secrets
- Ensure provider is added to config
Session Not Persisting
If sessions don’t persist:
- Check cookie settings in browser
- Verify database adapter is configured
- Check session duration settings