Authentication and Authorization in Next.js with Express Backend
Authentication and authorization are crucial aspects of web application security. When working with Next.js and an Express backend, implementing a robust authentication system can be challenging due to the different behaviors of server components, client components, and route handlers in Next.js. A regular approach won't suffice. In this post, we'll explore how to use cookies with JWT for secure authentication, addressing common pitfalls and best practices.
Note:
This article assumes you have a good understanding of Next.js and Node.js and can code full-stack applications from scratch. However, I'll briefly cover the fundamentals and provide insights into my strategies for authentication, authorization, and communication between the frontend and backend, focusing on handling cookies in client components, server components, and route handlers with an Express backend.
Benefits of Combining Next.js Server Components with an Express Backend
Best of Both Worlds
Next.js server components leverage the strengths of both Next.js and Express.js:
- Preserved Features: Server-side rendering (SSR), static site generation (SSG), and improved SEO.
- Flexibility: Developers can build dynamic web applications tailored to specific needs.
- Customization: Express.js allows for low-level server control and custom backend logic.
- Separation of Concerns: Simplifies scaling and organizing large projects by separating frontend and backend logic.
- Scalability: Helps achieve scalable architectures for larger projects.
- Performance Optimization: Optimizes both client- and server-side performance independently.
- Development Experience: Benefits from features like automatic code splitting, hot reloading, and TypeScript support.
For projects requiring custom backend logic or integration with existing systems, combining Next.js with Express.js can be highly beneficial. Express APIs can also be used independently to integrate with mobile, desktop, or other services.
Implementation Overview
Key Points to Consider Before Implementing:
- HttpOnly Cookies: Store JWT tokens in HttpOnly cookies to reduce the risk of client-side JavaScript access and token theft.
- Separate Backend Communication: We will discuss the challenges of authenticating requests from a separate Express backend to a Next.js application. The challenge lies in managing cookie-based authentication across client-side and server-side boundaries, as cookies may not be automatically passed in certain requests.
- API Proxies: API proxies work by creating a middle layer that allows Next.js to intercept and forward requests to the backend. This ensures that cookies are correctly attached and sent with the request, as the proxy route runs within the same origin. We’ll explore the concept of API proxies as a solution for handling authentication when making requests to external services from client Components.
//API Proxy implementation
import { getToken } from "@/lib/cookie";
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest) {
const url = `https://express-api.com/api/auth`;
const token = await getToken();
const res = await fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
next: { revalidate: 0 },
});
const data = await res.json();
return NextResponse.json({ message: data.message });
}
// Get token function
import { cookies } from "next/headers";
export async function getToken() {
const cookieStore = cookies();
const data = cookieStore.get("jwt");
return data?.value;
}
//Logout function
export async function logout() {
try {
await deleteSession();
console.log("logged out");
redirect("/login");
} catch (error) {
console.error(error);
}
}
Challenges of Separate Backends
- Cookie Accessibility: Cookies are not automatically sent with requests from Next.js to an external service. This makes it difficult for the backend to verify the request without the token. But if you are sending request to route handlers within the same Next.js server from client components, you don't face this problem. As cookies are httpOnly, you can't excess cookies in any way in client side, so you won’t be explicitly able to send them too.
- Server-Side Component Requests: When making requests from server-side components using fetch, cookies are not automatically included in the request headers, as we discussed above. For this, you need to explicitly put the cookies in request headers. We will talk about this in a later section.
- Client-Side vs. Server-Side Requests: Authentication mechanisms differ between client-side and server-side requests.
Solutions for Separate Backends
Authorizing user session from Client side in Next.js and sending token with fetch request to receive data in client side is crucial for authorizing the user. But, points we need to consider are that we are not able to send the cookie from Next.js to express backend with fetch requests even after inlcuding the credentials. I will tell you the solution for this below.Handling Client-Side Authentication:
- API Proxies: Create a Next.js API route to act as a proxy for requests to the Express backend.
- Fetch Requests: In client components, include credentials in fetch requests. Use an API proxy route for cookie handling.
- Next.js Route Handlers: Use `cookies()` from `next/headers` to access cookies in route handlers and send the token in headers to the Express backend for verification.
Handling Server-Side Authentication:
When making requests from server-side components, explicitly include cookies in the fetch request headers using functions like `getToken()`.
import { cookies } from "next/headers";
export async function getToken() {
const cookieStore = cookies();
const data = cookieStore.get("jwt");
return data?.value;
}
Best Practices
- Use HTTPS: Always encrypt communication via HTTPS.
- Short Token Lifetimes: Implement short-lived tokens to reduce potential breaches.
- Token Refresh Mechanism: Use refresh tokens for seamless session management.
- Secure Cookie Settings: Set `secure` and `HttpOnly` flags for cookies.
- Regular Security Audits: Perform audits to ensure your system remains secure.
Conclusion
In this article, we covered secure authentication with Next.js and Express.js using HttpOnly cookies, API proxies, and token management for client and server components. By following these strategies, you can build a secure and scalable full-stack application.
Further Reading
Take reference from these articles for more information on this topic: