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.
- On your first visit, the librarian gives you a library card with a number.
- You show the card on every visit.
- The librarian looks up your number in the register: name, class, books borrowed.
- 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:
- How cookies travel in
Set-CookieandCookieheaders. - How to set, read, sign and clear cookies.
- The cookie options that keep users safe.
- How sessions work with express-session.
- How to log in, protect routes and log out.
- 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-AgeorExpirestime. - 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-Cookieheader. Built into Express. - cookie-parser = middleware that reads the
Cookieheader intoreq.cookies. - Install it with
npm install cookie-parser, thenapp.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/darkto see theSet-Cookieline. - 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
maxAgeorexpires, 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 becomess: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
pathanddomainyou 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
maxAgeandsecure. - proxy: trust the proxy's HTTPS header for
securecookies. 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:
- Check the password against a stored hash, never plain text.
- Regenerate the session with
req.session.regenerate(). This prevents session fixation. - Store the user ID in
req.session.userId. - Protect routes with middleware that checks
req.session.userId. - Log out with
req.session.destroy()andres.clearCookie().
Pages vs APIs
- A page route should redirect to
/loginwhen 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: truein production, so the ID only travels over HTTPS. - Absolute expiry:
maxAgeends a session after a fixed time. - Inactivity timeout:
rolling: truewith a shortmaxAgeends idle sessions. - Strong secret: a long random value in
.env, never in code. - Errors: pass store errors from
regenerate,saveanddestroytonext(err).
- The first request is plain HTTP, so no
Set-Cookieheader 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
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Reading req.cookies without cookie-parser | Express parses cookies | Add cookieParser() | req.cookies is undefined |
Reading a signed cookie from req.cookies | All cookies are in one place | Signed ones are in req.signedCookies | The value is missing |
| Storing the user's role in a plain cookie | The browser keeps it safe | Users can edit cookies | Anyone becomes admin |
No regenerate() after login | The old session is fine | Get a new session ID at login | Session fixation attacks |
| MemoryStore in production | It works in development | Use Redis or a database | Logouts on restart, memory leaks |
| Storing plain passwords | Hashing is optional | Store a salted hash only | A leak exposes every password |
secure: true with plain HTTP and no proxy setup | The cookie is still sent | Secure cookies need HTTPS | Logins silently never stick |
Check Yourself
Exercises
What to Remember
- Cookies travel in
Set-Cookie(out) andCookie(back in). res.cookiesets them, cookie-parser reads them,res.clearCookieremoves them.- Use
httpOnly,secureandsameSite. 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.