Navigating the Crossroads: Mastering Cross-Domain Cookies in Web Applications

Rolique
8 min readFeb 5, 2024

This article is written by Rostyslav Moroziuk and originally published here.

Intro

In the ever-evolving realm of web development, where boundaries between applications blur and user experiences span multiple domains, the ability to set cookies across different origins has become a crucial facet of modern web architecture. Web applications often need to interact with multiple domains to provide a seamless user experience. However, setting cookies across domains can be challenging due to security concerns and browser restrictions.

The essence of cross-domain cookie settings lies in the seamless exchange of information between distinct web apps. Whether it be an e-commerce platform using external payment systems, a single-page application interfacing with multiple micro-frontends, or a federated authentication system, the ability to establish a unified state across diverse domains is paramount.

Yet, as the web matured, so have the mechanisms to safeguard user data and privacy, leading to solid security protocols. While browsers have become more observant in protecting users from potential security issues, developers must adeptly navigate these safeguards to ensure that the exchange of cookies aligns with both security best practices and the user’s expectations of a unified digital experience.

This article will explore the techniques and best practices for setting cookies for another domain in your web application. We will consider my experience in developing a micro-frontend app. Welcome to the realm where cross-domain cookie setting is not just a technical challenge but a gateway to unlocking the full potential of a seamless user experience.

Browser limitations

Before diving into the implementation, it’s crucial to understand the limitations and security considerations associated with cross-domain cookie settings. Here are some key aspects to consider:

  1. Same-Origin Policy (SOP)

The Same-Origin Policy is a fundamental security measure implemented by web browsers. It restricts web pages from requesting a different domain than the one that served the original web page. It prevents a malicious website on the Internet from running JS in a browser to read data from third-party services. This policy also extends to cookie access and settings. For example, http://company.com/page.html and http://company.com/another.html have the same origins, but
http://company.com/page.html and http://company1.com/page.html
have different origins.

  1. Cross-Origin Resource Sharing (CORS)

CORS is a mechanism that allows servers to specify which origins can access their resources. Browsers block cross-origin requests without proper CORS configuration, including those for setting cookies.

Let’s consider useful security flags:‍

  1. SameSite Attribute: The SameSite cookie attribute was introduced to prevent cross-site request forgery (CSRF) attacks. It defines when a cookie should be sent with a cross-origin request. The values can be Strict, Lax, or None. Developers must be cautious with theNonevalue, as it requires the Secure attribute and allows cookies to be sent with any cross-origin request.
  2. Secure Attribute: Cookies marked with the Secureattribute will only be sent over HTTPS connections. When setting cookies for another domain, ensure that the connection is secure to prevent interception of sensitive information.
  3. HttpOnly Attribute: The HttpOnly attribute prevents client-side scripts from accessing the cookie through the document.cookieAPI. This is a security measure to mitigate the risk of cross-site scripting (XSS) attacks. However, it can limit the ability to manipulate cookies using JavaScript.
  4. Path Attribute: The Path attribute specifies the subset of URLs a cookie applies to. Ensure that the Path is set appropriately to restrict the cookie to specific paths within the domain.
  5. Domain Attribute: The Domain attribute must be carefully configured when setting cookies for another domain. This attribute specifies the domain for which the cookie is valid. It is crucial to prevent unauthorized access to cookies by setting a specific domain. Here, we can also use subdomains — e.g., if we set domain=example.com, all our subdomains can access this domain (a.example.com, b.example.com, etc.)
  6. Expiration Time: Setting an appropriate expiration time for cookies is essential. A cookie that persists for an unnecessarily long time may pose security risks. Conversely, a duration that is too short may impact the user experience.
  7. Regulatory Compliance: Be aware of regional and international regulations related to privacy and data protection (e.g., GDPR, CCPA) and ensure that your cross-domain cookie practices comply with these regulations.

My story

Let’s dive into the challenges and requirements that characterize my application. The essence of my project lies in developing a system with different sub-apps, each hosting on separate subdomains. For instance, the authentication app ensures secure access, the order app facilitates seamless product and plan purchases, the account app oversees the management of user accounts, and the editor app empowers users to construct and customize websites.

The overarching objective of my project is to establish seamless communication among all the distinct applications. For instance, when a user initiates the authentication process in the auth app and completes it, it seamlessly redirects the user to the account app. The account app can distinguish whether the user is authenticated and allows users to open pages or handle redirects back to the auth app. From there, users can select and purchase plans in the order app. Successful purchasing triggers a message display in the account app. After successfully creating a website using the editor app, the next crucial step involves passing cookies to create websites hosted on separate domains accessible to our free users.

Utilizing cookies for data exchange between apps is a practical approach. By leveraging cookies, you establish a lightweight and convenient method for passing information seamlessly across different subdomains. This method simplifies the implementation process and can be an efficient solution for managing user authentication tokens, session data, or other relevant information. However, it’s important to prioritize security measures, such as encryption, to safeguard sensitive data transmitted via cookies. This approach aligns with the goal of creating a cohesive and user-friendly experience across your application ecosystem.

