Cookies and Sessions

By Syncfusion®, Inc. · Last updated

How cookies travel, setting, reading, signing and clearing cookies with cookie-parser, cookie options, sessions with express-session, session stores, login with hashed passwords, protecting page and API routes, logout, and session security.

What you will learn

  • Set, read, sign and clear cookies
  • Choose safe cookie options
  • Store per-visitor data with express-session
  • Build login, protected routes and logout
  • Prevent session fixation and use secure cookies

Cookies and Sessions Introduction

HTTP forgets everything between requests. Each request arrives as a stranger.

  • Cookie = a small name=value note the server asks the browser to keep.
  • The browser sends the cookie back with every later request to that site.
  • Session = data the server keeps about one visitor, found by an ID in a cookie.
  • Cookies hold small, harmless things, like a theme.
  • Sessions hold important things, like who is logged in.

Simple lesson: Cookies let the server recognise a returning visitor.

A Real-World Example

Think about the school library.

  1. On your first visit, the librarian gives you a library card with a number.
  2. You show the card on every visit.
  3. The librarian looks up your number in the register: name, class, books borrowed.
  4. The card only holds a number. The register holds the details.

The card is the session cookie. The register is the session store.

In this lesson, you will learn:

  1. How cookies travel in Set-Cookie and Cookie headers.
  2. How to set, read, sign and clear cookies.
  3. The cookie options that keep users safe.
  4. How sessions work with express-session.
  5. How to log in, protect routes and log out.
  6. How to keep sessions secure.

How Cookies Travel

Animation: the server sends Set-Cookie once, and the browser sends the Cookie header back on every later request

Browser

theme=dark

Express

Set-Cookie: theme=dark

Cookie: theme=dark

Cookie: theme=dark

response 1 request 2 request 3, and every request after

Set once by the server. Sent back by the browser every time.

  • Set-Cookie = a response header. The server says "please keep this".
  • Cookie = a request header. The browser says "here is what you gave me".
  • Session cookie = deleted when the browser closes. It has no expiry date.
  • Persistent cookie = kept until its Max-Age or Expires time.
  • First-party cookie = set by the site you are visiting. This lesson only uses these.

Simple lesson: The server sets a cookie once. The browser sends it back every time.

Setting and Reading Cookies

  • res.cookie(name, value, options) = adds a Set-Cookie header. Built into Express.
  • cookie-parser = middleware that reads the Cookie header into req.cookies.
  • Install it with npm install cookie-parser, then app.use(cookieParser()).
  • A missing cookie is simply undefined.
  • Objects are saved as JSON with a j: prefix. cookie-parser turns them back into objects.
  • The lab's client keeps cookies, like a browser. Press the quick requests in order.
  • Open Headers after /theme/dark to see the Set-Cookie line.
  • Press ↻ Restart to forget all cookies, like a new browser.

Easy way to remember: the server sets, the browser returns. Your code never has to send cookies back by hand.

Simple lesson: res.cookie writes, req.cookies reads.

Cookie Options

  • maxAge: how long to keep it, in milliseconds. Makes it persistent.
  • expires: an exact end date. Use maxAge or expires, not both.
  • path: only send the cookie for URLs under this path. Default /.
  • domain: which domain gets it. Leave it out for "this site only".
  • httpOnly: browser JavaScript cannot read it. Use it for anything important.
  • secure: only sent over HTTPS.
  • sameSite: "strict", "lax" or "none". Controls sending from other sites.
  • signed: adds a signature, so changes can be detected.

Simple lesson: httpOnly, secure and sameSite are your three safety switches.

Signed Cookies

A user can edit their own cookies. A signature shows if they did.

  • Give cookie-parser a secret: cookieParser("my-secret").
  • Set with { signed: true }. The value becomes s:value.signature.
  • Read signed cookies from req.signedCookies, not req.cookies.
  • A changed cookie reads as false.
  • Signed is not encrypted. Anyone can still read the value. Never put secrets in it.
  • The third request sends a forged cookie. The signature does not match, so it reads false.

Simple lesson: Signing stops changes. It does not hide the value.

Clearing Cookies

  • res.clearCookie(name, options) = tells the browser to delete it.
  • Pass the same path and domain you used when setting it.
  • Otherwise the browser treats it as a different cookie, and the old one stays.
  • Express sends the cookie with an expiry date in the past.

Never store passwords, phone numbers or marks in cookies. Users can read them, and attackers may steal them.

