2021-01-31 19:00:00 +0000

This article is about cookies and different ways we can implement them in Spring Boot. We are going to have a short overview of what cookies are, how they work, and how we can handle them using the Servlet API and Spring Boot.

If you are building a web application then you probably have reached the point where there’s the need to implement cookies. If you haven’t, you will!

Code Example

This article is accompanied by a working code example on GitHub.

What are Cookies?

Simply put, cookies are nothing but a piece of information that is stored on the client-side (i.e. in the browser). The client sends them to the server with each request and servers can tell the client which cookies to store.

They are commonly used to track the activity of a website, to customize user sessions, and for servers to recognize users between requests. Another scenario is to store a JWT token or the user id in a cookie so that the server can recognize if the user is authenticated with every request.

How Do Cookies Work?

Cookies are sent to the client by the server in an HTTP response and are stored in the client (user’s browser).

The server sets the cookie in the HTTP response header named Set-Cookie. A cookie is made of a key /value pair, plus other optional attributes, which we’ll look at later.

Let’s imagine a scenario where a user logs in. The client sends a request to the server with the user’s credentials. The server authenticates the user, creates a cookie with a user id encoded, and sets it in the response header. The header Set-Cookie in the HTTP response would look like this:

Set-Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t

Once the browser gets the cookie, it can send the cookie back to the server. To do this, the browser adds the cookie to an HTTP request by setting the header named Cookie:

Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t

The server reads the cookie from the request verifies if the user has been authenticated or not, based on the fact if the user-id is valid.

As mentioned, a cookie can have other optional attributes, so let’s explore them.

The attributes Max-Age and/or Expires are used to make a cookie persistent. By default, the browser removes the cookie when the session is closed unless Max-Age and/or Expires are set. These attributes are set like so:

Set-Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t; Max-Age=86400; Expires=Thu, 21-Jan-2021 20:06:48 GMT

This cookie will expire 86400 seconds after being created or when the date and time specified in the Expires is passed.

When both attributes are present in the cookie, Max-Age has precedence over Expires.

Domain is another important attribute of the Cookie. We use it when we want to specify a domain for our cookie:

Set-Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t; Domain=example.com; Max-Age=86400; Expires=Thu, 21-Jan-2021 20:06:48 GMT

By doing this we are telling the client to which domain it should send the cookie. A browser will only send a cookie to servers from that domain.

Setting the domain to “example.com” not only will send the cookie to the “example.com” domain but also its subdomains “foo.example.com” and “bar.example.com”.

If we don’t set the domain explicitly, it will be set only to the domain that created the cookie, but not to its subdomains.

The Path attribute specifies where a cookie will be delivered inside that domain. The client will add the cookie to all requests to URLs that match the given path. This way we narrow down the URLs where the cookie is valid inside the domain.

Let’s consider that the backend sets a cookie for its client when a request to http://example.com/login is executed:

Set-Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t; Domain=example.com; Path=/user/; Max-Age=86400; Expires=Thu, 21-Jan-2021 20:06:48 GMT

Notice that the Path attribute is set to /user/. Now let’s visit two different URLs and see what we have in the request cookies.

When we execute a request to http://example.com/user/, the browser will add the following header in the request:

Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t

As expected, the browser sends the cookie back to the server.

When we try to do another request to http://example.com/contacts/ the browser will not include the Cookie header, because it doesn’t match the Path attribute.

When the path is not set during cookie creation, it defaults to /.

By setting the Path explicitly, the cookie will be delivered to the specified URL and all of its subdirectories.

In cases when we store sensitive information inside the cookie and we want it to be sent only in secure (HTTPS) connections, then the Secure attribute comes to our rescue:

Set-Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t; Domain=example.com; Max-Age=86400; Expires=Thu, 21-Jan-2021 20:06:48 GMT; Secure

By setting Secure, we make sure our cookie is only transmitted over HTTPS, and it will not be sent over unencrypted connections.

HttpOnly is another important attribute of a cookie. It ensures that the cookie is not accessed by the client scripts. It is another form of securing a cookie from being changed by malicious code or XSS attacks.

Set-Cookie: user-id=c2FtLnNtaXRoQGV4YW1wbGUuY29t; Domain=example.com; Max-Age=86400; Expires=Thu, 21-Jan-2021 20:06:48 GMT; Secure; HttpOnly

Not all browsers support the HttpOnly flag. The good news is most of them do, but if it doesn’t, it will ignore the HttpOnly flag even if it is set during cookie creation. Cookies should always be HttpOnly unless the browser doesn’t support it or there is a requirement to expose them to clients’ scripts.

Now that we know what cookies are and how they work let’s check how we can handle them in spring boot.

Handling Cookies with the Servlet API

Now, let’s take a look at how to set cookies on the server-side with the Servlet API.

For creating a cookie with the Servlet API we use the Cookie class which is defined inside the javax.servlet.http package.

The following snippet of code creates a cookie with name user-id and value c2FtLnNtaXRoQGV4YW1wbGUuY29t and sets all the attributes we discussed:

Cookie jwtTokenCookie = new Cookie("user-id", "c2FtLnNtaXRoQGV4YW1wbGUuY29t");

jwtTokenCookie.setMaxAge(86400);
jwtTokenCookie.setSecure(true);
jwtTokenCookie.setHttpOnly(true);
jwtTokenCookie.setPath("/user/");
jwtTokenCookie.setDomain("example.com");

Now that we created the cookie, we will need to send it to the client. To do so, we add the cookie to the response(HttpServletResponse) and we are done. Yes, it is as simple as that:

response.addCookie(jwtTokenCookie);

After adding the cookie to the response header, the server will need to read the cookies sent by the client in every request.

The method HttpServletRequest#getCookies() returns an array of cookies that are sent with the request. We can identify our cookie by the cookie name.

In the following snippet of code, we are iterating through the array, searching by cookie name, and returning the value of the matched cookie:

public Optional<String> readServletCookie(HttpServletRequest request, String name){
  return Arrays.stream(request.getCookies())
    .filter(cookie->name.equals(cookie.getName()))
    .map(Cookie::getValue)
    .findAny();
}

To delete a cookie we will need to create another instance of the Cookie with the same name and maxAge 0 and add it again to the response as below:

Cookie deleteServletCookie = new Cookie("user-id", null);
deleteServletCookie.setMaxAge(0);
response.addCookie(deleteServletCookie);

Going back to our use case where we save the JWT token inside the cookie, we would need to delete the cookie when the user logs out. Keeping the cookie alive after the user logs out can seriously compromise the security.

Handling Cookies with Spring

Now that we know how to handle a cookie using the Servlet API, let’s check how we can do the same using the Spring Framework.

In this section, we will create a cookie with the same properties that we did using the Servlet API.

We will use the class ResponseCookie for the cookie and ResponseEntity for setting the cookie in the response. They are both defined inside org.springframework.http package.

ResponseCookie has a static method from(final String name, final String value) which returns a ResponseCookieBuilder initialized with the name and value of the cookie.

We can add all the properties that we need and use the method build() of the builder to create the ResponseCookie:

 ResponseCookie springCookie = ResponseCookie.from("user-id", "c2FtLnNtaXRoQGV4YW1wbGUuY29t")
    .httpOnly(true)
    .secure(true)
    .path("/")
    .maxAge(60)
    .domain("example.com")
    .build();

After creating the cookie, we add it to the header of the response like this:

 ResponseEntity
    .ok()
    .header(HttpHeaders.SET_COOKIE, springCookie.toString())
    .build();

Spring Framework provides the @CookieValue annotation to read any cookie by specifying the name without needing to iterate over all the cookies fetched from the request.

@CookieValue is used in a controller method and maps the value of a cookie to a method parameter:

@GetMapping("/read-spring-cookie")
public String readCookie(
    @CookieValue(name = "user-id", defaultValue = "default-user-id") String userId) {
    return userId;
}

In cases where the cookie with the name “user-id” does not exist, the controller will return the default value defined with defaultValue = "default-user-id". If we do not set the default value and Spring fails to find the cookie in the request then it will throw java.lang.IllegalStateException exception.

To delete a cookie, we will need to create the cookie with the same name and maxAge to 0 and set it to the response header:

 ResponseCookie deleteSpringCookie = ResponseCookie
        .from("user-id", null)
        .build();

 ResponseEntity
        .ok()
        .header(HttpHeaders.SET_COOKIE, deleteSpringCookie.toString())
        .build();

Conclusion

In this article, we looked at what cookies are and how they work.

All in all, cookies are simple text strings that carry some information and are identified with a name.

We checked some of the optional attributes that we can add to cookies to make them behave a certain way. We saw that we can make them persistent with Max-Age and Expires, narrow down their scope with Domain and Path, have them transmitted only over HTTPS with Secure, and hide them from client scripts with HttpOnly.

Finally, we looked into two ways of handling cookies using the Servlet API and Spring. Both of these APIs offer the required methods for creating (with attributes), reading, and deleting cookies.

They are easy to implement and developers can choose either of them to implement cookies.

You can play around with the example code of this article on GitHub.

Get My Book for just 50% Off!

Get Your Hands Dirty on Clean Architecture

Liked this article? Subscribe to my mailing list to get notified about new content and get my eBook "Get Your Hands Dirty on Clean Architecture" for just $7.50!