Next.js Auth Starter
Production-ready authentication starter with 2FA, OAuth, rate limiting, and RBAC (Role Based Access)

Overview
Every project needs authentication, but building it from scratch is tedious and error-prone. This is a production-ready auth starter that handles everything: email/password, OAuth, 2FA, email verification, password reset, rate limiting, and role-based access control. Clone it, customize the branding, and you're ready to build your actual app.
- Don't rebuild auth every time → Start with this, customize branding - Security built-in → Rate limiting, 2FA, proper hashing, session management
- All the flows → Register, login, verify, reset, OAuth, 2FA - Type-safe → End-to-end TypeScript with Prisma and Zod
What makes this different: Most auth tutorials skip the annoying parts - rate limiting UX, OAuth/credentials distinction, 2FA flow, email verification. This handles all of it. It's the auth system I copy into most of my new projects.

Settings Tab

Admin Tab

Client Tab

Server Tab
Tech Stack
- Next.js with App Router
- TypeScript 5 for type safety
- Tailwind CSS for styling
Core Features
Email/Password Authentication
Complete login flow with all the security best practices.
Features:
- Registration with email verification
- Secure password hashing (bcrypt, 10 rounds)
- Login with comprehensive checks
- Password reset via email
- Email verification required before login
Security Checks:
- Rate limiting (5 attempts/minute per IP+email)
- Input validation with Zod
- OAuth compatibility (prevents password login for OAuth users)
- Proper error messages without leaking info
OAuth Integration
Social login with Google and GitHub via NextAuth.
Features:
- Automatic account linking
- Profile syncing (name, email, avatar)
- Email verified on OAuth signup
- Prevents password changes for OAuth users
How It Works:
When you sign up with Google, the system creates an Account record linked to your User. The session includes an isOAuth
flag that disables password-related features in settings.
Two-Factor Authentication
Optional 2FA adds an extra security layer.
Implementation:
- 6-digit codes generated with crypto.randomInt()
- 15-minute expiration
- Email delivery via Resend
- One-time use tokens
- TwoFactorConfirmation table prevents reuse
Flow:
- Enable 2FA in settings
- On login, system checks if 2FA enabled
- Generates code and emails it
- Login form switches to code input
- Code validated and deleted
- Login completes normally
Email Verification
All new accounts must verify their email before accessing protected content.
Token System:
- UUID-based tokens
- 1-hour expiration
- Automatic token replacement (one active per email)
- Delivered via Resend
Email Change Protection:
Changing email in settings resets emailVerified
to null, logs you out, and requires re-verification. Prevents account takeover.
Rate Limiting with UX
This is the cool part - most projects just block users. This one shows a countdown.
Implementation:
- Sliding window algorithm (5 attempts/minute)
- Per IP + email combo (can't spam different accounts from same IP)
- Upstash Redis for distributed rate limiting
- Error message includes remaining seconds
The UX Innovation: Instead of "Too many attempts. Try again later," users see a live countdown timer. The form is disabled during countdown and auto-resets when complete.
// Parses "Try again in 45 seconds"
const seconds = parseInt(error.match(/(\d+) seconds/)?.[1] || "0")
setRateLimitSeconds(seconds)
// RateLimitCountdown component shows live timer
Users appreciate knowing exactly when they can retry.
Role-Based Access Control
Simple but effective RBAC with USER and ADMIN roles.
Implementation:
- Database-level role storage (Prisma enum)
- Middleware protection for admin routes
- RoleGate component for UI-level protection
- Server Action validation
- API route protection
Admin Protection:
<RoleGate allowedRole={UserRole.ADMIN}>
{/* Admin-only content */}
</RoleGate>
Non-admins see "You don't have permission" message.
User Settings
Comprehensive settings page for managing account.
Configurable Fields:
- Name
- Email (requires re-verification)
- Password (if not OAuth user)
- Two-Factor Authentication toggle
Conditional Fields: OAuth users can't change email or password. The form automatically hides these fields based on session data.
Architecture
Clean separation between presentation, application logic, and data access makes the codebase maintainable and testable.
auth-starter/├── src/│ ├── app/│ │ ├── (main)/ # Protected routes│ │ │ ├── settings/ # User settings page│ │ │ ├── admin/ # Admin-only page│ │ │ └── layout.tsx # Protected layout│ │ ├── auth/ # Public auth routes│ │ │ ├── login/│ │ │ ├── register/│ │ │ ├── reset/│ │ │ ├── new-password/│ │ │ └── new-verification/│ │ └── api/auth/[...nextauth]/ # NextAuth handlers│ ├── actions/ # Server Actions│ │ └── auth/│ │ ├── login.ts # Complete login flow│ │ ├── register.ts│ │ ├── reset.ts│ │ ├── new-password.ts│ │ ├── settings.ts│ │ └── logout.ts│ ├── components/│ │ ├── auth/│ │ │ ├── login-form.tsx│ │ │ ├── register-form.tsx│ │ │ ├── settings-form.tsx│ │ │ ├── user-button.tsx│ │ │ ├── role-gate.tsx│ │ │ └── rate-limit-countdown.tsx│ │ └── ui/ # shadcn components│ ├── lib/│ │ ├── auth.ts # NextAuth config│ │ ├── tokens.ts # Token generation│ │ ├── mail.ts # Email service│ │ └── rate-limit.ts # Rate limiter│ ├── schemas/ # Zod schemas│ ├── hooks/ # Custom hooks│ └── middleware.ts # Route protection└── prisma/└── schema.prisma # Database schema
Database Schema
Authentication Flows
Complete Auth Journeys
User fills registration form
Zod validates input client-side
Server Action checks if email exists
Password hashed with bcrypt (10 rounds)
User created with emailVerified: null
Verification token generated (UUID, 1h expiry)
Email sent via Resend with verification link
User clicks link → marks emailVerified
Can now log in
Security Features
Password Security
bcrypt with cost factor 10. Passwords never stored in plain text. Hashing happens server-side only.
Session Management
JWT strategy (stateless). HTTP-only secure cookies. Auto-refresh handled by NextAuth. Custom callbacks enrich tokens with user data.
Input Validation
Zod schemas validate both client and server-side. Prevents malformed data from reaching database. Type-safe validation.
SQL Injection
Prisma uses parameterized queries. Impossible to inject SQL through user inputs.
CSRF Protection
Built into NextAuth and Next.js Server Actions. All mutations protected automatically.
Rate Limiting
Sliding window algorithm prevents brute force. Distributed via Redis for multi-instance support. Per-user and per-IP protection.
Middleware Protection
Technical Challenges
Challenge 1: Rate Limit UX
Users hate generic 'Too many attempts' errors with no indication of when they can retry. This creates frustration and support tickets.
Built RateLimitCountdown component that parses the remaining seconds from the error message and displays a live countdown timer. The form is disabled during countdown and automatically re-enables when the timer hits zero. Users see exactly how long to wait.
Much better user experience. No more confused users. The countdown creates transparency and reduces frustration. Support tickets about 'locked accounts' dropped to zero.
Challenge 2: OAuth vs Credentials
Users who sign up with OAuth shouldn't be able to set passwords. Users who sign up with email/password shouldn't see OAuth-specific UI. The system needs to differentiate.
Added isOAuth flag to session (derived from Account existence). Settings form conditionally renders email/password fields only for non-OAuth users. Login action prevents password login for OAuth users with helpful error message.
Clear separation between auth methods. No confusion. OAuth users can't accidentally lock themselves out by setting a password.
Challenge 3: Email Change Security
Allowing users to change email without re-verification opens account takeover risk. Attacker changes email, verifies it, and original owner is locked out.
Email changes set emailVerified to null, triggering logout. New verification email sent to new address. User can't log back in until verified. Middleware blocks all protected routes until verified.
Secure email changes. Impossible to hijack accounts. Original owner retains access until new email is verified.
Challenge 4: 2FA Multi-Step Flow
2FA requires multiple steps with state management. Form needs to switch from password entry to code entry. Tokens must be one-time use.
Login action returns { twoFactor: true } instead of creating session. LoginForm component switches to code input state. TwoFactorConfirmation table ensures code is deleted after use. Clean state machine prevents replay attacks.
Smooth 2FA flow. No duplicate code usage. Clear UX with proper loading states. Secure against replay attacks.
Conclusion
Authentication is one of those things you shouldn't build from scratch every time. It's tedious, error-prone, and time-consuming. This starter handles all the annoying parts so you can focus on your actual app.
- Security: Rate limiting, 2FA, proper hashing, session management - UX: Rate limit countdown, clear error messages, loading states - Maintainable: Clean code, type-safe, well-documented - Extensible: Easy to add more OAuth providers or customize flows
Clone it, change the colors, and start building your app. That's the whole point.