A comprehensive authentication system built with Next.js 15, NextAuth v5, Prisma, and PostgreSQL. This application demonstrates modern authentication patterns including OAuth integration, email verification, two-factor authentication, and role-based access control.
The application implements a complete authentication flow with support for both OAuth providers (Google and GitHub) and credential-based authentication. JWT-based sessions provide stateless authentication whilst Prisma with PostgreSQL ensures persistent storage for users, tokens, and account data. Email verification, password reset, and two-factor authentication are handled through secure token-based flows using Resend for email delivery.
The application provides complete OAuth authentication functionality:
- Google OAuth 2.0 integration with NextAuth
- GitHub OAuth 2.0 integration with NextAuth
- Automatic account linking for OAuth providers
- Automatic email verification for OAuth users
- Separate user flow for OAuth vs. credentials authentication
Email and password authentication with security features:
- Email and password registration with validation
- Password hashing with bcryptjs (10 salt rounds)
- Email verification required before first login
- Account validation against database
- Prevention of OAuth account conflicts
Email verification system for new accounts:
- Verification email sent on registration via Resend
- Single-use verification tokens (UUID v4)
- Token expiration (1 hour)
- Automatic verification for OAuth users
- Re-send verification option during login
- Token validation on protected verification page
Secure password reset functionality:
- Password reset request via email form
- Single-use reset tokens sent via email
- Token expiration (1 hour)
- New password validation (minimum 8 characters)
- Automatic token deletion after successful reset
- Current password verification for password changes
Optional email-based two-factor authentication:
- 6-digit random code generation
- Code delivery via email
- Token expiration (15 minutes)
- Per-user 2FA toggle in settings
- Enforced during credential login flow
- Not available for OAuth users
User role management and enforcement:
- Two roles: ADMIN and USER (default)
- Server-side role validation via server actions
- Client-side role-based UI hiding with RoleGate component
- API route protection with role checks
- Admin-only endpoints returning 403 for non-admins
- Role changes via settings page
Secure session handling:
- JWT-based sessions for stateless authentication
- Session token stored in httpOnly cookies
- Automatic session refresh on data changes
- Extended session data (role, 2FA status, OAuth flag)
- Callback URL preservation for redirect after login
- Logout with session cleanup
Route protection with middleware:
- Authentication-required routes under
(protected)group - Automatic redirect to login for unauthenticated users
- Callback URL preservation in redirect
- Prevention of authenticated users accessing auth pages
- Public routes accessible without authentication
- Middleware running on all routes except static assets
Profile settings and updates:
- Name, email, and password updates
- Email change with verification flow
- Current password validation before password change
- 2FA toggle in settings
- Role selection (for demonstration)
- OAuth user restrictions (cannot change email/password/2FA)
Secure token generation and validation:
- Verification tokens (UUID v4, 1-hour expiry)
- Password reset tokens (UUID v4, 1-hour expiry)
- Two-factor tokens (6-digit number, 15-minute expiry)
- Automatic deletion of expired tokens
- Single-use token enforcement
- Token validation before operations
These are the requirements needed to run the project:
- Node.js 20 or higher
- PostgreSQL database (local or cloud-hosted like Neon)
- Google OAuth Application credentials (Client ID and Client Secret)
- GitHub OAuth Application credentials (Client ID and Client Secret)
- Resend API key for email delivery
These are the main technologies used in this project:
- TypeScript: Strongly typed programming language building on JavaScript.
- Next.js: A React framework with server-side rendering, App Router, and Turbopack support.
- React.js: A JavaScript library for building user interfaces with components.
- Tailwind CSS: A utility-first CSS framework for rapid UI development.
- shadcn/ui: Beautifully designed components built with Radix UI and Tailwind CSS.
- React Hook Form: Performant, flexible forms with easy-to-use validation.
- Zod: TypeScript-first schema declaration and validation library.
- Lucide React: Beautiful & consistent icon toolkit.
- Sonner: An opinionated toast component for React.
- Auth.js (NextAuth.js): Complete authentication solution for Next.js applications with OAuth and credentials support.
- PostgreSQL: Advanced open-source relational database with strong data integrity.
- Prisma: Next-generation ORM for Node.js and TypeScript with type safety.
- bcryptjs: Library for hashing and comparing passwords securely.
- Resend: Modern email API for developers with high deliverability.
The application uses NextAuth v5 with a JWT session strategy rather than database sessions. This approach enables edge runtime compatibility and reduces database queries. Session tokens are stored in httpOnly cookies to prevent XSS attacks. The JWT contains extended user information including role, two-factor status, and OAuth flag, which is synchronized with the database on every request through the jwt callback.
PostgreSQL stores six main collections via Prisma:
User: Stores user accounts with email, password hash, role, and 2FA preferencesAccount: Stores OAuth provider data (Google, GitHub) linked to usersVerificationToken: Stores email verification tokens with 1-hour expiryPasswordResetToken: Stores password reset tokens with 1-hour expiryTwoFactorToken: Stores 2FA codes with 15-minute expiryTwoFactorConfirmation: Stores 2FA confirmation state per user
All token models use unique constraints on email and token to prevent duplicates. Previous tokens are automatically deleted when generating new ones for the same email.
+-----------------------+
| User |
|-----------------------|
| id (PK) |
| email (unique) |
| role |
| password (nullable) |
| isTwoFactorEnabled |
+-----------+-----------+
|
| 1 ────────* accounts
v
+----------+-----------+
| Account |
|----------------------|
| id (PK) |
| userId (FK → User) |
| provider |
| providerAccountId |
| access/refresh tokens|
+----------------------+
^
| 1 ──────── 0..1 confirmation
+-----------+-----------+
| TwoFactorConfirmation |
|-----------------------|
| id (PK) |
| userId (unique FK) |
+-----------------------+
+-----------------------+ +-----------------------+ +---------------------+
| VerificationToken | | PasswordResetToken | | TwoFactorToken |
|-----------------------| |-----------------------| |---------------------|
| id (PK) | | id (PK) | | id (PK) |
| email (unique + token)| | email (unique + token)| | email (unique token)|
| token (unique) | | token (unique) | | token (unique) |
| expires | | expires | | expires |
+-----------------------+ +-----------------------+ +---------------------+
The User table sits at the center of the schema with a one-to-many relationship
to Account for OAuth providers and a one-to-one relationship with
TwoFactorConfirmation to mark successful 2FA challenges. The token tables
(VerificationToken, PasswordResetToken, TwoFactorToken) are intentionally
decoupled from foreign keys and instead use unique (email, token) pairs so that
tokens can be issued before an account exists and can be rotated without
referential constraints. Cascading deletes on the Account and
TwoFactorConfirmation relations ensure that removing a user automatically
cleans up linked records.
Verification and password reset tokens use UUID v4 for cryptographic randomness. Two-factor tokens use 6-digit random numbers (100,000 to 999,999) for user convenience. All tokens include expiration timestamps validated server-side before operations. Tokens are single-use and deleted immediately after successful validation.
Middleware intercepts all requests to enforce authentication rules. Unauthenticated users accessing protected routes are redirected to login with the original URL preserved as a callback parameter. Authenticated users cannot access authentication pages and are redirected to the settings page. Public routes and NextAuth API endpoints bypass all protection checks.
- User initiates login (credentials or OAuth)
- For credentials: validates email verification and 2FA if enabled
- NextAuth processes authentication via signIn callback
- JWT token generated with user claims (ID, role, 2FA status, OAuth flag)
- Session cookie set as httpOnly with Secure flag
- User redirected to callback URL or default protected page
- Subsequent requests include session cookie automatically
- Middleware validates authentication on protected routes
- User registers with email and password
- Server generates verification token (UUID, 1-hour expiry)
- Verification email sent via Resend with token link
- User clicks link to
/auth/new-verification?token=xxx - Server validates token exists and not expired
- User's
emailVerifiedtimestamp updated in database - Token deleted from database
- User redirected to login
- User requests password reset via email form
- Server generates reset token (UUID, 1-hour expiry)
- Reset email sent via Resend with token link
- User clicks link to
/auth/new-password?token=xxx - User enters new password
- Server validates token and password strength
- Password hashed with bcryptjs and updated in database
- Token deleted from database
- User redirected to login
- User enables 2FA in settings page
- On next credential login, server detects 2FA enabled
- Server generates 6-digit code (15-minute expiry)
- Code sent via email
- Login form shows 2FA code input
- User enters code from email
- Server validates code against database
- TwoFactorConfirmation record created
- Session created and user logged in
- TwoFactorConfirmation deleted after successful login
- User clicks logout button
- Client calls logout server action
- Server calls NextAuth
signOut() - Session cookie deleted
- User redirected to home page
These are simple steps to run the application locally.
git clone https://github.com/mbeps/oauth-nextjs-springboot-backend.git
cd oauth-nextjs-springboot-backendyarn installEnsure PostgreSQL is running locally or create a serverless PostgreSQL database on Neon. Note your database connection string for the next step.
Create a Google OAuth application with the following settings:
- Authorized JavaScript origins:
http://localhost:3000 - Authorized redirect URIs:
http://localhost:3000/api/auth/callback/google
Note your Client ID and Client Secret.
For Production: Update the origins and redirect URIs to your production domain (e.g., https://yourdomain.com instead of http://localhost:3000).
Create a GitHub OAuth application with the following settings:
- Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
Note your Client ID and Client Secret.
For Production: Update the Homepage URL and Authorization callback URL to your production domain (e.g., https://yourdomain.com instead of http://localhost:3000).
Sign up for a Resend account and obtain your API key from the dashboard.
For Production: Verify your domain in Resend to send emails from your own domain instead of onboarding@resend.dev.
Create a .env file in the project root:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/auth_db?schema=public"
# NextAuth
AUTH_SECRET="your-auth-secret-here" # Generate with: openssl rand -base64 32
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_TRUST_HOST="true"
# GitHub OAuth
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
# Google OAuth
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
# Resend
RESEND_API_KEY="your-resend-api-key"
# Public App URL for email links
NEXT_PUBLIC_APP_URL="http://localhost:3000"Environment Variable Descriptions:
DATABASE_URL:
- PostgreSQL connection string for Prisma
- Format:
postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA - Example local:
postgresql://postgres:password@localhost:5432/auth_db - Example Neon:
postgresql://user:pass@ep-xxx.region.aws.neon.tech/dbname?sslmode=require
AUTH_SECRET:
- Secret key for signing and encrypting JWT tokens
- Generate with:
openssl rand -base64 32 - Keep this secret and never commit to version control
NEXTAUTH_URL:
- The canonical URL of your site for OAuth callbacks
- Local development:
http://localhost:3000 - Production:
https://yourdomain.com
GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET:
- Credentials from GitHub OAuth application
- Obtained from GitHub Developer Settings > OAuth Apps
GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET:
- Credentials from Google Cloud Console OAuth application
- Obtained from Google Cloud Console > Credentials
RESEND_API_KEY:
- API key for Resend email service
- Obtained from Resend dashboard
NEXT_PUBLIC_APP_URL:
- Public URL used in email links for verification and reset
- Must match your actual site URL
- Local:
http://localhost:3000 - Production:
https://yourdomain.com
For Production:
- Set
NEXTAUTH_URLto your production domain - Set
NEXT_PUBLIC_APP_URLto your production domain - Use a strong, randomly generated
AUTH_SECRET - Configure PostgreSQL with SSL/TLS
- Update OAuth callback URLs to production domain
npm run prisma-pushThis will push the Prisma schema to your database and generate the Prisma Client.
npm run devThe application should now be running on http://localhost:3000
- Navigate to
/auth/register - Enter name, email, and password (minimum 8 characters)
- Click "Create an account"
- Verification email sent to provided email address
- Click verification link in email
- Redirected to login page
- Login with credentials
Standard Login:
1. Navigate to /auth/login
2. Enter email and password
3. Click "Sign In"
4. Redirected to callback URL or /settings
Login with 2FA Enabled:
1. Navigate to /auth/login
2. Enter email and password
3. Click "Sign In"
4. 2FA code input appears
5. Check email for 6-digit code
6. Enter code and submit
7. Redirected to callback URL or /settings
Login with Unverified Email:
1. Navigate to /auth/login
2. Enter email and password
3. Click "Sign In"
4. New verification email sent
5. Click verification link in email
6. Return to login and try again
Google Login:
1. Navigate to /auth/login
2. Click "Continue with Google" button
3. Authenticate with Google
4. Redirected back to application
5. Logged in and redirected to /settings
GitHub Login:
1. Navigate to /auth/login
2. Click "Continue with GitHub" button
3. Authenticate with GitHub
4. Redirected back to application
5. Logged in and redirected to /settings
1. Navigate to /auth/reset
2. Enter email address
3. Click "Send reset email"
4. Check email for reset link
5. Click link to /auth/new-password?token=xxx
6. Enter new password (minimum 8 characters)
7. Click "Reset password"
8. Redirected to login page
9. Login with new password
1. Login to application
2. Navigate to /settings
3. Find "Two Factor Authentication" section
4. Toggle switch to ON
5. Save changes
6. Logout and login again
7. Enter 6-digit code from email
8. Successfully logged in with 2FA
1. Login to application (credentials only, not OAuth)
2. Navigate to /settings
3. Enter new email address
4. Click "Save"
5. Verification email sent to new email address
6. Click verification link in email
7. Email updated in profile
1. Login to application (credentials only, not OAuth)
2. Navigate to /settings
3. Enter current password
4. Enter new password (minimum 8 characters)
5. Click "Save"
6. Password updated successfully
Protected routes require authentication:
Settings Page:
Navigate to /settings
- Redirected to login if not authenticated
- Access granted if authenticated
Admin Page:
Navigate to /admin
- Redirected to login if not authenticated
- Access granted if authenticated
- Admin-only content hidden for non-admin users via RoleGate
Server Component Demo:
Navigate to /server
- Demonstrates server-side authentication
- Shows user information from server session
Client Component Demo:
Navigate to /client
- Demonstrates client-side authentication
- Shows user information from client session
Admin-only API endpoint with role validation:
GET /api/admin
Authorization: Session Cookie (automatic)Response if Admin:
{
"message": "Access granted"
}Response if Not Admin:
403 Forbidden
All protected pages automatically check authentication via middleware.
Use the currentUser() server function in server components or useCurrentUser() hook in client components to access user data.
1. Click user avatar in top right
2. Click "Logout" from dropdown menu
3. Session cleared
4. Redirected to home page