Alternatives to Cookies for Third-Party UI Integrations

Browsers are making the next jump to protect user privacy by treating iframed resources as third-party requests. These policy changes will break third-party Salesforce integrations that rely on iframes. In this post, I’ll share how Salesforce has already modified its use of iframes. I’ll also share the alternatives you can use in your applications to replace cookies.

About cookies

Cookies have always gotten a lot of attention from browsers and users as the web evolved. Cookies are a great tool for applications that need to authenticate web requests. An application can respond with a Set-Cookie header, and every request to that application’s domain will include the user’s session cookie. But cookies are a tool for trackers as well, helping advertisers spy on users’ every move across the web.

More than a decade ago, browsers made the first policy change and stopped allowing origins to set cookies into third-party domains. Browsers blocked third-party domains from setting new cookies into domains that aren’t theirs. But up until recently, iframes and included resources have allowed trackers to work around blocked cookies, so long as that cookie was set with SameSite=None, which was the default behavior in years past. The document in the iframe was treated as its own first party, and could both set and read cookies.

This latest policy change has different names (and unique behaviors) in each browser. Google Chrome even has a different behavior in Incognito mode! The best resources for learning the specifics are the following blog posts from each browser on their own policies.

All of these changes help everyone understand how they are tracked throughout the web, and are aligned with governmental regulations like GDPR. Experimental APIs like the Storage Access API allow users to give intentional consent when websites need to store tracking data on their browser. With these policies having already rolled out or rolling out soon, the message is clear: don’t rely completely on cookies and other web storage to authenticate or track users.

How Salesforce uses multiple domains

Salesforce has used multiple domains for a long time, even in out-of-the-box orgs. As you use Salesforce, you’ll notice your address bar change to salesforce.com, lightning.force.com, content.force.com, or visualforce.com. And there are several other domains that we typically only use inside iframes. We use cookies to authenticate requests, and we’ve had to make some adjustments to our usage patterns already.

Salesforce sets a different session cookie for each domain in order to enforce security policies for that domain. For example, the session cookie for salesforce.com runs Setup, so it has more privileges than visualforce.com or lightning.force.com cookies. A lot of functionality in Setup is sensitive; Setup allows a user to edit metadata for an organization. Even if you are using Lightning Experience, many Setup pages you view are iframes to a page on salesforce.com.

With new browser tracking policies though, this means that those iframed Setup pages won’t receive cookies in their requests. The top-level page, on lightning.force.com, doesn’t match the iframed page on salesforce.com! Without cookies, we can’t authenticate requests, which is why we’re detecting this behavior and providing alternatives when your browser blocks cookies.

We’ve had to make some other changes too. You’ve noticed a critical update in your org to move Visualforce pages to a visualforce.com subdomain. Instead, we’re moving that product back to a force.com-based hostname. This shares a second-level domain with lightning.force.com, so Lightning pages can include Visualforce and still receive cookies. This change requires a couple other updates in your org, so be sure to read the Help page on this topic.

Salesforce still has a ways to go for other integrations that rely on third-party cookies. We’d like all our products to work with default browser settings, but it takes time to adapt to policy changes like these.

How you use multiple domains

Your org probably includes other integrations that rely on cookies. For example, the Salesforce Canvas feature allows easy integrations of third-party applications. Or your custom code may be using the iframe tag to include other applications.
For the most part, these are true third-party integrations; an iframe from a Salesforce property to some content not hosted on salesforce.com nor force.com. These integrations can’t use the strategy we used for Visualforce, reusing the same second-level domain. For these integrations, you’ll need to adapt the third-party application for the new best practices on the web.

Avoiding cookies entirely

Some web applications may be able to completely avoid cookies. If the application only accesses public resources, and doesn’t need to identify users, cookies won’t be needed to authenticate or authorize web requests. But for almost every business application, identity and authentication is essential. Almost all the data your application accesses must be protected.

Salesforce’s Canvas feature helps application developers identify users by providing data in an HTTP POST request. The body of the POST contains enough information to identify the Salesforce user. Application developers need to make sure this is enough information to identify the Third-party application user too, and return an authorization token in the response, usually in a hidden form field or a JavaScript block. Read more about Signed Request Authentication in the Canvas Developer Guide.

HTTPS Authorization header

The authorization token your application returns can then be used in JavaScript in the XMLHttpRequest or fetch() APIs to fetch further resources with an Authorization header. Developers are probably familiar with using the Authorization header to request JSON data resources. In case you aren’t, this tutorial is helpful for describing how to include headers like Authorization.

In the following example, we use the fetch() API to request some data. Then, we use the json() method to decode the response as a JSON object.

