Cookies: The Indispensable Memory of Web Applications
Hello! Previously we talked about HTTP’s stateless nature. So how do websites “recognize” us, keep us logged in, and preserve our shopping carts despite this memoryless protocol? The answer often lies in small text files: cookies. Today we’ll dive deep—from how they’re created to security risks and modern alternatives. Ready? Let’s open the cookie jar!
Structure of HTTP Cookies: Set-Cookie Header and Parameters
A cookie’s life begins with an HTTP response header from the server: Set-Cookie
. When the browser sees this header, it stores the cookie according to the directives and sends it back on future matching requests via the Cookie
header.
A typical Set-Cookie header might look like this:
Let’s analyze the directives in detail:
- Name=Value: The main payload.
sessionId=a3fWa
shows the cookie’s name (sessionId) and value (a3fWa). Used by the server to identify the user or their state. - Expires=Date: Expiration date. After this, the browser deletes the cookie. If neither Expires nor Max-Age is set, it’s a session cookie and is deleted when the browser session closes.
- Max-Age=Seconds: How many seconds the cookie lives. More modern than Expires and preferred if both are set. Negative deletes immediately. Example:
Max-Age=3600
→ 1 hour. - Domain=domain.name: Which domain(s) the cookie will be sent to. If omitted, only the originating host. A value starting with
.
(e.g.,.example.com
) covers the main domain and all subdomains. - Path=path: Limits which URL paths receive the cookie.
/
for all paths;/app
for/app
and its subpaths. - 🛡️ Secure: Only send the cookie over HTTPS. Prevents sending over HTTP, reducing MITM risk. Must be used!
- 🛡️ HttpOnly: Prevents access via JavaScript (
document.cookie
). Critical to protect session cookies from XSS theft. - 🛡️ SameSite=Strict | Lax | None: Helps mitigate CSRF by controlling whether to send cookies with cross-site requests:
- Strict: Only same-site requests. Not sent even when navigating via external links. Safest, may impact UX.
- Lax: Default in many modern browsers. Sent with same-site requests and top-level cross-site navigations (GET via link). Not sent with cross-site POST/PUT/DELETE or in
<iframe>
,<img>
. Good balance. - None: Sent with all cross-site requests, but requires Secure. Used mainly in third-party contexts.
Practical Use Cases: Session Management and UX
Cookies primarily compensate for HTTP’s statelessness:
- Session Management:
- On login, the server creates a unique session identifier.
- Sends it in
Set-Cookie
(usually with HttpOnly and Secure). - The browser stores it and sends it back in subsequent requests (
Cookie: sessionId=...
). - The server uses the session ID to fetch session data (who is logged in, permissions) from server-side storage and “recognizes” the user.
- Result: The user doesn’t need to log in on every page—foundation of a stateful experience.
- User Experience and Personalization:
- Preferences (language, theme), recently viewed items.
- Shopping cart for even non-logged-in users (though more complex solutions often use backend sessions or
localStorage
). - Tracking & Analytics (e.g., Google Analytics), often via third-party cookies—hence privacy concerns.
JavaScript Cookie Operations (document.cookie
)
If a cookie isn’t HttpOnly, it can be accessed via client-side JavaScript:
⚠️ Note: The document.cookie
API is clunky. It returns a single string you must parse, and updating requires re-specifying all attributes. Most importantly, if your site has an XSS vulnerability, all non-HttpOnly cookies (including those set via JS) can be stolen! Use HttpOnly to keep sensitive data (session IDs, tokens) inaccessible to JS.
Backend Integration: Working with Cookies in Spring Boot
On the backend (e.g., Java Spring Boot), working with cookies is more controlled and secure.
-
Reading Cookies:
@CookieValue
annotation: Easiest approach. Spring injects the cookie value into a method parameter.
controller/CookieController.java HttpServletRequest
: To get all cookies as an array.
controller/CookieControllerReadAll.java -
Writing/Setting Cookies:
HttpServletResponse.addCookie()
(Traditional): Create ajakarta.servlet.http.Cookie
and add it to the response.
controller/CookieControllerSetTheme.java ResponseCookie
(Spring 5+ — Modern): Fluent builder to set modern attributes (especially SameSite) easily. Used withHttpHeaders.SET_COOKIE
.
controller/CookieControllerModern.java ⚙️ Using
ResponseCookie
helps write clearer code and ensures all essential security attributes are considered.
On the Security Radar: Risks and Defenses
Cookies can be as dangerous as they are useful. Key risks and mitigations:
- XSS (Cross-Site Scripting):
- Risk: If your site has an XSS vulnerability, an attacker can inject JS in the user’s browser and exfiltrate any non-HttpOnly cookies (e.g., session ID) via
document.cookie
, leading to session hijacking. - 🛡️ Defense: Enable HttpOnly — mandatory. Also implement input validation and output encoding.
- Risk: If your site has an XSS vulnerability, an attacker can inject JS in the user’s browser and exfiltrate any non-HttpOnly cookies (e.g., session ID) via
- CSRF (Cross-Site Request Forgery):
- Risk: Browsers automatically attach cookies on requests to the same domain. Attackers can trick users into sending requests from another site (e.g.,
evil.com
) to a site they’re logged into (e.g.,bank.com
). - 🛡️ Defense: Set SameSite to Lax (default) or Strict to limit cross-site cookie sending. Additionally, use anti-CSRF tokens for state-changing requests (POST/PUT/DELETE).
- Risk: Browsers automatically attach cookies on requests to the same domain. Attackers can trick users into sending requests from another site (e.g.,
- Session Hijacking / Fixation:
- Risk: Cookies can be sniffed (without HTTPS), stolen via XSS, or a fixed session ID can be forced on a user.
- 🛡️ Defense: Always use HTTPS (with Secure). Use HttpOnly. Regenerate a new session ID immediately after login and invalidate the old one.
Browser Arena: Third-Party Cookie Bans
Cookies by context:
- First-party cookie: Set by the domain the user is visiting (e.g.,
example.com
while onexample.com
). - Third-party cookie: Set by a different domain embedded in the page (ads, social widgets, analytics).
⚠️ Trend: Due to privacy concerns (especially cross-site tracking), most modern browsers (Safari, Firefox, and gradually Chrome) are blocking or restricting third-party cookies. This impacts ad/analytics ecosystems and accelerates alternatives (e.g., Google Privacy Sandbox).
Alternatives to Cookies: Modern Approaches
- JWT (JSON Web Tokens):
- Popular for stateless authentication. On login, the server issues a signed token to the client.
- The client stores it (often in
localStorage
orsessionStorage
) and sends it inAuthorization: Bearer <token>
for protected requests. - No server-side session store required.
- Comparison: More resilient to CSRF than cookies (not sent automatically), but vulnerable to XSS if stored in localStorage.
- Web Storage API (localStorage/sessionStorage):
- Key-value storage in the browser (more space than cookies, typically 5–10MB).
localStorage
persists across browser restarts;sessionStorage
persists for the life of the tab/window.- Comparison: Unlike cookies, not sent automatically to the server; must be read and added to requests via JS. Also XSS-exposed. Not recommended for sensitive session data; better for UI state, preferences, and offline data.