Skip to main content

Command Palette

Search for a command to run...

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

Published
3 min read
How to Implement Full Authentication in Next.js with JWT and PostgreSQL
V

Hi, 👋 I'm Vinay Patel I am a Software Developer with a passion for building scalable and high-performance applications.

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

ErrorFix
verifyToken not exportedExported it from lib/auth.ts
Login not redirectingUsed credentials: 'include' in fetch
Navbar not updatingUsed 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.

More from this blog

J

Javascript Interview

14 posts