DEV Community

mahdi
mahdi

Posted on

Understanding Cookies and Sessions in Node.js

Understanding the Foundation: HTTP and Its Statelessness:

Before diving into the intricacies of cookies and sessions, it's crucial to grasp the underlying protocol that governs the web the HyperText Transfer Protocol (HTTP). At its core, HTTP is a set of rules for transferring the myriad of files and data that makeup websites from text and images to videos and more. It's built on the bedrock principles of resources, identifiable by Uniform Resource Identifiers (URIs), and the simple but powerful client server communication model.

However, HTTP was designed to be stateless. This means that each request from your browser to the server is treated as an entirely new interaction, with no memory of previous requests. While this simplicity has benefits, it poses a challenge: How does a server remember who we are from one transaction to the next?

This is where the invention of cookies and sessions comes in. They are like the web's solution for giving a short-term memory to the otherwise forgetful HTTP conversations.

But if we want to say it more colloquially:

Think of HTTP like a conversation with someone who has no long-term memory. You can have a fantastic talk, exchange all sorts of ideas, but the moment you say goodbye and leave, your acquaintance forgets who you are. The next time you meet, you have to introduce yourself all over again. This is where cookies and sessions step in they're like handy notes that remind the server, "Hey, we've met before; here's what we talked about."

understanding cookies

what is cookies?

Cookies are tiny data files that web servers ask web browsers to store on a user’s device. Whenever the browser fetches a page from the server, it sends these cookies back to the server. Through this mechanism, cookies enable the server to “memorize” certain bits of user information, thus supporting a range of things like keeping the user logged in, storing site preferences, and tracking user browsing patterns.

In simpler terms:

Imagine you’re at a large, vibrant celebration, an extravagant masquerade party where everyone is wearing elaborate masks and costumes. As you mingle, you want to remember some details about the people you meet: their masks, their costumes, and perhaps a fun fact they share with you.

Since it’s difficult to remember everyone due to the masks, you have a small notepad. Each time you encounter someone, you jot down a note about their costume and something distinctive about your conversation, like “the person in the owl costume loves star gazing” or “the individual in the red jester hat makes excellent tiramisu.”

You decide you’ll keep these notes just for the night—24 hours at most. Why? Because after the party, you may never see these masqueraders again, and holding onto those notes won’t be necessary.

Now, as the party goes on, you meet some of these folks again. Each time, you peek at your notepad and recall who they are immediately, allowing you to continue the conversation with ease. “How was the stargazing last week?” you might ask the owl, or “Could you share your tiramisu recipe?” you might say to the jester. This personal touch makes the interactions much more meaningful and enjoyable.

As time ticks past and the party draws to a close, so does the relevance of your notes. After the 24 hours are up, you toss the notepad away. The transient yet significant memories are no longer necessary, for the enchanting evening has come to an end.

Similarly, when we browse the web, sites send cookies to our browser so it can remember us for a certain period—like your notepad, they are the memory of our interactions with the site. They ensure that when we return to a webpage, we don’t have to reenter our login details, reset our preferences, or refill our shopping carts. Then, just like discarding your notes when their purpose is fulfilled, the cookies expire or are deleted, making certain our browser doesn’t get bogged down with irrelevant data.

This is how cookies on websites serve as a streamlined and self-cleaning memory system—preserving important details when needed and fading away when their job is done, much like your temporary notes at the masquerade party.

the structure of a cookie

Image description

  1. name: The key or identifier for the cookie. It's the name by which the cookie is referenced.
  2. value: The data stored within the cookie. This is the information the server wants to remember about the user.
  3. expires: The date and time when the cookie will expire. After this, the cookie will automatically be removed.
  4. path: This defines the scope of the cookie within the domain – which URLs the cookie should be sent to.
  5. domain: Specifies which domain the cookie belongs to. It sets the scope of the cookie to the specified domain and its subdomains.
  6. secure: A flag indicating that the cookie should only be sent over secure protocols like HTTPS.
  7. HttpOnly: A flag that helps mitigate the risk of client side script accessing the protected cookie.

Unfortunately, I'm unable to generate and provide images. However, I can provide you with examples of how to set these attributes in a Node.js environment without using any external libraries.

Here's an example that creates a simple cookie with all these attributes:

const http = require('http');

http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/plain',
    'Set-Cookie': [
      'name=example; Max-Age=9000; HttpOnly',
      'preferences=dark; Expires=Wed, 09 Jun 2021 10:18:14 GMT',
      'sessionToken=abc123; Path=/; Secure; HttpOnly',
      'shoppingCart=12345; Domain=example.com;',
      'logged_in=true; Secure; Path=/; Domain=example.com; HttpOnly'
    ]
  });

  res.end('Cookie set!');
}).listen(8080);

console.log('Server started on http://localhost:8080');
Enter fullscreen mode Exit fullscreen mode

Well, let's see how to set a cookie with express js:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  // "name" and "value"
  res.cookie('sessionId', '12345678', {
    // "expires" - The cookie expires in 24 hours
    expires: new Date(Date.now() + 86400000), 
    // "path" - The cookie is accessible for APIs under the '/api' route
    path: '/api', 
    // "domain" - The cookie belongs to the 'example.com' domain
    domain: 'example.com', 
    // "secure" - The cookie will be sent over HTTPS only
    secure: true, 
    // "HttpOnly" - The cookie cannot be accessed by client-side scripts
    httpOnly: true
  });

  // We can also use "maxAge" to specify expiration time in milliseconds
  res.cookie('preferences', 'dark_theme', {
    maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
    httpOnly: true // For security, also set "httpOnly" flag
  });

  res.send('Cookies are set with different attributes.');
});

const server = app.listen(3000, () => {
  console.log('Server running on port 3000...');
});
Enter fullscreen mode Exit fullscreen mode

In this example, two cookies are being set: 'sessionId' and 'preferences'.

Here's what each attribute means in plain language:

  1. sessionId: This is the name of the cookie that will be referenced by the server to recall session data. Its value is '12345678', a unique string that identifies the user's session.

  2. expires: This tells the browser when to delete the cookie. Here, it's set to 24 hours from when the cookie is created.

  3. path: This limits the cookie's scope to a particular path on the domain. Only requests to 'example.com/api' will include this cookie.

  4. domain: By setting 'example.com', we're telling the browser to only send the cookie to servers at that domain, ensuring that cookies are not sent to other sites.

  5. maxAge: Instead of specifying an expiration date, we set a duration in milliseconds. The 'preferences' cookie will last for 30 days after being set.

  6. secure: This flag ensures the cookie is sent securely via HTTPS, which prevents attackers from intercepting it.

Attention :
If you are running your server with nodejs or, for example, with live-server, by activating it, the content of your project will no longer be displayed and you will encounter an error because their server is with http and is vulnerable to attacks.

7.HttpOnly: This flag tells the browser to prevent client-side scripts from accessing this cookie, which can help protect against cross-site scripting attacks.
If we want to give a better example:
Think of the HttpOnly attribute as a special lock on your cookie jar at home. Normally, your family members can reach in and grab cookies anytime they want. But with this lock on the jar, they can only see the cookies when they’re served at the dinner table (which, in this case, is similar to the server’s response).

In the web world, setting the HttpOnly flag on a cookie is like adding that lock to the jar. It means that only the web server can access and read the cookie. It’s as if the cookie jar is invisible and untouchable to anyone who isn’t the server, including any JavaScript code running on the webpage, which could represent nosy family members trying to sneak a cookie.

This makes it much safer because if any sneaky script tries to reach into the cookie jar (a potential security attack), it will find that the jar is locked and can’t grab anything without permission.

By setting these attributes, you have fine control over how cookies are used and secured, ensuring user information is handled appropriately.

Security Concerns with Cookies:

In general, cookies do not pose a security risk, but hackers and cybercriminals can use cookies to impersonate and steal passwords and access user accounts.

Let's look at some of the security risks associated with cookies

Cookies are a fundamental component of web browsing, but their use comes with several security implications. Some of the main security concerns involving cookies are as follows:

  1. Sensitive Data Exposure: Cookies often contain sensitive data, such as session tokens or personal information. If cookies are not secured properly, this data could potentially be exposed to unauthorized parties, leading to privacy breaches or identity theft.

  2. Cross-Site Scripting (XSS): If a cookie doesn't have the HttpOnly attribute set, it can be accessed by JavaScript, which means that if an attacker can inject malicious scripts into your web pages ( e.g., through an XSS vulnerability), they could read and steal cookie data, including session cookies.

  3. Cross-Site Request Forgery (CSRF): Attackers might exploit cookies to perform actions on behalf of a logged-in user without their consent. If an attacker can trick a user's browser into making a request to a site where they are authenticated, cookies attached to that site will be included automatically, potentially leading to unauthorized actions.

  4. Session Hijacking: If an attacker intercepts a user's cookies (especially session cookies), they can impersonate the user on the website. The secure flag helps mitigate this risk by ensuring cookies are only sent over encrypted HTTPS connections, but it's not foolproof, especially if the user can be tricked into using an insecure connection.

  5. Cookie Tossing: This is where an attacker sets a cookie from a subdomain to interfere with cookies from the main domain. This can lead to overwriting legitimate cookies with malicious ones, potentially hijacking user sessions or carrying out other attacks.

  6. Third-Party Cookies: These cookies are set by domains other than the one the user is visiting at the time and can track a user across multiple sites to create a detailed profile of their browsing habits. This is a privacy concern and is being addressed with new regulations and browser features.

So what should we do?

In general, we cannot have 100% security in the virtual world, but we developers can follow these instructions to reduce security concerns:

  • Use the ``Secure'' flag to ensure cookies are only sent over secure HTTPS connections.
  • Use the ``HttpOnly'' flag to prevent JavaScript from accessing sensitive cookies.
  • Set the SameSite attribute to restrict how cookies are sent with cross-site requests.
  • Keep sensitive data out of cookies if possible, and if necessary, make sure it's encrypted and has integrity checks.
  • Clearly set the Domain and Path attributes to restrict where the cookies are used.
  • Use strong session management mechanisms, like regenerating session IDs after login.
  • Consider other storage mechanisms like Secure, HttpOnly, server-side sessions based on user tokens, which can mitigate some of the aforementioned risks.

Overall, when using cookies, it is essential to follow best practices for secure web application development and keep up-to-date with the latest security standards and browser features designed to enhance privacy and security.


I have used the name of sessions several times so far, so what are sessions really? And what do they have to do with cookies? And what is their use?

sessions

what is sessions?

Sessions are like temporary ID cards given to visitors at an office building. When you first walk in, the receptionist doesn't know who you are or why you're there. So, they give you a badge with a number on it. This number doesn't reveal anything personal about you; it's just a way for the office staff to keep track of your visit and assist you with your needs while you're in the building.

When you go to different departments, you show your badge, and the staff there can look up the number and see the notes associated with it—maybe you're allowed in certain areas or you've requested specific assistance. Your activities are remembered as long as you hold onto the badge.

As soon as you leave the building, you return the badge. The office doesn't need to track your visit anymore, so the information attached to that badge number can be discarded or prepared for the next visitor. If you come back another day, you'll get a new badge with a new number.

Similarly, in the web world, a session works like this:

  1. The Beginning: When you first visit a website, the server gives you a unique session ID, which is stored in a cookie on your browser.
  2. During the Visit: With every request you make to the server (clicking links, submitting forms, etc.), your browser sends back that session cookie, reminding the server who you are. The server uses this ID to retrieve your session data from its memory or database.
  3. Storing Information: This session data can store anything from what items you've added to a shopping cart to whether you're logged in.
  4. Session Ends: When you log out, close your browser, or after a period of inactivity, the session ends. Just like returning the ID badge, your unique visit data on the server can be cleared.

The beauty of sessions is that they allow you to have a personalized and continuous experience without the server needing to remember every visitor permanently. They recognize you briefly and provide a way to interact meaningfully with the website during your visit.

How to hold meetings in nodejs

In Node.js, one of the common ways to handle sessions is by using the express-session middleware with session cookies. When a user logs in, a unique session identifier (session ID) is created and sent to the client as a cookie. The server maintains a session store where it maps the session ID to the session data.

Here's a basic example of using express-session in a Node.js Express application:

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'your_secret_key', // A secret key used to sign the session ID cookie
  resave: false, // Forces the session to be saved back to the session store
  saveUninitialized: false, // Forces a session that is "uninitialized" to be saved to the store
  cookie: {
    maxAge: 3600000, // Sets the cookie expiration time in milliseconds (1 hour here)
    httpOnly: true, // Reduces client-side script control over the cookie
    secure: true, // Ensures cookies are only sent over HTTPS
  }
}));

app.get('/', (req, res) => {
  if (req.session.views) {
    req.session.views++;
    res.send(`Number of views: ${req.session.views}`);
  } else {
    req.session.views = 1;
    res.send('Welcome to this page for the first time!');
  }
});

const server = app.listen(3000, () => {
  console.log('Server running on port 3000...');
});
Enter fullscreen mode Exit fullscreen mode

Let's unpack the key parts of this example to make it a little easier for me and you:

  1. Session Middleware: We're using express-session, which helps manage sessions using cookies.
  2. Session Secret: This is a passphrase used to sign the cookie, making it secure. It should be a random string that's kept secret, as it ensures that the session ID is encrypted and not tampered with.
  3. resave & saveUninitialized: These options control when the session gets saved. resave: false means the session won't be stored on every request but only if there was a change. saveUninitialized: false means no session will be saved if it's new and hasn't been modified, which is useful to save resources and also for consent laws in some areas.
  4. Cookie Settings: The maxAge sets the lifespan of the session cookie on the client's browser. httpOnly: true makes the cookie inaccessible to JavaScript running in the browser, enhancing security. secure: true is used to make sure the cookie is sent only over HTTPS, which is essential for protecting the session data as it travels across the networks.

In this code, each time you visit the root '/', the server checks if your session has the views property. If it does, it increments that number. If not, it sets it to 1. So, the server is keeping track of how many times you've visited this page.

Remember that you should use environment variables or other secure means to store the secret key and other sensitive configuration details in a secure and manageable way.

In production, it's common to use a store designed for session persistence, like Redis or MongoDB, to handle large numbers of sessions and to ensure they remain available even if the server restarts. The express-session middleware is compatible with many such stores, making it a versatile choice for session management in Node.js applications.

Data storage and retrieval

Let's continue with the previous example we did with express-session with only a few changes

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'your_secret_key', // Replace with your secret key
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false } // Set to `true` if you are using HTTPS
}));

app.get('/', (req, res) => {
  // Check if the user has a session property called 'views'
  if (req.session.views) {
    req.session.views++;
    res.send(`You visited this page ${req.session.views} times.`);
  } else {
    // If not, initialize the 'views'
    req.session.views = 1;
    res.send('Welcome to this page for the first time!');
  }
});

app.get('/store', (req, res) => {
  // Save some data in the session
  req.session.customData = 'This is saved in session.';
  res.send('Data has been saved in the session.');
});

app.get('/retrieve', (req, res) => {
  // Check if the session data exists before trying to access it
  if (req.session.customData) {
    res.send(`Here's your session data: ${req.session.customData}`);
  } else {
    res.send('No session data found.');
  }
});

const server = app.listen(3000, () => {
  console.log('Server running on port 3000...');
});
Enter fullscreen mode Exit fullscreen mode

In this example:

  • We've set up express-session to handle our sessions. The secret in the session configuration is a key used for signing and/or encrypting cookies. You should replace 'your_secret_key' with an actual secret string.

  • The '/' route handler checks for req.session.views. If it exists, it increments the count, otherwise, it initializes it. This is how data is saved to the session object. This could be any key-value pair.

  • The /store route saves some custom string data under req.session.customData.

  • The /retrieve route attempts to read the data from req.session.customData. If customData does not exist on the session, it means no data has been saved, and it informs the user accordingly.

Remember:

  • In a real application, the secure property of the cookie should be set to true, which will enforce the use of HTTPS.
  • The saveUninitialized: false option would usually be preferred to prevent sending a cookie unless the session object is modified.
  • Always keep your secret key safe and do not hard-code it in your applications; use environment variables or configuration files.

express-session has many options to configure, allowing you to fine-tune how your sessions are managed, including setting up stores for persisting sessions, setting cookie expiration, and much more for production use.

Best Practices:

Managing cookies and sessions securely is crucial for maintaining the integrity and confidentiality of user data as well as ensuring that the application is not vulnerable to common attacks such as session hijacking and cross-site scripting (XSS). Here are some best practices for secure cookie and session management:

  1. Use HTTPS with TLS:

    • Always serve your site over HTTPS and use TLS (Transport Layer Security) to protect cookie data in transit. This prevents session hijacking through man-in-the-middle (MITM) attacks.
    • Use the Secure cookie attribute to ensure cookies are only sent over HTTPS connections.
  2. Set Cookie Attributes Properly:

    • HttpOnly: This attribute should be set to prevent access to cookie data via JavaScript. This helps mitigate the risk of client-side script accessing the protected cookie data and also reduces the impact of potential XSS vulnerabilities.
    • SameSite: This attribute controls whether cookies are sent along with cross-site requests. Specify SameSite=Strict or SameSite=Lax to prevent CSRF (Cross-Site Request Forgery) attacks.
    • Domain: Define which domain the cookie is valid for; limit this to the domain of the application to control cookie scope.
    • Path: Limit the path attribute to restrict where the cookie is sent.
  3. Minimize Sensitive Data in Sessions:

    • Avoid storing sensitive data (like passwords or personal information) directly in the session. If absolutely necessary, ensure the data is encrypted.
    • Use session IDs to reference data stored securely on the server side.
  4. Implement Regular Session Expiration:

    • Set a reasonable timeout for sessions and expire them aggressively to reduce the risk of unauthorized use of stale sessions.
    • Implement sliding session expiration, where session timers reset with each user interaction.
    • When logging out, ensure that the session is destroyed on the server side, and the session cookie is cleared on the client side.
  5. Regenerate Session IDs:

    • Change the session ID after any privilege level change (e.g., logging in). This helps prevent session fixation attacks, where an attacker fixes a user's session ID before the user logs in.
  6. Secure Session Cookies Against Tampering:

    • Sign session cookies with a server-side secret key to detect any tampering by the client or an attacker.
  7. Use CSRF Tokens:

    • Apart from setting the SameSite cookie attribute, use CSRF tokens to protect against cross-site request forgery, alongside ensuring that sensitive actions require POST requests.
  8. Content Security Policy (CSP):

    • Utilize CSP to restrict the sources of content, such as scripts, that can be loaded and run in the browser, to mitigate XSS attacks.
  9. Set Proper Cache-Control Headers:

    • For responses that contain sensitive data, use Cache-Control: no-store to instruct browsers not to cache the response, preventing sensitive data from being stored on the client side.
  10. Monitor and Log Session Activity:

    • Keep track of session generation, invalidation, and usage to monitor for abnormal activities that could indicate a compromised session.

These practices help ensure that your application's session handling is robust against common attack vectors, thereby safeguarding your users' data. It's important to keep abreast of the latest security recommendations and adapt your practices accordingly, as security is a continually evolving field.

Conclusion

  1. Use HTTPS with TLS to secure data in transit, also leveraging the Secure flag on cookies to ensure they're only sent over secure connections.

  2. Properly configure cookie attributes such as HttpOnly, SameSite, Domain, and Path to limit scope and vulnerability to different types of attacks like XSS and CSRF.

  3. Store minimal sensitive data in sessions to reduce exposure. If sensitive data must be stored, use server-side storage with session IDs and ensure any data stored is encrypted.

  4. Implement session expiration with timeouts to reduce the risk of old sessions becoming security vulnerabilities. Ensure that session IDs are regenerated after login and that sessions are completely invalidated on logout.

  5. Protect against CSRF attacks using CSRF tokens in addition to setting cookie attributes.

  6. Employ a Content Security Policy (CSP) to reduce the risk of XSS attacks by controlling the sources from which content is loaded.

  7. Set Cache-Control headers properly to prevent sensitive information from being cached on client-side devices.

  8. Monitor session activity to detect and react to potential security breaches.

References:

  1. Mozilla Developer Network (MDN):
    • MDN's Basics of HTTP: This resource provides essential knowledge about the HTTP protocol, which is the foundation of data communication on the web. MDN is a trusted source for web developers due to its comprehensive documentation on web standards and practices.
  • MDN's HTTP Overview: This provides an overview of the HTTP protocol's main concepts and structure. Understanding this is crucial for learning how browsers communicate with servers, including how sessions and cookies operate within that framework.
  1. Vercel's Understanding Cookies Guide:

    • Vercel's Guide: Vercel, a platform for frontend frameworks and static sites, offers a straightforward guide on how cookies work. It is valuable for developers who need to implement session management and understand the role cookies play in web application security.
  2. Kaspersky's Cookie Definitions:

    • Kaspersky's Cookie Resource: Kaspersky is renowned for its cybersecurity expertise and its articles offer easy-to-understand definitions and explanations that can be helpful for non-technical individuals to grasp the concept and implications of cookies in digital security.
  3. Quadrant Information Security:

    • Quadrantsec's Blog on Cookie Security Issues: Quadrant provides insights into security issues with cookies from a cybersecurity firm's perspective. This can be particularly helpful for understanding the security vulnerabilities associated with cookies and how to mitigate them.

In conclusion, maintaining robust security practices for managing cookies and sessions is paramount for the protection of user data and the integrity of web applications. It is crucial to encrypt data during transit using HTTPS, manage cookies securely by setting appropriate flags and attributes, minimize the amount of sensitive information stored in sessions, handle session expiration diligently, defend against CSRF with tokens, and implement CSP to prevent XSS attacks. Additionally, preventing sensitive data from being cached and monitoring session activity are also important steps in fortifying your application's security.

Regarding the sources provided earlier, they are all reputable and provide valuable information on their respective topics. Should there be inaccuracies or dated information in any of the articles concerning HTTP and cookie management, I encourage community feedback to ensure the information remains current and reliable.

If you have any concerns or wish to discuss the contents of the articles or any other matters related to web security, please feel free to reach out to Mahdi via the following social media and collaboration platforms:

  • GitHub for collaboration and code-related queries: m-mdy-m
  • Twitter for quick updates and direct communication: m_mdy_m
  • Instagram for a more visual and personal connection: medishn

Top comments (0)