Authentication overview
Authentication and authorisation answer two questions: who this visitor is (logging in) and what they may do (permissions). Next.js has no built-in login system. Here is the simplest working option — a cookie session for the blog's only author — plus when you need a ready-made library.
How a session works
- The visitor enters a password in the login form.
- The server checks it, generates a random session ID and remembers whom it belongs to.
- The ID is sent to the browser in a
sessioncookie. - On later requests the server reads the cookie and recognises the user.
The sessions file
Create lib/session.ts. In Next.js 16 the cookies() function from next/headers is used only with await:
// lib/session.ts
import { cookies } from 'next/headers';
const sessions = new Map<string, string>();
export async function createSession(user: string) {
const id = crypto.randomUUID();
sessions.set(id, user);
const cookieStore = await cookies();
cookieStore.set('session', id, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7,
});
}
export async function getUser(): Promise<string | undefined> {
const cookieStore = await cookies();
const id = cookieStore.get('session')?.value;
return id ? sessions.get(id) : undefined;
}httpOnly hides the cookie from JavaScript in the browser, maxAge sets its lifetime — a week. Sessions live in memory and vanish on restart; a real project keeps them in a database table.
Logging in
The author's password is in the ADMIN_PASSWORD variable in .env.local. The login action:
// app/(auth)/login/actions.ts
'use server';
import { redirect } from 'next/navigation';
import { createSession } from '@/lib/session';
export async function login(formData: FormData) {
if (formData.get('password') !== process.env.ADMIN_PASSWORD) {
redirect('/login');
}
await createSession('Anna');
redirect('/posts/new');
}The app/(auth)/login/page.tsx page holds a <form action={login}> with a password field and a button. After the right password the browser gets the cookie and opens the new post form.
Checking permissions
The proxy from the previous lesson only redirects when there is no cookie. The real check happens where data changes — in the server action:
// app/posts/actions.ts (fragment)
const user = await getUser();
if (!user) {
redirect('/login');
}This way nobody can add a post, even by calling the action without the form.
When to use a library
Logging in with Google or GitHub, registering many users, resetting passwords — that is a lot of code where mistakes are easy. For such tasks use Auth.js (the next-auth package) or hosted services such as Clerk. User passwords are stored only as a hash, for example with bcrypt.
Common mistake
Setting a cookie in a page's server component. Next.js throws "Cookies can only be modified in a Server Action or Route Handler": the page is already being sent to the browser, and it is too late to change headers. Write cookies in server actions and route handlers; reading them works anywhere on the server.