DEV Community

khg5293
khg5293

Posted on

Your Session Cookie Is Basically a Temporary Password - Part 2

In Part 1, we looked at the basics:

How sessions work, why cookies matter, and why attributes like HttpOnly, Secure, and SameSite are important.

But secure cookie settings are only one part of session security.

A session has a lifecycle.

It is created, authenticated, used, refreshed, expired, and eventually destroyed.

Problems can happen at any point in that lifecycle.

In this post, we'll look at some of the less obvious session security issues developers should understand.

CSRF: When the Browser Helps the Attacker

One of the strange things about cookies is that browsers send them automatically.

That is convenient for users.

It is also exactly why CSRF exists.

Imagine a user is logged into:

bank.example
Enter fullscreen mode Exit fullscreen mode

Their browser has:

Cookie: session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

Now the user visits:

evil.example
Enter fullscreen mode Exit fullscreen mode

That page contains something like:

<form action="https://bank.example/transfer" method="POST">
  <input type="hidden" name="amount" value="500">
  <input type="hidden" name="to" value="attacker">
</form>

<script>
  document.forms[0].submit();
</script>
Enter fullscreen mode Exit fullscreen mode

The attacker does not need to know the victim's session cookie.

The browser may attach it automatically when sending the request to bank.example.

From the server's perspective, the request could arrive with a valid authenticated session.

That is the core idea behind Cross-Site Request Forgery.

The attacker is not stealing the session.

They are abusing the browser's ability to use it.

CSRF Tokens

One common defense is a CSRF token.

The application generates a random value associated with the user's session.

For example:

<form method="POST" action="/settings/email">
  <input
    type="hidden"
    name="csrf_token"
    value="9bf231ce89f5d10a"
  >

  <input
    type="email"
    name="email"
  >

  <button type="submit">
    Change email
  </button>
</form>
Enter fullscreen mode Exit fullscreen mode

The server expects both:

A valid authenticated session

and

A valid CSRF token
Enter fullscreen mode Exit fullscreen mode

If the attacker can cause the browser to submit a request but cannot obtain the correct CSRF token, the server can reject it.

Conceptually:

Session cookie
      +
CSRF token
      ↓
Request accepted
Enter fullscreen mode Exit fullscreen mode

A missing or incorrect token should cause the request to fail.

SameSite cookies can also reduce CSRF risk, but it is useful to understand CSRF as its own problem rather than thinking one cookie flag solves everything.

Session Fixation

Another session attack is less intuitive.

It is called session fixation.

Normally, we think of an attacker trying to steal a session after login.

Session fixation reverses that idea.

The attacker tries to make the victim use a session ID the attacker already knows.

Imagine the attacker knows:

session_id=XYZ123
Enter fullscreen mode Exit fullscreen mode

The victim somehow begins using that session.

Then the victim logs in.

If the application keeps the same session identifier after authentication:

Before login:
session_id=XYZ123

After login:
session_id=XYZ123
Enter fullscreen mode Exit fullscreen mode

the attacker already knows the identifier of the authenticated session.

The attack flow looks like this:

Attacker knows session XYZ123
          ↓
Victim begins using XYZ123
          ↓
Victim logs in
          ↓
Server authenticates XYZ123
          ↓
Attacker still knows XYZ123
Enter fullscreen mode Exit fullscreen mode

The password was never stolen.

The session was never guessed.

The problem was that the session identifier was not changed when authentication happened.

Regenerate the Session ID After Login

The defense is straightforward:

Generate a new session identifier when authentication state changes.

For example:

app.post("/login", async (req, res) => {
  const user = await authenticate(
    req.body.username,
    req.body.password
  );

  if (!user) {
    return res.status(401).send("Invalid credentials");
  }

  req.session.regenerate((error) => {
    if (error) {
      return res.sendStatus(500);
    }

    req.session.userId = user.id;
    req.session.username = "khg5293";

    res.redirect("/dashboard");
  });
});
Enter fullscreen mode Exit fullscreen mode

The important transition is:

Unauthenticated session ID
          ↓
User logs in
          ↓
New session ID
          ↓
Authenticated session
Enter fullscreen mode Exit fullscreen mode

The old identifier should not simply become the authenticated session.

