How to Implement Full Authentication in Next.js with JWT and PostgreSQL


In this article, I'll walk you through how I implemented a complete authentication system using:
PostgreSQL for user data
JWT for secure authentication
React Context API for managing auth state
Middleware for protected routes
A dynamic navbar that displays the logged-in user's name
Errors I encountered—and how I fixed them!
Step 1: Setting Up PostgreSQL and Prisma
I started by initializing PostgreSQL and integrating it with Prisma ORM.
Prisma Schema Example:
model User {
id String @id @default(uuid())
name String
email String @unique
password String
createdAt DateTime @default(now())
}
Then ran:
npx prisma migrate dev --name init
This generated the tables in my PostgreSQL database.
Step 2: Creating Auth Utilities with JWT
I created lib/auth.ts
to handle:
Password hashing with bcrypt
Token generation with jsonwebtoken
Token verification
Example:
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
export const generateToken = (user: any) => {
return jwt.sign({ id: user.id, email: user.email, name: user.name }, process.env.JWT_SECRET!, {
expiresIn: '1d',
});
};
export const verifyToken = (token: string) => {
return jwt.verify(token, process.env.JWT_SECRET!);
};
Step 3: Backend Login Route
I created /api/auth/login/route.ts
to validate credentials and set the token in cookies.
Key steps:
Compare hashed passwords
On success: generate JWT and send as cookie
Return user data
Error Faced:
"Login failed" on every attempt
Solution:
Ensured credentials: 'include'
was added in frontend fetch()
to allow cookies to persist.
Step 4: Frontend Auth Context
I created a global AuthContext
to manage login, logout, register, and current user state using useEffect
.
useEffect(() => {
async function loadUser() {
const res = await fetch('/api/auth/me');
if (res.ok) {
const data = await res.json();
setUser(data.user);
}
setLoading(false);
}
loadUser();
}, []);
Step 5: Protecting Routes with Middleware
Used Next.js middleware to check if user has token for protected pages like /cart
.
import { NextResponse } from 'next/server';
export function middleware(req: NextRequest) {
const token = req.cookies.get('token')?.value;
if (!token && req.nextUrl.pathname.startsWith('/cart')) {
return NextResponse.redirect(new URL('/login', req.url));
}
return NextResponse.next();
}
Step 6: Testing Login + Redirect to Cart
After login, user redirected to /cart
:
window.location.href = '/cart';
This worked only after fixing credentials: 'include'
in the fetch call.
Step 7: Fetch Logged-in User in /api/auth/me
Here’s the important route to get the user from token:
import { cookies } from 'next/headers';
import { verifyToken } from '../../../../lib/auth';
export async function GET() {
const token = cookies().get('token')?.value;
if (!token) return NextResponse.json({ user: null });
try {
const decoded = verifyToken(token);
return NextResponse.json({ user: decoded });
} catch (e) {
return NextResponse.json({ user: null });
}
}
Error Encountered:
Export verifyToken doesn't exist in target module
Fix:
I forgot to export verifyToken
from lib/auth.ts
. Added:
export const verifyToken = ...
Step 8: Display Logged-in User Name in Navbar
Finally, I updated the Navbar:
const { user, isAuthenticated, loading } = useAuth();
{!loading && isAuthenticated && (
<span className="text-sm font-medium text-gray-700">Hi, {user?.name}</span>
)}
Final Result
PostgreSQL stores secure user data
JWT authenticates users and persists session
Middleware protects sensitive routes
Navbar shows real-time user name
Auth state persists with React Context +
/api/auth/me
Bugs I Solved
Error | Fix |
verifyToken not exported | Exported it from lib/auth.ts |
Login not redirecting | Used credentials: 'include' in fetch |
Navbar not updating | Used useEffect to fetch user on load |
{"user":{}} | Fixed logic in /api/auth/me to return user properly |
Have questions or improvements? Drop them in the comments.
Subscribe to my newsletter
Read articles from Vinay Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Vinay Patel
Vinay Patel
Hi, My name is Vinay Patel, and I'm an aspiring software developer.