Cookies, everything you need
Hi, it’s been a while since the last post, hope you are fine. If this is your first one, hi again I am Mine. Today we will talk about cookies. It will be mainly about Same-Site attribute, but still, this will be a full package post, don’t worry. We came accross with this when there was a bug on my current project. I still haven’t solved it, but I am sure that we will by the end of this post.
So, we are currently developing a web project and we use cookies for authentication. (k hold on, I suggest not to but here we are) Everything is working nicely on Chrome but Safari doesn’t want our cookies! (I wanted to make apple pie joke but couldn’t find a punch line, so this the joke).
Let’s find out the reason and make all cookies delicious together!
What is a Cookie?
A cookie is a small piece of information that websites store on client-side, aka user’s browser, the information it stores travels back and forth between the browser and the website itself, aka server.
Here is in example for Medium, if you open your Web Inspector window and go to Application tab, you will see your current web-base application’s cookies, which ask you to hold them to run smoothly. It can use other types of storage like local or cache storage but we will focus on cookies for now.
Each cookie is a key-value pair along with a number of attributes that control when and where that cookie is used.
For example, the first cookie’s key is _cfruid and its value is an encrypted information. Domain, Path, Expires… and the rest of the columns are attributes of this key.
Cookies are one of the methods available for adding persistent state to websites. But as you can see, we can reach them so easily. If a bad guy wants your cookies, it’s relatively easy to take or change them, and this can make you and your website’s server really sad. Over the years, methods have changed, especially around authentication and authorization. Here is a post that I mention these issues and explain a more secure way to handle them.
To address these vulnerabilities, browsers are changing their behavior to enforce more privacy-preserving defaults. This is the reason why I was facing this bug. Basically, they are not following the same rules and defaults.
How they forwarding through client and server?
Communication between them happens through HTTP headers.
Servers set cookies using the Set-Cookie HTTP response header.
Example response from server:
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
Then, with every following request to the server, the browser sends all previously stored cookies back to the server using the Cookie header.
Here is an example request to server:
GET /page.html HTTP/2.0
Host: www.website.com
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
What is difference between first-party and third-party cookies?
Did you open your Web Inspector window previously? I know you did :), please open it again and check on it, look for other domain than medium.com. (no screenshot this time)
Cookies that match the domain of the current site, which is displayed in the browser’s address bar are referred to as first-party cookies. Similarly, cookies from domains other than the current site are referred to as third-party cookies.
Medium.com server’s sets first-party cookies here, but the page may contain components stored on servers in other domains, such as images or iframes. These components may set third-party cookies.
Third-party cookies mostly uses as advertising and tracking users across the web. Remember the last time you checked for cool sneakers, and started to see them every website you visited? Before you read this, you were paranoid about that, but now you know they just give you a cookie :)
How to prepare the best cookie ever?
Okey we have flour, egg and milk, is it enough for cookie? (ur turn : NOOO!) You right! Where is the chocolate and all other possible cool stuff that makes it the best cookie ever !?!
We call them attributes, and prepare your pen and paper here comes the recipe.
- Expires and Max-Age : Lifetime of the cookie simply. Cookies are deleted at a date specified by the
Expires
attribute or after a period prescribed by theMax-Age
attribute.
Set-Cookie: name=cool_cookie; Expires=Thu, 31 APR 2024 07:28:00 GMT;
If your site authenticates users with cookie, it should regenerate and resend session cookies, even ones that already exist, whenever a user authenticates. This can help prevent session fixation attacks, where a third party can reuse a user’s session. Because cookie is in the user’s browser, do not rely on that for authentication.
- Secure : This means your cookie only sent to the server with an encrypted request over the HTTPS protocol. Insecure sites (with
http:
in the URL) can't set cookies with theSecure
attribute, which means man-in-the-middle attackers can’t access it easily. What they do is intercepts a communication between two systems. Never trust sites without HTTPS encryption on public Wi-Fi networks. - HttpOnly : The
HttpOnly
attribute directs browsers not to expose cookies through channels other than HTTP (and HTTPS) requests. This means that the cookie cannot be accessed via client-side scripting languages, which means it will help you with XSS (cross-site scripting). XSS allows an attacker to inject into a website malicious client-side code.
(Use these both always! u don’t have SSL? go and get one!)
Set-Cookie: name=cool_cookie; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly
- Domain : These two defines what URLs the cookies should be sent to. The
Domain
attribute specifies which server can receive a cookie. The cool part is that if domain specified, then cookies are available on the server and its subdomains, if its not than its not available on subdomains. - Path : The
Path
attribute indicates a URL path that must exist in the requested URL in order to send theCookie
header.
For example, if you set Path=/path_to_go
, these request paths match: /path_to_go
or /path_to_go/api
or /path_to_go/api/user
But these request paths don’t: /
or/path_not_to_go
or /en/path_to_go
(And finally secret ingredient)
- Same-Site : The
SameSite
attribute lets servers specify whether cookies are sent with cross-site requests. Your cookie is restricted either first-party or same-site context. (I hear you saying wtf, don’t worry I got u)
What is site means here? The site is the combination of the domain suffix and the part of the domain just before it. For example, the www.medium.com
domain is part of the medium.com
site.
=> If the user is on www.medium.com
and requests an image from my-blog.medium.com
, that's a same-site request.
=> If the user is on my-blog.medium.com
and requests an image from your-blog.medium.com
that's a cross-site request.
Same-Site Attribute
After we learned the basics, lets dive deeper. As I mentioned, The SameSite
attribute lets servers specify whether cookies are sent with cross-site requests. This provides some protection against cross-site request forgery attacks (CSRF or XSRF). The attacker causes the user’s browser to perform a request to the website’s backend without the user’s consent or knowledge. If we set up a proper cookie with a SameSite attribute, we will be sure that the session cookie is not sent along with cross-site requests and so the request will be invalid for the server.
It takes three possible values: Strict
, Lax
, and None
.
If you set SameSite
to Strict
, your cookie can only be sent in a first-party context, what is on the address bar, medium.com, so only medium.com not my-blog.medium.com. It never allows the cookie to be sent on a cross-site request.
Set-Cookie: name=cool_cookie; SameSite=Strict
If you set SameSite
to Lax
, it allows the cookie to be sent on some cross-site requests. For example, when the browser requests cool_photo.jpg
for the other person's blog, your site doesn't send the cookie. However, when the reader follows the link to photo.html
on your site, that request does include the cookie.
Set-Cookie: name=cool_cookie; SameSite=Lax
You can also set SameSite
to None
to indicate that you want the cookie to be sent in all contexts. If you provide a service that other sites consume such as widgets, embedded content or sign-in across multiple sites, use None
.
Set-Cookie: name=cool_cookie; SameSite=None, Secure
- Cookies without a
SameSite
attribute by default isSameSite=Lax
. - Cookies with
SameSite=None
must also specifySecure
, meaning they require a secure context.
So what I did?
Basically I did what we learn.
Previous code: Same-Site was None but it wasn’t Secure at all
//Cookie Management
builder.Services.AddScoped<CustomCookieAuthenticationEvents, CustomCookieAuthenticationEvents>();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.Cookie.IsEssential = false;
o.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
o.EventsType = typeof(CustomCookieAuthenticationEvents);
o.LoginPath = "/api/Login/login";
o.LogoutPath = "/api/Login/logout";
o.AccessDeniedPath = "/api/Login/logout";
});
Current Code: Chocolate Chip Cookie
//Cookie Management
builder.Services.AddScoped<CustomCookieAuthenticationEvents, CustomCookieAuthenticationEvents>();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.Cookie.IsEssential = true;
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
o.Cookie.HttpOnly = true;
o.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
o.EventsType = typeof(CustomCookieAuthenticationEvents);
o.LoginPath = "/api/Login/login";
o.LogoutPath = "/api/Login/logout";
o.AccessDeniedPath = "/api/Login/logout";
});
Here is cool website to see browsers different behaviors for SameSite : link.
Here is more detailed post only about SameSite : link.
Hope you find it helpful, see you on the next one :)