Session rotation can also make sense after important privilege changes.

For example:

User
  ↓
Admin role granted
  ↓
Rotate session ID
Enter fullscreen mode Exit fullscreen mode

A session should not quietly carry sensitive state transitions forever.

Session Expiration

Sessions should not remain valid indefinitely.

If a session ID is stolen today and still works six months later, the attacker gets a very large window of opportunity.

Two common expiration strategies are:

Idle Timeout

The session expires after a period of inactivity.

For example:

Last activity: 10:00 AM

No activity for 30 minutes

Session expires at 10:30 AM
Enter fullscreen mode Exit fullscreen mode

If the user makes another legitimate request before the timeout:

10:00 AM request
10:20 AM request
10:40 AM request
Enter fullscreen mode Exit fullscreen mode

the idle timer may be refreshed.

This protects against sessions that are abandoned but never explicitly logged out.

Absolute Timeout

An absolute timeout limits the total lifetime of the session.

For example:

Login: 9:00 AM

Absolute lifetime: 8 hours

Session expires: 5:00 PM
Enter fullscreen mode Exit fullscreen mode

Even if the user remains active, the session eventually ends.

This creates an upper bound on how long the same authenticated session can exist.

A system may use both:

Idle timeout:
30 minutes

Absolute timeout:
8 hours
Enter fullscreen mode Exit fullscreen mode

The correct values depend on the application.

A low-risk community forum and an online banking system should not necessarily use the same session lifetime.

Remember Me Changes the Risk

Many applications offer a feature like:

Remember me
Enter fullscreen mode Exit fullscreen mode

That can be useful, but developers should understand what it means from a security perspective.

A normal session might last:

30 minutes
Enter fullscreen mode Exit fullscreen mode

while a persistent login might last:

30 days
Enter fullscreen mode Exit fullscreen mode

That dramatically increases the usefulness of a stolen token.

If long-lived authentication is required, it is often better to treat it as a separate mechanism rather than simply making the normal session live forever.

For example:

Short-lived session
       +
Long-lived reauthentication token
Enter fullscreen mode Exit fullscreen mode

The persistent token can be separately rotated, revoked, and monitored.

Longer lifetime should always be treated as increased exposure.

Logout Should Destroy the Session

A common logout implementation is:

res.clearCookie("session_id");
res.redirect("/");
Enter fullscreen mode Exit fullscreen mode

That removes the cookie from the user's browser.

But ask an important question:

What happened to the session on the server?

Possibly nothing.

Imagine the server still contains:

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

The original browser may no longer have the cookie.

But if an attacker already copied the session identifier, that copy might still work.

Logout should normally invalidate the server-side session too.

For example:

app.post("/logout", (req, res) => {
  req.session.destroy((error) => {
    if (error) {
      return res.sendStatus(500);
    }

    res.clearCookie("session_id");

    res.redirect("/");
  });
});
Enter fullscreen mode Exit fullscreen mode

The goal is:

Delete browser cookie
        +
Destroy server session
Enter fullscreen mode Exit fullscreen mode

Both matter.

Password Changes Should Affect Existing Sessions

Another important lifecycle question is:

What happens to existing sessions when the password changes?

Imagine this situation:

Laptop session
Phone session
Tablet session
Unknown attacker session
Enter fullscreen mode Exit fullscreen mode

The user changes their password.

If all existing sessions remain valid, the attacker's session may continue working.

Depending on the application, a password reset or other major account security event may justify invalidating other sessions.

For example:

Password reset
      ↓
Invalidate old sessions
      ↓
Require new authentication
Enter fullscreen mode Exit fullscreen mode

This is especially important after suspected account compromise.

Session Revocation Matters

Expiration answers:

When should this session naturally stop working?
Enter fullscreen mode Exit fullscreen mode

Revocation answers:

How can we make this session stop working right now?
Enter fullscreen mode Exit fullscreen mode

Useful scenarios include:

User clicks "log out everywhere"

Password reset

Account locked

Admin disables account

Suspicious login detected

User removes a trusted device
Enter fullscreen mode Exit fullscreen mode

A server-side session store makes this relatively straightforward.

If the session no longer exists:

session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

the server should reject it.

For example:

