Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Security

Sessions

What is a session, and where is the logged-in state remembered?

A session is a memory that is written in a store inside the server. In the code box, session is an argument of the type HttpSession, and it points to one slot in the server's session store that belongs to this request. The line session.setAttribute("user", "kim") in the login method writes "the user of this session is kim" in that slot, and the line session.getAttribute("user") in the me method reads that value from the slot. Each slot has a number the server gave it (the session ID), and the server gives only this number to the browser, as a cookie named JSESSIONID. The browser sends that cookie with every following request, and the server finds the slot in the store by the number and knows who it is. In this lab you change how /me is requested after logging in (without the cookie, by another person, after the server deleted the slot) and see whether the server recognizes who it is. The code box shows only the part that runs when login succeeds. The process of checking the username and password is not covered in this lab, so it was left out. What a cookie is follows the earlier "Cookies" lab.

Checked against real execution · Started Spring Boot 4.1.1 (embedded Tomcat 11.0.24) with real component scanning for 8 combinations, sent requests, and checked them (the HttpClient writes the Cookie header itself, and it was not run in a real browser)

Concepts in this lab

Session
A way to store a user's state on the server side and give the client only an identifier (the session ID) that points to that stored state, delivered in a cookie. The actual information is on the server, so the client cannot change its contents (having the session ID stolen is a different problem). When there are several servers, a common setup keeps this state in a shared store such as Redis so that any server that receives a request can find the same state.
Session ID (JSESSIONID)
A session ID is a unique string that a servlet container gives to a session when it creates it, and it is a number that points to one slot in the server's session store. It can be read with getId() of HttpSession, and what kind of number to use is decided by the container (in the Tomcat 11 of this lab it was 32 uppercase letters and digits). When a session is carried over by a cookie, the specification fixes the name of the cookie as JSESSIONID, and when the server gives the number in a Set-Cookie header, the browser sends it in the Cookie header with every following request. The server finds the slot by this number alone, so if another person sends the same number, it is treated as the same session. That is why the number must not be exposed.
HttpSession
HttpSession is a servlet interface that provides a way to identify one user across more than one request and to store information about that user on the server. A Spring controller method can receive it by declaring an argument of the type HttpSession, and if the request has no session, the server creates a new one. setAttribute("user", "kim") writes a value into the slot under a name, and getAttribute("user") reads it, and reading a name that has not been written returns null. When invalidate() is called, the session becomes invalid, the values bound to it are released, and later requests cannot see that session. The values of a session are stored on the server, and only the session ID goes to the browser.
Cookie
A small piece of data that a server sends in a response. The browser stores it and automatically sends it back with every request to the same site. HTTP does not remember state between requests, so this data is used to keep information that must carry over, such as a login state. Because it is stored in the user's browser, you must use it on the assumption that the user can read or change its contents.

Try these

Running with the default settings

  1. When you click the login button on the login page, the browser sends a POST /login request to the server. This request has no Cookie header. The server's session store is empty. The process of checking the username and password is not covered in this lab, so it was left out.
  2. To call the login method, Spring has to fill in the session argument, but this request carries no session ID, so there is no session belonging to the request yet. So the server (Tomcat) creates a new session and gives it a random ID (the ID Tomcat gave was 32 uppercase letters and digits, and here only the first part is written, shortened to 3A9F…). One slot is created in the store, and it is empty. Our code has no line that creates a session and no line that gives it an ID.
  3. The login method runs the line session.setAttribute("user", "kim"). user=kim is written in the slot that was just created. The place where the fact of having logged in is written is not the browser but the session store inside the server.
  4. The login method returns "Logged in," and a Set-Cookie: JSESSIONID=3A9F… line is added to the response. We did not write code that adds this line. Tomcat adds it. The cookie value is only the ID, and the characters kim are not in it.
  5. The browser receives the response and saves the cookie JSESSIONID=3A9F…. What the browser holds is only the ID, and user=kim is only in the server's store.
  6. The browser sends a GET /me request to the server. A cookie is saved, so the browser adds the header Cookie: JSESSIONID=3A9F… to the request. We did not write code that adds this header. The browser does it.
  7. The server reads the ID 3A9F… from the Cookie header of the request and looks in the session store for the slot with the same ID. The slot exists, so it does not create a new one, and passes that slot in as the session argument of the me method.
  8. The me method reads the slot with the line session.getAttribute("user"). user=kim is written in the slot, so it returns "Hello, kim." The server knows kim not because kim is in the cookie, but because kim is written in the slot it found by the ID.
  9. The browser receives a 200 response. The body is "Hello, kim". This response carries no Set-Cookie line. This is because an existing slot was found.

Result of this run

Code

@PostMapping("/login")
public String login(HttpSession session) {
    // Write "the user of this session is kim" in the server's session store
    session.setAttribute("user", "kim");
    return "Logged in";
}

@GetMapping("/me")
public String me(HttpSession session) {
    // Use the session ID in the request to find the slot in the store and read it
    Object user = session.getAttribute("user");
    return user == null ? "Unknown user" : "Hello, " + user;
}

Assumptions of this model

These are the results of running Spring Boot 4.1.1 (Spring Framework 7.0.9, embedded Tomcat 11.0.24) on JDK 21. For all eight combinations, we compiled the same source as the code on the screen (with only the logout method, which is not on the screen, added) in the package app, started a new Java process together with a start class (@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan), and checked. We did not register the controller by hand but let Spring scan for the classes, and we sent the requests over real HTTP after the server was up. We did not put Spring Security, JPA, or H2 on the classpath (because with Security present, requests could be blocked and session handling could differ). The code on the screen leaves out the package and import lines, the class declaration, and the logout method. The logout method runs session.invalidate() and returns "Logged out", and the process of checking the username and password is also left out because this lab does not cover it. What we checked against was the server's session store right after login (the number of sessions in the Tomcat session manager and what is written in each slot), the Set-Cookie of the login response, and the response to /me. "The same browser" is one client that writes the value of the Set-Cookie it received directly into the Cookie header of the next request (we did not use a cookie jar, and we did not run it in a real browser), "another person" is a separate connection (a separate client) that writes the same value into the Cookie header and sends it, and the server deleting the session was reproduced by first sending POST /logout with the same cookie. The IDs on the screen (3A9F…, B71E…) are made-up examples for explanation, and in reality they were 32 uppercase letters and digits that differed every run. What the documents decide is Chapter 7 of the Jakarta Servlet 6.1 specification (7.1.1: the standard name of the session tracking cookie is JSESSIONID, 7.2: a session is new until the client returns the tracking information to the server and joins it, and the id can be read with getId(), 7.5: once invalidation starts, new requests cannot see that session) and the API documentation of HttpSession (a session is a way to identify a user across more than one request and to store information about that user, and the identifier returned by getId is assigned by the servlet container and is implementation dependent). What follows is not decided by the documents but is behavior we observed in this version. The ID is 32 uppercase letters and digits and the cookie value does not contain kim; the cookie carries the attributes Path=/ and HttpOnly; a method that takes a session argument creates a session even if it does not call setAttribute, and a cookie goes out (Spring calls getSession() of the request to fill that argument; we confirmed this call in the bytecode of the Spring class); if /me is requested without a cookie, the server creates a new empty session and gives a new ID; even if the same ID is sent over a separate connection, the server treats it as the same session and does not create a new one (the connection ports of the two requests were different); if a request comes with the ID of a session the server deleted, the server creates a new session and gives a new ID; and the logout response has no Set-Cookie line that tells the browser to delete the cookie. The attack that steals a session ID and the defenses against it (session fixation, the HttpOnly setting, and so on) are not covered in this lab, and are left to the study labs ("Sessions and JWT" and "Session hijacking and session fixation"). We also did not cover the session timeout, how to share sessions among several servers, or methods that do not use cookies (such as putting the ID in the URL).

Previous lab: Authentication and authorization · Next lab: Tokens

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these