This episode covers authentication patterns in Next.js, integrating NextAuth.js or Auth.js, protecting routes with middleware and session management, and role-based access control and secure redirects for production applications.

Every application that stores user data needs answers to two questions: who is using the application, and what are they allowed to do. These two things are authentication and authorization.
Episode 12 covers authentication patterns in Next.js, integrating Auth.js (NextAuth.js), protecting routes with middleware and session management, and role-based access control and secure redirects.
There are several common authentication patterns: session-based, which stores session data in a cookie, JWT, which signs claims in a token, and OAuth, which delegates login to providers like Google or GitHub. The choice depends on the type of application: server-rendered apps suit sessions and cookies, while API-only apps use JWT.
Auth.js (formerly NextAuth.js) is the de facto standard authentication library for Next.js: it supports many OAuth providers, credentials, database sessions, and tight integration with the App Router. Start by installing and configuring the route handler:
npm install next-auth@betaThe beta version is Auth.js v5, designed for the App Router. After installing, create the following route handler:
import { handlers } from "@/auth"
export const { GET, POST } = handlersThe file app/api/auth/[...nextauth]/route.ts above exports GET and POST handlers that manage the entire login and callback flow.
The center of Auth.js configuration is the auth.ts file at the project root:
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Google],
session: { strategy: "jwt" },
})providers: [Google] enables login with a Google account. The session: { strategy: "jwt" } option stores the session in a JWT — lightweight and suitable without a database.
To read the login status in a server component, call the auth() function:
import { auth } from "@/auth"
export default async function Dashboard() {
const session = await auth()
if (!session) {
return <p>Silakan login terlebih dahulu.</p>
}
return <h1>Selamat datang, {session.user.name}</h1>
}session.user.name displays the logged-in user's name. Checking session at the top of a server component is the basic pattern for protecting pages at the rendering level.
To protect many routes at once, use middleware.ts. Middleware runs before the request reaches the page, so it can redirect users who aren't logged in:
import { auth } from "@/auth"
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname.startsWith("/dashboard")) {
const url = req.nextUrl.clone()
url.pathname = "/login"
return Response.redirect(url)
}
})
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
}The middleware above redirects unauthenticated users away from /dashboard and /admin to /login. The redirect happens server-side before the page renders, preventing protected content from being sent.
Authorization is the layer on top of authentication: once you know who the user is, decide what they may access. Check the role from the session:
const session = await auth()
const role = session?.user?.role
if (role !== "admin") {
return <p>Anda tidak memiliki akses.</p>
}The role check in the server component above restricts access to the admin page. For large applications, consider libraries like Permit or Casper for centralized access policies.
Avoid open redirects: never copy the destination URL directly from a query parameter without validation. Redirect only to allowed paths. After a successful login, direct users to the page they originally requested, and after logout return them to a public page — all redirect targets must come from a safe list.
Here's what to take away:
In the next episode, episode 13, we'll discuss API security and data protection — securing route handlers and request validation, CORS and CSRF with secure headers, handling sensitive data and secrets, and rate limiting and abuse prevention. Your application's backend will be fortified.