HTTP/1.1 401 Unauthorized
Enter fullscreen mode Exit fullscreen mode

A session is only useful while the server still considers it valid.

Don't Put Session IDs in URLs

This is worth stating directly.

Do not design authenticated URLs like:

https://example.com/account?session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

URLs leak into many places.

For example:

Browser history

Server logs

Proxy logs

Analytics systems

Screenshots

Copied links

Bookmarks

Referer headers
Enter fullscreen mode Exit fullscreen mode

A URL is a terrible place for a bearer credential.

Authentication tokens belong in mechanisms designed to transport them safely.

For traditional browser sessions, that generally means cookies.

Avoid Logging Session Tokens

The same idea applies to application logging.

This is useful:

2026-09-15T21:42:11Z
event=login_success
user=khg5293
source_ip=192.0.2.18
Enter fullscreen mode Exit fullscreen mode

This is dangerous:

2026-09-15T21:42:11Z
event=login_success
user=khg5293
session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

Logs are often copied, indexed, retained, exported, and accessed by many systems.

A live session identifier appearing in logs can become a credential leak.

Application logs should avoid exposing secrets unnecessarily.

Keep Cookie Scope Narrow

Cookie attributes can control where a cookie is sent.

For example:

Set-Cookie: session_id=abc123; Path=/
Enter fullscreen mode Exit fullscreen mode

sends the cookie more broadly than:

Set-Cookie: session_id=abc123; Path=/account
Enter fullscreen mode Exit fullscreen mode

The correct scope depends on the application.

The same idea applies to domains.

An overly broad cookie configuration can cause a credential to be sent to places that do not actually need it.

A useful security principle is:

Give credentials the smallest scope they actually require.
Enter fullscreen mode Exit fullscreen mode

That applies to cookies too.

Avoid Unnecessary Session Data in the Browser

In a traditional server-side session architecture, the browser does not need to know everything about the session.

This is usually enough:

Cookie: session_id=a8f19d27c4e91b72
Enter fullscreen mode Exit fullscreen mode

The server can associate that identifier with:

{
  userId: "khg5293",
  role: "user",
  permissions: ["profile:read"],
  authenticated: true
}
Enter fullscreen mode Exit fullscreen mode

There is usually no benefit in exposing sensitive server-side session state to the browser unnecessarily.

Keep the client-side value minimal.

Treat Session Security as a Lifecycle

It is easy to focus entirely on login.

But a secure session has many stages.

Session created
      ↓
User authenticates
      ↓
Session ID rotated
      ↓
Session used
      ↓
Session monitored
      ↓
Session expires or is revoked
      ↓
Session destroyed
Enter fullscreen mode Exit fullscreen mode

Security decisions exist at every step.

If any one step is poorly designed, the session can become easier to abuse.

A More Complete Session Security Checklist

For a traditional web application:

[ ] Generate unpredictable session IDs

[ ] Rotate session IDs after authentication

[ ] Rotate again after major privilege changes where appropriate

[ ] Use HttpOnly

[ ] Use Secure

[ ] Choose an appropriate SameSite policy

[ ] Protect state-changing requests against CSRF

[ ] Implement idle expiration

[ ] Consider an absolute session lifetime

[ ] Invalidate sessions during logout

[ ] Revoke sessions after important account security events

[ ] Support session revocation where appropriate

[ ] Never place session IDs in URLs

[ ] Avoid logging authentication tokens

[ ] Keep cookie scope as narrow as practical

[ ] Avoid exposing unnecessary session state to the browser
Enter fullscreen mode Exit fullscreen mode

Final Thought

Session security is not just about protecting one cookie.

It is about controlling the entire lifecycle of an authenticated identity.

The session has to be created securely.

It has to change when authentication changes.

It has to expire.

It has to be revocable.

And when the user logs out, it should actually stop working.

The safest way to think about a session ID is still the simplest one:

If someone possesses this value,
what can they do?
Enter fullscreen mode Exit fullscreen mode

If the answer is:

Act as the authenticated user
Enter fullscreen mode Exit fullscreen mode

then that session deserves the same care as any other credential.

Because once the password has already done its job, the session becomes the thing standing between the user and everyone else.

Top comments (0)