Using subdomains

Let’s get down to business and consider a case with using cookies for different subdomains:

Ensure the cookies have a domain attribute, including the primary domain and all relevant subdomains. For instance, if your primary domain is example.com, set the domain attribute of your cookies to “.example.com”. This allows cookies to be accessible across subdomains.

export const setCookie = ({
name,
value,
path = '/',
domain,
expires
}: {
name: string;
value: string | boolean;
path: string;
domain: string;
expires: string;
}): void => {
document.cookie = `${name}=${value};path=${path};domain=${domain}` + (expires ? `;expires=${expires}` : '');
};

setCookie({
name: 'foo',
value: 'bar',
path: '/',
domain: '.example.com'
});

With the appropriate domain configuration, you can successfully share cookies among subdomains like auth.example.com, account.example.com, andeditor.example.com. This enables a unified user experience across your micro-apps.

By default, if you don’t set an expiresvalue for a cookie, it becomes a session cookie. Session cookies persist only for the user’s session on the website. Once the user closes their browser, the session cookie is deleted. Add expiresa field if you need to store cookies longer, for example, 30 days:

const date = new Date();
date.setDate(date.getDate() + 30);
setCookie({
name: 'foo',
value: 'bar',
path: '/',
domain: '.example.com',
expires: date.toUTCString()
});

Note: Consider setting the secure and HttpOnlyflags on your cookies. The secureflag ensures that the cookie is only sent over HTTPS connections, enhancing security. The HttpOnlyflag prevents client-side scripts from accessing the cookie, reducing the risk of cross-site scripting (XSS) attacks.

The following code is responsible for cleaning up our cookies:

export const deleteCookie = (name: string, path = '/'): void => {
setCookie(name, '', path, undefined, 'Thu, 01 Jan 1970 00:00:01 GMT');
};

Technically, we just expire the passed cookie. The browser automatically removes expired cookies, ensuring they are no longer used for subsequent requests.

Different domains

Let’s delve into the most exciting part: how can we set cookies for another domain when you control both domains?

Ensure that you have administrative control over both example.comand another-example.com. This is crucial for implementing the necessary changes and configurations.

Step 1: Configuring CORS. Configure the server on another-example.comto include the appropriate CORS headers, granting permission for example.com, to access resources seamlessly.

Access-Control-Allow-Origin: https://example.co

Step 2: Create a small HTML file (set-cookie.html) that can set appropriate cookies using postMessageAPI.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />

<meta name="viewport" content="width=device-width, initial-scale=1" />

<script>
// Array of allowed domains to receive messages from
const allowedDomains = ['example.com']

// Function to extract the domain from a subdomain
function getDomain(subdomain) {
const domainRegex = /^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)/g;
const match = domainRegex.exec(subdomain);

return match ? match[1].replace(/^.*?\./, '') : null;
}

// Event listener for receiving messages from the main app
window.addEventListener(
'message',
(event) => {
// Extract the domain from the origin of the event
const eventDomain = getDomain(event.origin);
// Check if the event domain is allowed. Required for the security
if (!allowedDomains.find(domain => domain === eventDomain)) return;

const { name, value } = event.data || {};
// Extract the domain from the current window's location.
// We will host this HTML file on another-example.com
// and we will embed it inside our main app as an iframe
const domain = getDomain(window.location.hostname);

// Set a cookie with the received data,
// allowing cross-domain access (*.another-example.com)
document.cookie = `name=${value};path=/;domain=.${domain};SameSite=None;Secure`
},
false,
);
</script>
</head>
<body>
</body>
</html>

SameSite=None means the browser sends the cookie with cross-site and same-site requests. Asecureflag should be set. Any cookie that requests SameSite=Nonebut is not marked Secure will be rejected.

Note: A Securecookie is only sent to the server with an encrypted request over the HTTPS protocol. Note that insecure sites (http:) can’t set cookies with the Secure directive and, therefore, can’t use SameSite=None.

Step 3: You need to host this HTML file on a server or hosting using a domain that you have access to (e.g another-example.com )

Step 4: In the main application, we add a hidden iframe with the URL to the HTML file created in the previous step.

const iframe = document.createElement('iframe')
iframe.setAttribute('src', "https://another-example.com/set-cookie.html")
document.querySelector('body').append(iframe)

Step 5: In this iframe, we pass the necessary value through postMessage, which needs to be added to the cookies.

iframe.contentWindow.postMessage({ name: 'foo', value: 'bar' }, "https://another-example.com");

Congratulations! We are done, and users can now enjoy our app.

Conclusions

Setting cookies for another domain in your web application requires careful consideration of security implications and adherence to browser policies. Understanding and implementing the appropriate techniques can ensure a smooth cross-domain cookie-setting experience for your users. Always prioritize security and follow best practices to maintain a secure and reliable web application.

--

--