Simple lesson: Clear a cookie with the same options you set it with.

Sessions with express-session

Session = server-side data for one visitor, plus a cookie holding only its ID.

  • Session ID = a long random string. It is the "library card number".
  • Session store = where the data lives on the server.
  • Install it: npm install express-session.
  • Read and write data on req.session, like a normal object.
  • Express saves changes at the end of each request.
  • The count grows, because the browser sends the same session cookie back.
  • The last request sends no cookie, so it starts a new session at 1.
  • The cookie only holds the ID. The count lives on the server.

Session Options

  • secret: signs the session cookie. Use a long random value from .env.
  • name: the cookie name. Default connect.sid. A neutral name hides what you use.
  • resave: save even if nothing changed. Usually false.
  • saveUninitialized: save empty new sessions. Usually false, so visitors get no cookie until needed.
  • rolling: reset the expiry on every request.
  • cookie: the cookie options, such as maxAge and secure.
  • proxy: trust the proxy's HTTPS header for secure cookies. See Redirects, Content Negotiation and Proxies.

Simple lesson: Data lives in req.session. The cookie only carries the ID.

Session Stores

  • The default store is MemoryStore: sessions live in the server's memory.
  • MemoryStore loses every session when the server restarts.
  • It also leaks memory and does not work with more than one server.
  • Real apps use an external store, such as Redis (connect-redis) or a database.
  • Pick a store that says it supports express-session, then pass it as store.
import { RedisStore } from "connect-redis";
import { createClient } from "redis";

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
}));

In this lab, MemoryStore is fine, because the lab server only lives a few minutes. Never use it on a real server.

Simple lesson: MemoryStore is for learning. Real servers need Redis or a database.

Logging In and Protecting Routes

A login flow with sessions has five parts:

  1. Check the password against a stored hash, never plain text.
  2. Regenerate the session with req.session.regenerate(). This prevents session fixation.
  3. Store the user ID in req.session.userId.
  4. Protect routes with middleware that checks req.session.userId.
  5. Log out with req.session.destroy() and res.clearCookie().

Pages vs APIs

  • A page route should redirect to /login when the user is not logged in.
  • An API route should answer 401 with JSON. A program cannot follow a login page.
  • Before login: the API gets 401, and the page redirects.
  • A wrong password gets 401 with a vague message. Do not say which part was wrong.
  • After logout, the session is gone on the server too.

Simple lesson: Hash passwords, regenerate on login, check on every protected route, destroy on logout.

Session Security

  • Session fixation: an attacker plants a known session ID before you log in. regenerate() gives you a new ID, so the planted one is useless.
  • Secure cookies: set cookie.secure: true in production, so the ID only travels over HTTPS.
  • Absolute expiry: maxAge ends a session after a fixed time.
  • Inactivity timeout: rolling: true with a short maxAge ends idle sessions.
  • Strong secret: a long random value in .env, never in code.
  • Errors: pass store errors from regenerate, save and destroy to next(err).
  • The first request is plain HTTP, so no Set-Cookie header comes back.
  • The second pretends a proxy handled HTTPS, so the cookie is sent.

Simple lesson: Use secure cookies, and regenerate the session at login.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Reading req.cookies without cookie-parserExpress parses cookiesAdd cookieParser()req.cookies is undefined
Reading a signed cookie from req.cookiesAll cookies are in one placeSigned ones are in req.signedCookiesThe value is missing
Storing the user's role in a plain cookieThe browser keeps it safeUsers can edit cookiesAnyone becomes admin
No regenerate() after loginThe old session is fineGet a new session ID at loginSession fixation attacks
MemoryStore in productionIt works in developmentUse Redis or a databaseLogouts on restart, memory leaks
Storing plain passwordsHashing is optionalStore a salted hash onlyA leak exposes every password
secure: true with plain HTTP and no proxy setupThe cookie is still sentSecure cookies need HTTPSLogins silently never stick

Check Yourself

Exercises

What to Remember

  • Cookies travel in Set-Cookie (out) and Cookie (back in).
  • res.cookie sets them, cookie-parser reads them, res.clearCookie removes them.
  • Use httpOnly, secure and sameSite. Signing stops changes but does not hide values.
  • Sessions keep data on the server. The cookie only carries the session ID.
  • Hash passwords, regenerate on login, protect routes, destroy on logout.
  • MemoryStore is for learning only. Use Redis or a database in production.

Next: File Handling. You will send files, offer downloads and accept uploads.