Skip to content

Intermediate · Authentication and files end to end · topic 22 of 34 · 25 min

Protecting pages and API routes

Explanation

A protected route is a page or API that only logged-in users may use. Redirecting to /login on the frontend makes the app pleasant, but it does not protect anything. The backend must check the session on every request, return 401 when nobody is logged in, and return 404 when someone asks for another user's note.

Two kinds of protection

In the Notes app, /notes must only show to logged-in users. There are two places to check: the frontend (the pages) and the backend (the API). They do different jobs.

The frontend check is for user experience. If you are not logged in, the app sends you to /login instead of showing an empty, broken page. But anyone can call your API directly with a tool like curl, so this check stops nobody.

Think of it like this: A "Staff only" sign on a door helps honest visitors find their way. A lock on the door is what actually stops people. The frontend redirect is the sign; the backend session check is the lock. You need the lock on every door.

The backend check: middleware

Middleware is a function that runs before your route handler. An auth middleware reads the session cookie, finds the session, and puts the user id on the request. If there is no valid session, it stops and answers 401 Unauthorized.

javascript
async function requireUser(req, res, next) {
  const sid = req.cookies.sid;
  const session = sid ? await db.findSession(sid) : null;
  if (!session || session.expiresAt < Date.now()) {
    return res.status(401).json({ error: 'Login required' });
  }
  req.userId = session.userId;
  next();
}

app.get('/api/notes', requireUser, async (req, res) => {
  const notes = await db.all('SELECT * FROM notes WHERE user_id = ?', [req.userId]);
  res.json(notes);
});
Auth middleware in Express (runs on a server with Node.js).

Logged in is not enough: check the owner

Asha is logged in. She changes the URL to /api/notes/42, and note 42 belongs to Ravi. The session check passes, so you also need an ownership check: always filter by the user id from the session, never by an id the browser sends in the body.

sql
SELECT id, title, body, pinned FROM notes
WHERE id = ? AND user_id = ?;
If no row comes back, answer 404: the note is missing or not yours.
SituationStatusWhy
No cookie or expired session401 UnauthorizedWe do not know who you are; please log in
Logged in, note belongs to someone else404 Not Found (or 403)404 hides that the note exists at all
Logged in, but not allowed (e.g. not an admin)403 ForbiddenWe know you, but you may not do this
Logged in, own note200 OKAllowed

Frontend guards, for a smooth experience

javascript
// React (runs in the browser)
function RequireUser({ children }) {
  const { user, loading } = useCurrentUser(); // calls GET /api/me
  if (loading) return <p>Loading...</p>;
  if (!user) return <Navigate to={'/login?next=' + encodeURIComponent(location.pathname)} />;
  return children;
}

// Next.js server component (runs on the server)
export default async function NotesPage() {
  const user = await getCurrentUser(); // reads the session cookie
  if (!user) redirect('/login?next=/notes');
  const notes = await getNotesFor(user.id);
  return <NotesList notes={notes} />;
}
A React route guard and a Next.js server-side page check.
Tip: In Next.js, middleware (renamed proxy.ts from Next.js 16) is good for quick redirects, but do the real check close to the data too: in the server component, route handler or server action that reads the notes. Then a mistake in one place does not open everything.

Sending the user back after login

When the guard sends Asha to /login?next=/notes/5, after login the app should return her to /notes/5. But if you redirect to any value of next, an attacker can send a link like /login?next=https://evil.example, and after a real login your site sends the victim to a fake site. This is called an open redirect.

Careful: Only redirect to relative paths that start with exactly one "/". Reject full URLs, and reject "//evil.example" and "/\\evil.example": browsers treat both as a jump to another website.

Watch the lesson

Protecting pages and API routes · 1/6

Protected routes

Logging in is only half the job. Now we protect pages and API routes.

Prefer a video by a teacher? Find free videos on “protected routes authentication middleware react express tutorial” on YouTube ↗

In real life: A UPI app and someone else's transaction

In a payments app, your phone shows only your own transactions. If the app's server checked only "is this person logged in?", a curious user could change a transaction id in a request and see a stranger's payment. Real payment backends check, on every request, that the transaction belongs to the logged-in account, and answer "not found" otherwise. The Notes app does the same with WHERE id = ? AND user_id = ?.

Practice (0/3 done)

Task 1 · Code · Javascript

Write safeRedirect(next) for the login page. Return next only when it is a string that starts with exactly one "/" and whose second character is not "/" or a backslash. In every other case (missing, empty, full URLs, "//evil.example", "/\\evil.example", "javascript:...") return "/notes".

Task 2 · Code · Javascript

Write requireUser(handler). It returns a new function that takes req. If req.session is missing or has no userId, return { status: 401, body: { error: "Login required" } } without calling handler. Otherwise call handler with a copy of req that also has userId set to req.session.userId, and return what handler returns. Then write getNote(req) for a list called notes (already given in the tests): find the note with id req.params.id that also has userId === req.userId; return { status: 200, body: note } or { status: 404, body: { error: "Note not found" } }.

Task 3 · Practice

Make a protection table for every Notes API route (GET /api/notes, POST /api/notes, GET/PATCH/DELETE /api/notes/:id, POST /api/auth/signup, /login, /logout). For each, write: needs login? ownership check? status codes for failures.

Quick quiz

Question 1

1.Why is a frontend redirect to /login not real protection?

Question 2

2.A request to GET /api/notes has no session cookie. Which status fits?

Question 3

3.Which query safely loads note 42 for the logged-in user?

Question 4

4.Which next value must safeRedirect refuse?

Question 5

5.What does auth middleware usually do when the session is valid?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Signup and login end to end