fetch(dataURL, {
        headers: {
            'Authorization': 'auth_token_here'
        }
    })
    .then(response => response.json())
    .then(json => {
        console.log("Use the data", json)
    }, err => {
        // Show an error message to the user
    });

But images often need authorization too! To use the Authorization header when requesting images, we’ll have to move away from static HTML markup to using JavaScript. A traditional web application would normally just create an img tag, and set the src attribute to the URL for the image. But the browser wouldn’t send authentication when requesting that image, so we’ll have to dynamically fetch the image data in JavaScript.

In the example below, we use the fetch() API with an Authorization token to fetch an image, just like requesting a JSON data resource. We use the blob() method to decode that image as a blob. We create a blob: URL, an object that lasts for the lifetime of the document. Then, we create a new img element for the document and set the src attribute to that blob URL. Finally, we append the img element to the document.

fetch(imageURL, {
        headers: {
            'Authorization': 'auth_token_here'
        }
    })
    .then(response => response.blob())
    .then(imageBlob => {
        const blobURL = URL.createObjectURL(imageBlob);
        console.log("This URL will begin with blob:", blobURL);

        const img = document.createElement('img');
        img.src = blobURL;
        document.body.appendChild(img); // Just an example; append the image wherever your app requires

    }, err => {
        // Show an error message to the user
    });

This pattern for creating blob: URLs comes with its own pitfalls. Applications with strict Content-Security-Policy (CSP) rules may not be able to use blob: URLs without allowlisting. So check that the img-src attribute of your application’s CSP allows this type of URL.

Be very careful with URL path or query string authorization

Another common pattern for authorization is tokens passed as a query string parameter. This can be useful for static JS and CSS requests in script or style elements — or for font-file resources in CSS because there is no opportunity to add authorization headers to those requests. But there are many considerations you need to take into account for this pattern, and the OWASP Foundation has documentation on the security risks.

Firefox’s Anti-tracking policy calls out URL parameters under “Tracking We Will Block”. So be careful that URL parameters can’t be used to identify or track a user. The tokens your application uses shouldn’t be unique for each user. Personally, I’m worried that browsers will flag even uses like these as false positives in future anti-tracking efforts.

Additionally, URL paths and query parameters are frequently logged by TLS termination proxies and web access logs. So the tokens should expire quickly to avoid data exposure. And the tokens should authorize a very limited range of requests, like a single script or CSS file.

Avoid tracking users across app sessions

Separate from authenticating requests, don’t try to track users across sessions using client-side storage. Use server-side storage, or use communication through the top-level frame, or don’t track users at all. Browsers are sending a clear message that using client storage to track users is not appropriate.

There are proposals out there for alternatives to cookies, like the HTTP state tokens proposal. This proposal fixes the security problems with cookies by making state tokens HTTPS-only, as well as some of the privacy problems by making it clear the user agent controls the state token’s value. However, it is only a proposal and hasn’t been implemented in any major browser.

Use Storage Access API to get cookies when you need them

Some applications won’t be able to completely avoid cookies. Browsers are adding support for a Storage Access API to help these applications initiate a request to temporarily use web storage. But be careful, this API is experimental. It’s not available in all browsers yet, so guard any calls to the API by checking for the existence of the functions.

To use the Storage Access API, an application will need to initially load a page without cookies. This can be tricky, because the initial application request could lack cookies because this is the user’s first request to your application! This page should contain whatever content can load without authentication, such as a banner for the application. It should also have some user affordance, like a button, that calls the storage access API when clicked.

In this example code block, we use a click handler to call the requestStorageAccess() function. Then we add a handler to the Promise that function returns. The key point of this example is that requestStorageAccess() must be called during some interaction with the page. It can’t just be called when the page loads, it has to be inside a ‘click’ event handler!

if (document.requestStorageAccess) {
    button.addEventListener('click', () => {
        document.requestStorageAccess()
            .then(() => {
                // Access granted
            }, () => {
                // Access denied
            });
    });
} else {
    // Show fallback error message
}

Of course, the button should be labelled appropriately, and text should be given near the button to describe why the application needs access to cookies and web storage. After the user allows access to cookies, further requests can be made to load content for the page, like adding script and style tags that require authentication.

Conclusion

Overall, limit your application’s usage of cookies and other web storage. Just like the removal of third-party cookies more than a decade ago, the contract between browsers and applications is changing. Even applications written long ago will have to update to the new standards for privacy and security. Salesforce supports the ongoing efforts to improve privacy and security across the web.

This post demonstrates a couple ways in which cross-domain applications can authorize data and script requests without cookies. There are other options for authorization, like hidden form fields, that will be useful for form data. Consider all options when developing your application.

About the author

Mike Senn is a Lead Software Engineer at Salesforce working on Lightning platform. He focuses on integration with Visualforce and other Salesforce UI technologies.