Going DeeperPro· 55 min read

Authentication and Protected Routes

Add login to your app with Auth.js (NextAuth) — sign users in, read the session, and block pages from anyone who is not logged in.

What you will learn

  • Explain authentication vs authorization in plain words
  • Set up sign-in and sign-out with Auth.js
  • Protect a page by checking the session and redirecting

Two words people mix up

Almost every real app needs to know who is using it. Two terms come up constantly:

  • Authentication ("authn") = proving who you are. Logging in with Google or a password answers "are you really this person?"
  • Authorization ("authz") = deciding what you are allowed to do. "Is this user an admin who can delete posts?"

You authenticate first (log in), then authorize (check permissions). This lesson focuses on authentication and on locking pages to logged-in users.

Why not build login from scratch?

Doing auth correctly is hard and risky — password hashing, sessions, "log in with Google", security against attacks. So almost everyone uses a trusted library. The standard one for Next.js is Auth.js (formerly NextAuth). It handles the hard, dangerous parts for you.

A minimal Auth.js setup

You create one config file that lists your sign-in providers (Google, GitHub, email/password, etc.). Auth.js gives you ready-made helpers: signIn, signOut, and auth (to read the current session).

auth.js: configure Auth.js with a GitHub sign-in provider
// auth.js
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitHub],   // "Sign in with GitHub"
});

Note: Output (behaviour): This sets up "Sign in with GitHub" for your whole app. handlers powers the login routes, signIn/signOut trigger login/logout, and auth() lets any server code ask "who is logged in right now?"

Sign-in and sign-out buttons

You trigger login and logout with tiny Server Actions that call the helpers Auth.js gave you.

Show a sign-in button, or the user name + sign-out when logged in
// app/components/AuthButtons.js
import { signIn, signOut, auth } from '@/auth';

export default async function AuthButtons() {
  const session = await auth();          // who is logged in?

  if (session) {
    return (
      <form action={async () => { 'use server'; await signOut(); }}>
        <span>Hi, {session.user.name}</span>
        <button>Sign out</button>
      </form>
    );
  }
  return (
    <form action={async () => { 'use server'; await signIn('github'); }}>
      <button>Sign in with GitHub</button>
    </form>
  );
}

Note: Output: - Logged out -> a "Sign in with GitHub" button. - Logged in -> "Hi, Ashish" with a "Sign out" button. auth() read the current session on the server. Clicking sign-in runs the GitHub login flow; clicking sign-out ends the session.

Protecting a page (a protected route)

A protected route is a page only logged-in users may see — like a dashboard. In a Server Component you call auth(); if there is no session, you redirect the visitor to the login page before any private content renders.

A protected page redirects logged-out visitors to /login
// app/dashboard/page.js
import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function Dashboard() {
  const session = await auth();
  if (!session) {
    redirect('/login');     // not logged in -> bounce to login
  }
  return <h1>Welcome back, {session.user.name}!</h1>;
}

Note: Output (behaviour): - A logged-out visitor opening /dashboard -> redirected to /login; the dashboard never renders. - A logged-in visitor -> "Welcome back, Ashish!" The private content is only ever built for someone with a real session.

The whole login-to-dashboard flow, step by step

Here is the full journey for a brand-new visitor who wants the dashboard:

  1. The visitor opens /dashboard while logged out. The page calls auth(), finds no session, and redirect('/login') sends them away — no private content shown.
  2. On the login page they click Sign in with GitHub, which runs the signIn('github') Server Action.
  3. Auth.js sends them to GitHub, where they approve. GitHub sends them back, and Auth.js creates a session (stored safely, usually in a cookie).
  4. Now they open /dashboard again. This time auth() returns their session, so the redirect is skipped.
  5. The page renders "Welcome back, Ashish!" using session.user.name.
  6. For belt-and-braces, middleware (previous lesson) can also redirect logged-out users away from /dashboard before the page even runs.
HelperWhat it does
auth()Read the current session (who is logged in)
signIn(provider)Start the login flow
signOut()End the session (log out)
redirect('/login')Bounce a logged-out user away from a protected page

Tip: Use two layers of protection: middleware to redirect logged-out users early, and an auth() check in the page or Server Action that reads sensitive data. The middleware is convenience; the in-page check is the real lock on the data.

Watch out: Authentication needs secrets (an Auth.js secret and your provider keys). Keep them in .env (never in code, never behind NEXT_PUBLIC_) exactly as the environment-variables lesson showed — leaking them lets others impersonate your app.

Q. In a Server Component, how do you stop a logged-out visitor from seeing a protected dashboard?

Answer: Read the session with auth() on the server. If it is empty, redirect("/login") before rendering, so private content is never built for a logged-out user. Middleware can add an earlier redirect too.

✍️ Practice

  1. Add Auth.js with one provider and render a "Sign in" / "Sign out" button that reflects the session.
  2. Protect a /dashboard page so logged-out visitors are redirected to /login.

🏠 Homework

  1. Add login to your blog with Auth.js: show the user name when signed in, and protect an "/admin" page that redirects logged-out visitors to /login.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →