Get a solid understanding of cookies

There are many chances that if you ask someone about cookies, you will get an "I know cookies" answer. But there are some things related to cookies, that seem to confuse different people. Of course I was confused too. There are many things to explore when it comes to cookies. Hopefully that's a great first look into them.

So, what are cookies?

Cookies are small pieces of data. They are stored in the browser, and they are being sent to the server on each HTTP request. That's by default. It's the default browser behaviour.

Ok, so we create cookies on browsers right?

That's one confusion I have seen many times. That's only one way! There are actually 2 ways to create cookies.

  1. You can create cookies immediately on the browser.
  2. You can create cookies on web server. Your web server basically instructs your browser to create a cookie. This happens on the response of a request via a header. We will see this later.

Very important to know. So cookies are not only being created on browser. You can create them on your backend too. You basically saying from your backend, "hey browser when you get that response, also create this cookie".

Creating cookies on the browser, on the frontend

The syntax is pretty simple actually

document.cookie = "theme=dark"

This JavaScript code create a cookie named "theme" with value "dark", straight into your browser. You can try that line to your Browser console, and then you can go to your Application tab, Cookies section and see the new cookie live there!

Creating cookies on your backend

Of course I am going to use Laravel for this example, since that's my go-to backend, but it really doesn't matter. We want to learn how this works under the hood, and not focus on an implementation of a specific framework.

The idea is that on your response, you also send the "Set-Cookie" header which asks your browser to create a cookie when it receives it.

Let's say that we have a controller, creating a cookie on the response is easy!

public function getTheme(): \Illuminate\Http\Response
{
    return response()->noContent()->withCookie('theme', 'dark');
}

That's using Laravel helper. Which under the hood does something like:

public function getTheme(): \Illuminate\Http\Response
{
    return response()->noContent()->withHeaders([
            'Set-Cookie' => 'theme=dark'
    ]);
}

So now, if you visit the route that's responsible for this controller, you will see that you will have a new cookie on your browser. This time you instructed browser to create one, from your backend!

Note: If the cookie created from your backend is encrypted on your browser, that's because you use EncryptCookies middleware that Laravel provides us. That way, you can set cookies that user won't be able to decrypt their values, because they are encrypted with your app secret key.

Cookies are being sent with every request

....automatically. You don't have to do something about that, people wondering how server receives a cookie and if they need to add some extra code. It happens for you, don't worry. Browsers do this for us.

Important note here. If you are using any library to make HTTP calls from your frontend, this means that you are bypassing default browser's behaviour. So in that case, you may need to configure your client, to send the cookies automatically. Otherwise it may not. As an example on axios library, you can configure a withCredentials property that will do the job.

HttpOnly Cookies

This is one of my favourite. Extremely useful for our modern & secure web applications. These cookies can be created only on the web sever, on the backend. Also HttpOnly cookies, can not be accessed via JavaScript on the browser. But they will be sent normally (browser handles that) on every request like others. That's super useful because since JavaScript can't access them, that means that hackers can't too. If you have an XSS vulnerability on your site, hackers won't be able to fetch these cookies via document.cookie. Extremely useful to handle authentication, storing session IDs or access tokens on these kind of cookies! They are perfect for sensitive data like that (preferable over localStorage probably).

// Controller method
public function getTheme(): \Illuminate\Http\Response
{
    return response()->noContent()->withCookie(
        new Cookie(
            name: 'accessToken',
            value: 'something-secret-here',
            httpOnly: true
        )
    );
}

Cookie Scope

That can be very useful.

You can scope cookies to be available on specific domain(or subdomain) and specific path(s).

Let's suppose that we own a domain, example.com

We can instruct our newly created cookie, to only be available a specific domain/path.

For example, if we have a subdomain app.example.com , we can instruct our cookie to only be available on this subdomain.

document.cookie = "theme=dark; domain=app.example.com"

or via backend

// Controller method
public function getTheme(): \Illuminate\Http\Response
{
    return response()->noContent()->withCookie(
        new Cookie(
            name: 'theme',
            value: 'dark',
            domain: 'app.example.com'
        )
    );
}

So yea, Laravel offers that to us of course.

Now if you visit another subdomain, let's say dashboard.example.com, the cookie won't exist/be sent there, since it's scoped to another domain!

The same logic goes for "path" property. Instead you specify a path starting with "/".

Note: if you have for example multiple subdomains and you want to have a cookie for all the subdomains, you can do this using the following format:

.example.com

Expires, Max-age property

You can control when a cookie will be destroyed using this.

If you don't specify this by default the cookie will be destroyed after the current session, which means after closing the browser. This is called a "session" cookie. Don't confuse the name with Authentication (this can cause some kind of confusion), it's just a name.

If you want to make a cookie to be expired, somewhen on the future, you can do it like this:

document.cookie="theme=dark; max-age=3min"

These are called "permanent" cookies. I know, it doesn't make that sense since they are not totally permanent, but is is what it is I guess. The above one will be destroyed after 3 minutes. Don't let naming confuse you.

Laravel way:

// Controller
public function getTheme(): \Illuminate\Http\Response
{
    return response()->noContent()->withCookie(
        new Cookie(
            name: 'theme',
            value: 'dark',
            expire: now()->addMinutes(3),
        )
    );
}

Same-Site property

I think an example will make this a lot easier to understand. Playing a little with that on your local/testing environment, helps.

Suppose we have a website with "our-site.com" domain. On our website for our domain we have a cookie, that can be whatever, it really does not matter.

Now "their-site.com" website, has a link on their website that links to our site, so something like: "https://our-site.com/whatever".

So you click the link on their website, and you get redirected to our webpage. Let's see how cookies will behave, with different Same-Site property set.

SameSite "Strict"

Cookies won't be sent. "Strict" actually indicates that these cookies are going to be sent only when we visit our webpage, without being redirected from another foreign page. Only if we manually put our website URL to the browser, or if we are already there and we navigate on the website.

  • Ideal for extremely high-security applications like online banking.

SameSite "Lax"

Cookies will be sent. "Lax" means that no matter if we ended to our website from an external link, cookies are just going to be sent normally. That's the default one. Even if you see SameSite empty, it fallbacks to this one.

And syntax, for one more time is pretty straightforward.

document.cookie="theme=dark; SameSite=Lax"

or from backend

// Controller
public function getTheme(): \Illuminate\Http\Response
{
    return response()->noContent()->withCookie(
        new Cookie(
            name: 'bank-session-cookie',
            value: 1,
            sameSite: 'Strict'
        )
    );
}

Some Cookies Jargon

That's not something official. This is clearly based on research and it is something that will help us to remember different things.

  • Session Cookies - they do not have Expires / Max-age set. They are destroyed after closing the browser.
  • Permanent Cookies - they have Expires / Max-age set. It doesn't mean they will live forever, so don't get confused with the naming.
  • HttpOnly Cookies - that's exciting! These are cookies that can only be sent from the backend. JavaScript that runs on the browser, can not access them which means that they offer XSS security. Very useful for authentication. (access tokens, or session ID cookies)
  • Secure Cookies - they will only be sent to websites with SSL encryption (HTTPS).
  • Third party cookies - Cookies that you can see on your website's cookies but they have a different domain. For example: 3rd party ads that you put on your website. These most of the times are used for tracking.
  • Zombie Cookies - Cookies that are being created automatically again with the same value, if they get deleted / expired.

Note: planning to create a different post about zombie and third party cookies , because they seem to be quite interesting! I think they worth their own attention.

Hope you learnt something.

Subscribe to Lioy

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe