A Practical Guide for Secure Login, JWT Handling, and Protected Routes
Authentication is one of the most important parts of any modern web application. Whether you are building a SaaS product, an admin dashboard, an eCommerce platform, or a custom internal tool, you must ensure that only authorized users can access protected resources.
If you are using Next.js with a separate backend API (such as Node.js with Express, ASP.NET Core, Django, or any REST API), the most common and scalable approach is token-based authentication using JWT (JSON Web Tokens).
In this guide, you will understand how authentication works in a real-world Next.js application, how to connect it with a backend API, how to store tokens securely, and how to protect routes both on the client and server side.
Understanding the Architecture
Before writing any code, let’s understand the typical architecture.
You usually have:
Frontend: Next.js application
Backend: REST API (Node.js, .NET, etc.)
Database: Stores users and hashed passwords
Authentication Method: JWT-based authentication
Here’s how the login flow works:
User enters email and password.
Next.js sends credentials to the backend API.
Backend validates credentials.
Backend generates a JWT access token.
Token is returned to Next.js.
Token is stored securely.
Future requests include the token in the Authorization header.
Think of JWT like a digital identity card. Once the server issues it, the frontend carries it with every request to prove identity.
Step 1: Backend API – Login Endpoint
Your backend must expose an authentication endpoint.
Example endpoint:
POST /api/auth/login
Backend logic should:
A typical response looks like this:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600
}
Important security practices:
Always hash passwords (bcrypt or similar)
Use HTTPS in production
Set short expiry for access tokens
Optionally use refresh tokens
Step 2: Creating Login in Next.js
In your Next.js application, create a login page.
Inside your login function:
Collect email and password.
Send POST request to backend API.
Receive access token.
Store the token securely.
Example logic (simplified explanation):
Where Should You Store the Token?
This is extremely important.
There are three common approaches:
localStorage
sessionStorage
HTTP-only cookies
The most secure production-ready approach is:
Use HTTP-only cookies.
Why?
Because JavaScript cannot access HTTP-only cookies, which protects against XSS attacks.
Real-world example:
If you store tokens in localStorage and your site has a cross-site scripting vulnerability, attackers can steal tokens easily. But HTTP-only cookies cannot be accessed by malicious scripts.
Step 3: Setting HTTP-Only Cookie (Recommended)
Instead of storing the token in localStorage, your backend should set the cookie directly.
Example backend behavior:
Set-Cookie: accessToken=xxxxx; HttpOnly; Secure; SameSite=Strict
Now the browser automatically includes this cookie in future requests.
No manual token handling required on the frontend.
This is considered best practice for production applications.
Step 4: Protecting API Requests in Next.js
When calling protected endpoints:
GET /api/user/profile
The browser automatically sends the cookie.
Backend middleware should:
If token is invalid, return 401 Unauthorized.
Step 5: Protecting Pages in Next.js
There are two levels of protection.
Client-side protection:
Server-side protection (more secure):
Using middleware in Next.js.
Example scenario:
If user tries to access /dashboard without authentication, middleware checks cookie and redirects to /login.
This prevents even initial rendering of protected pages.
Step 6: Using Next.js Middleware
Create a middleware.js file in your Next.js project.
Middleware logic:
Read cookies
Validate token
Redirect if invalid
This works especially well in Next.js App Router architecture.
Optional: Using NextAuth.js
If you want to avoid building everything manually, you can use NextAuth.
NextAuth supports:
It reduces boilerplate and improves security.
However, for enterprise systems using custom backend APIs, manual JWT handling is often preferred for full control.
Real-World Example Scenario
Let’s say you are building a SaaS billing dashboard.
Without authentication:
Anyone can access financial reports.
With proper authentication:
Authentication is not just login. It is identity verification + access control.
Common Mistakes Developers Make
Storing tokens in localStorage in production.
Not setting Secure flag on cookies.
Not handling token expiry.
Not validating JWT on every request.
Forgetting CORS configuration when frontend and backend are on different domains.
Always configure CORS properly:
Advantages of JWT-Based Authentication
Stateless authentication
Scalable across microservices
Works well with REST APIs
No server session storage needed
Disadvantages
Token revocation is complex
If token is stolen, it works until expiry
Requires careful security configuration
Summary
Implementing authentication in Next.js with a backend API involves creating a secure login endpoint, generating JWT tokens, storing them safely using HTTP-only cookies, and protecting both API routes and frontend pages using middleware. When implemented correctly, this approach ensures scalable, stateless, and secure authentication suitable for SaaS platforms, enterprise dashboards, and production-grade web applications. The key to success lies in proper token handling, secure cookie configuration, strict backend validation, and thoughtful route protection to prevent unauthorized access.