Let’s taste some Cookies
Strictly for devs — localhost-ing
Most of us know what a Web Cookie is. But just in case, if you do not know, a simple google search can bring it to the light.
This post covers mostly the questions that a dev might have while actually implementing a cookie, rather than reading them in theory.
Contents
- Cookie table
- Who can set the Cookie ?
- Default fields
- Can a Cookie be accessed in a subdomain or vice versa ?
- How to delete a Cookie ?
- Where can a Cookie be used ?
- How to make a Cookie unreadable in JavaScript ?
- Sub domain Set-Cookie
- Code changes
- With no HttpOnly field
- With HttpOnly field - Cross domain Set-Cookie
- Why do they say that a Cookie is unsecure ?
- How can we secure the Cookie ?
- Issues and remedies
- Final take
This post is about exploring all the fields with a simple JavaScript example.
Cookie table
The cookie table has multiple fields. Here is a Sample JavaScript application running in local,
Who can set the cookie ?
- The Cookie can be set in the JavaScript client code using,
document.cookie = 'username=hello world'
- A cookie can be also set from the server response in the Set-Cookie header,
Default fields
As we can, there are four fields that are defaulted say,
- Domain
This specifies that only the host localhost can read the Cookie. If trying to access from any other domain 127.0.0.1 it will not show up the cookie as string localhost != 127.0.0.1
- Path
Cookies are only visible in the path set. It defaults to the current path in the url.
- Expiry/Max-age
By default set to Session(mostly when the browser is closed but varies based on the broswer and user settings). When an explicit expiry or a max-age is set the cookie is removed from the cookie table. - Priority
Priority is set to medium.
Can a Cookie be accessed in a subdomain or vice versa ?
Nope by default. However it can be achieved when the Domain is set explicitly as in the following,
document.cookie = 'username=suriya;domain=localhost.com'
Well, this works ok when the the Cookie is set in the client, but how about when the Cookie is set from the server and send back to the client in the response ?. Let’s check it out in Set-Cookie within sub-domain and cross-domain sections.
How to delete a Cookie ?
Setting the expired expiry date to the Cookie key makes the lifetime out of bound, removing the Cookie from the cookie table.
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
Where can a Cookie be used ?
It remembers stateful information for the stateless HTTP protocol. Cookies are mainly for three purpose,
- Session management
- Personalization
- Tracking
Can multiple tabs running same domain update the same Cookie key ?
Different tab/window in the same browser— Yes. The Cookie is based on domain field. If different tabs are running the same domain then each tab can update the cookie value by its key.
How to make a cookie unreadable in JavaScript ?
Cookies, that are created via client code is always readable by the client code.
However, the Cookie that’s created by a server response header Set-Cookie with HttpOnly field makes a Cookie unreadable to JavaScript like a magic.
Sub-domain Set-Cookie
Let’s to demonstrate this case,
- run the server at server.localhost.com
- run the client at client.localhost.com
here, the server and client are subdomains of localhost.com represented as .localhost.com in the Cookie table
Code changes
To have it working, we need to update our Client code http.withCredentials = true,
const http = new XMLHttpRequest();
const url='https://server.localhost.com:3000/server';
http.open("GET", url);
http.withCredentials = true;
http.send();
Also, we need to set our Server code with,
- Accept the request from the origins
- Allow access to the credentials
res.setHeader('Access-Control-Allow-Origin', 'https://client.localhost.com:5500');
res.setHeader('Access-Control-Allow-Credentials', 'true');
This requires the connection to be secure meaning, the server application need to be on https.
res.setHeader('Set-Cookie', 'where=server;domain=localhost.com;Secure;expires='+ getUtcTimeInSecondsFromNow(60));
Check this post to generate a Self-singed certificate,
Note The client application does not need to be on https. However, for this demonstration purpose to mimic the production environment, running the client on https as well.
With no HttpOnly field
The following screenshot shows, the sub-sequent http call to the same domain does not have the cookie value set when the HttpOnly is missing.
With HttpOnly field
The following is with the HttpOnly field set in the response,
The above makes it unreadable for the JavaScript code even though it shows up in the Cookie table.
When the Read Stored value button is clicked to read it, it displays nothing,
Also only the cookie that’s HttpOnly is only sent back to the server,
Cross-domain Set-Cookie
The cross domain cookies that are set by the server response do not show up in the Cookie table.
But, they are sent to the same domain in the subsequent request for furthur server validation, provided the response has following fields,
- HttpOnly
- Secure
- SameSite=None, check browser compatibility here.
and the following field removed,
And trying it in a non incognito window or else it might ask to change the user preference.
For demonstration purpose we can,
- run the client at clienthost.net
- run the server at server.localhost.com
Why do they say that Cookie is unsecure ?
Cookies are prone to XSS and CSRF attacks.
How can we secure the Cookie ?
To tackle from the attacks and to avoid information loss, we need to be aware of the following,
- Trying to avoid cross domain and have a root domain handle the requests thru sub-domain. This could be possibly avoided once we start using cookies with chips.
- Use path and restrict access to client where necessary.
- Use Secure field to avoid man in the middle as it the traffic would be secured on http.
- Set an expiry or max-age for the cookie where a sensitive information can be removed at a certain interval.
- Use SameSite to Strict to not allow cross domain if possible
- Use httpOnly flag to avoid JavaScript code accessing the cookie where required.
Issues and remedies
CORS domain access
Error Access to XMLHttpRequest at ‘https://server.localhost.com:3000/server' from origin ‘https://clienthost.net:5500' has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
Add the following response header in the ,
res.setHeader('Access-Control-Allow-Origin', 'https://clienthost.net:5500');
Incorrect domain set in the Set-Cookie response
Warning This attempt to set a cookie via a Set-Cookie header was blocked because its Domain attribute was invalid with regards to the current host url.
Check the following,
- Domain in the url is same as that of the request url in case of same root domain
- See if the SameSite=None flag in case of cross-domain and avoid passing the domain field in the response
- Check if the right domain name in the url is passed in Set-Cookie header
Blocked due to user preference
Warning This attempt to set a cookie via a Set-Cookie header was blocked due to user preference.
Try using a non-incognito window or change the settings of the user preferences.
Final take
Cookies are a useful tool for storing information about a user’s browsing activity and preferences. They can be used to personalize a user’s experience on a website and to track a user’s browsing activity across multiple websites.
However, it is important to use cookies responsibly and to respect user’s privacy. This means being transparent about how cookies are used and giving users the ability to control how their data is collected and used. It is also important to consider the security implications of storing sensitive information in cookies.
In general, it is a good idea to use cookies only when they are necessary and to minimize the amount of personal data that is stored in them. It is also a good idea to give users the option to opt out of cookie tracking, and to delete or block cookies when they are no longer needed.
The fields that we missed to cover are,
Which we will cover in another localhost-ing post !