DocumentationGuidesAuthentication

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 32

NextAuth 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

  1. Create a GitHub OAuth App:

  2. Add credentials to .env:

AUTH_GITHUB_ID="your-client-id"
AUTH_GITHUB_SECRET="your-client-secret"
  1. The provider is configured in src/server/auth/providers/github.ts

Google

  1. 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
  2. Add credentials to .env:

AUTH_GOOGLE_ID="your-client-id"
AUTH_GOOGLE_SECRET="your-client-secret"
  1. 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

  1. User registers with email
  2. Verification email is sent
  3. User clicks link in email
  4. Email is marked as verified
  5. 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.com

Or 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:

  1. Verify AUTH_SECRET is set in .env
  2. Ensure NEXTAUTH_URL matches your domain
  3. Restart dev server

OAuth Callback Errors

For OAuth callback issues:

  1. Check redirect URIs in provider settings
  2. Verify client IDs and secrets
  3. Ensure provider is added to config

Session Not Persisting

If sessions don’t persist:

  1. Check cookie settings in browser
  2. Verify database adapter is configured
  3. Check session duration settings

Next Steps