This proposal is an early design sketch by Privacy Sandbox to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.
A new web primitive is needed to cover short-lived popup use cases which require access to storage partitioned by the popup opener. This primitive should be private and secure by default, while providing a consistent UI experience across user agents.
To solve this need, we propose the “Partitioned Popin”, a type of pop-up for loading web content with two unique new features: a modal-like UI relative to its opener tab and cookies/storage being partitioned to its opener context.
This ‘popin’ could be useful for any sites wanting a consistent way to prompt the user to interact with a new window in a way that makes it clear what site initiated the interaction. Making the ‘popin’ partitioned by its opener ensures the privacy of an iframe (restricting access to first-party storage) while retaining the security of a top-level navigation (isolating the process).
Many smaller businesses and applications on the web currently use third-party vendors to perform or facilitate security sensitive operations such as authentication. These third-party vendors prefer to be loaded in top-level contexts so that they are not subject to clickjacking or script injection attacks by a compromised relying party. To obtain this behavior, login vendors typically open popups or perform redirects, which subsequently close or redirect back to the relying party, with a result token sent back via mechanisms such as cookies, postMessage, or URL parameters.
These methods are incompatible with modern browsers’ privacy and security goals for partitioning cross-site state:
There are also some UX challenges with this pattern:
In the example below, popup authentication is blocked by third-party cookie deprecation.
In the example below, redirect authentication is blocked by third-party cookie deprecation.
These flows are currently partly detected and mitigated through the cookie access heuristics that shipped in major browsers. The general consensus is that the web ecosystem needs to find ways to replace the heuristics with more private and secure alternatives. We see this proposal as a major contribution to that effort.
Existing work on the restrict-properties proposal (allowing popups without breaking crossOriginIsolation) ran into feedback from developers who needed a way to focus the opened window (postMessage and closed were not sufficient).
Investigations into protecting openers revealed the amount of use in the wild that would have to adapt to such a paradigm, and the need for some underlying improvement in functionality to make the prospect more attractive to developers.
The following would be added to windowFeatures:
_blank
as others have no special meaning in this context. Cannot be used with popup
feature as these options conflict. The features width
, height
, left
, and top
may or may not be respected by the rendering browser (especially on mobile). noopener
and noreferrer
will be respected as usual.A draft API shape for partitioned popins would look like:
let popin_handle = window.open('https://example.com', '_blank', 'popin');
Only one popin at a time can be active in a given window. If there is an active popin then calls to window.open to trigger further popins will fail.
Opener access is available to popins by default, but is restricted to the postMessage
and closed
properties, to give the same security benefits as the proposed restrict-properties COOP header (note: plans in this area are changing rapidly).
Opener access can be fully severed by passing the noopener
feature, COOP headers, and other existing methods.
There is no way to gain opener access to functions/members beyond postMessage
and closed
.
In the opened popin context, closed
would always return false as the popin can only exist in a window while the window is open.
Within the popin context, window.open
cannot be used to open a nested popin.
window.open
(as well as HTML links) can be used to open new popups or tabs, but no opener will be connected to these contexts.
window.location.assign
(as well as HTTP/HTML redirects) can be used to navigate the popin itself.
This API is only available in secure contexts and can only open secure contexts. Attempting to navigate/redirect a popin to an insecure context will fail.
The popin can navigate/redirect, but will always be partitioned as though it was embeded as an iframe in the opener.
User interaction is required to open the popin.
Without user interaction the popin will be blocked and an error will be thrown when calling window.open
.
This is important to prevent short-lived popins from being opened and immediately grabbing auto-fill information to nearly-silently complete login without user consent.
When a popin is opened by a browsing context, it inherits the storage partition key that an iframe opened in that context would have. If top-level site A opens popin A, then the popin will be unpartitioned. If top-level site A opens popin B, the popin will be partitioned. If top-level site A embeds iframe B and that iframe opens popin A, the popin would be partitioned using the ancestor chain bit.
Like a cross-site iframe, a Partitioned Popin will reject any cookies unless they are marked with the Partitioned
and SameSite=None
attributes.
The cookie partition key will also be based on the context that opened the popin.
To allow developers to gracefully fall back to navigation-based or iframe-based flows when Partitioned Popins are not supported by the platform, it could be useful to expose this information to them at runtime. Similarly, it seems useful for developers to understand when their documents are loaded in a popin context, to adjust the loaded content accordingly.
To support potential future types of popins we should use an enum rather than a boolean value in feature detection paths.
enum PopinContextType { "partitioned" };
A draft API for feature detection by a prospective popin opener could look like:
partial interface Window {
// Returns an empty array if no popin context types are supported.
sequence<PopinContextType> popinContextTypesSupported();
};
A draft API for a popin to detect it is a popin could look like:
partial interface Window {
// Returns null if this isn't a popin context.
PopinContextType? popinContextType();
};
All pages navigated to inside a partitioned popin must send an HTTP request header indicating this. The proposed header’s value would be an sf-token one possible token supported (‘partitioned’) potentially expandable in future.
Sec-Popin-Context: partitioned
This header should be omitted when the request is not for a popin context.
All pages navigated to inside a partitioned popin must return an HTTP response header indicating their support for the partitioned popin context. The proposed header’s value would work similar to a permissions policy (an sf-dictionary) with one possible permission (partitioned) potentially expandable in future.
Popin-Policy: partitioned=()
Popin-Policy: partitioned=*
Popin-Policy: partitioned=("https://demo.example/")
partitioned=()
is the default value and disallows the page from ever being opened in a partitioned popin context.
partitioned=*
allows the page to be loaded in a partitioned popin context by any page.
partitioned=("https://demo.example/")
allows the page to be loaded in a partitioned popin context by a top-level frame on https://demo.example/ specifically.
Navigating to or redirecting through a page without a Popin-Policy
that permits the opening context to do so will cause the popin to automatically close and report the error to the opening context.
This header is important to ensure that, where partitioned popins are used for authentication and other sensitive tasks, the pages engaging in these sensitive tasks affirmatively consent to the popin context. This prevents unexpectedly authenticated iframe contexts from breaking assumptions sites have made about their security model.
A new policy directive, popins
, will be added to control which frames can open partitioned popins. This directive will have a default value of self
(not *
as many directives do) to ensure that without affirmative action, cross-site iframes will not be able to open popins.
Draft UX (not final) for large-screen/desktop could look like:
Draft UX (not final) for small-screen/mobile could look like:
Please note that we recommend the UX has the following properties:
SAA provides a way for the cross-site iframe to complete authentication by requesting access to the same unpartitioned storage that the popup/redirect just wrote. Unfortunately, this access requires user interaction with the iframe, preventing silent token acquisition. Further, this API was not intended to be used for login so the wording in the permission prompt the user sees when access is attempted may confuse them.
IDaaS providers are different from federated identity providers, in that user identity is scoped/partitioned to the website that the user is visiting. FedCM is targeted to the latter use case so is not a good fit in terms of user prompt language and adoption cost. For example, an enterprise single-sign-on (SSO) solution doesn’t want user identities partitioned by service, it wants a single user identity which provides access to multiple services. For this reason solutions like FedCM+SAA also would not work.
This provides an automated way for some login-like flows to automatically grant access to unpartitioned storage in third-party contexts. This has the downside of providing access to unpartitioned storage in a partitioned context rather than requiring all storage remain partitioned. Additionally, these heuristics may be deprecated and require interaction.
The IdP login page (secured via frame-ancestors) could simply be embedded in the RP as an iframe. Without full trust between the parties, frame-ancestors alone likely would not provide sufficient assurances around abuse of the IdP login iframe. The IdP may trust the RP enough to perform limited auth-related communication through an opener relationship or third-party cookies, but it can not necessarily ensure that the RP’s overall security posture is strong enough to prevent clickjacking or other attacks on the IdP iframe. Popins also give the IdP more control over the user experience, not having to rely on the RP to present the login flow in a trustworthy and understandable way to the user. Even with full trust between the parties, large-scale adaptation would be required on the part of RPs to allow the IdP login iframe to appear on their page. Using pop-ups for authentication flows is a very widely established pattern and very familiar to users on the web.
Another crucial benefit of showing authentication flows in popups or popins is the user’s ability to inspect the URL bar to decide whether to trust the site with their credentials or not, which is not possible with iframes.
The Document Picture in Picture API provides a way for a top-level frame to create a ‘floating’ and positionable window treated as same-origin (allowing DOM access). This differs from our proposal as cross-origin content loaded in the PiP window is subject to DOM manipulation by the top-level page, so it does not provide the same security assurances as if the content were loaded as a top-level window.
As explained, we expect a major use case of partitioned popins to be cross-site login. The proposal primarily wants to address logging into 3rd party vendors that need to integrate with a single top-level site in a 1:1 relationship. However, specifically in enterprise or federated identity scenarios, users may want to integrate with the same 3rd party service for single-sign-on (SSO) or other 1:many purposes. Partitioned popins are not very effective at addressing that use case, because they’re partitioned. They don’t carry over state from the 3rd party vendor, thus requiring the user to log into the top-level site again and again, effectively rendering the single-sign-on functionality moot. In addition to increasing user friction, this has two potentially harmful consequences from a security perspective:
To counteract this risk, browsers that implement Partitioned Popins should offer a range of additional API options that make it easy for developers to implement SSO / federated login, such as FedCM, Passkeys or the Storage Access API. We believe that the increased user friction from partitioning will lead most developers to choose the most secure user experience if good alternatives are available.
The assumption behind security and privacy review of storage partitioning and third-party cookie deprecation has been that authentication should be performed in a top-level frame which has access to unpartitioned storage. The partitioned popin model breaks this assumption by providing a top-level frame that has partitioned storage. Compatibility between this and many existing web features will have to be considered to ensure we aren’t opening up an avenue for abuse.
If we allow partitioning of any links opened by some top-level site this could enable cross-site tracking by unifying the cookie buckets of iframes in the opened popins. For example, if a top-frame on origin A (storage key: <A,A,SameSite>) opens a partitioned popins on origin B (storage key: <A,B,CrossSite>) and later one on origin C (storage key: <A,B,CrossSite>) and each popup embeds an iframe for origin A (storage key: <A,A,CrossSite>), these iframes share the same cookies/storage. This could enable a publisher with many outbound links to share ad-network cookies across any links opened in partitioned popins. This could be mitigated by a heuristic (prompting the user if more than one popin is opened per-navigation) or by prompting the user if a partitioned popin is to be opened.
The risk of popin spam is lower than popup spam as only one can be active at a time and user interaction with browser UX is not preempted (popins live within the owning window and the user can always close the tab/window or switch to a different tab/window). If needed further heuristics could be applied to rate limit popins or require user permission to open them.
Although postMessage
and closed
would be available via the window proxy by default, it should remain possible to further restrict popin access via existing means.
For websites already using COOP same-origin
or same-origin-allow-popups
options, in cases where the opener would be severed they should remain as such.
No path to enabling more window proxy access to popins should be added.
This is a new web UX primitive and significant thought should go into design and delivery. Picking one platform (e.g., mobile) first for testing to get the implementation right may be preferable.
This new surface should remain legible to screen readers and other common browser tools. The ability to close the popin is especially important to communicate to users who would have difficulty intuiting they could interact with the negative space around the popin.
Popins are intended to be for short-term interactions that do not require user access to the initiator before resolution. What about use cases not covered by this (e.g., an iframed web app opening a popup to display information the user wants to compare against the opening iframe)? Can we provide the user with a way to ‘pop out’ the popin? How do we differentiate between the partitioned and unpartitioned top-level windows?
It’s possible to get a popin-like UX on Chrome for Android or in Safari as part of the link-preview feature. These previews have different privacy and security implications than popins as they are unpartitioned, ephemeral/non-interactive while previewed, and resolve as new tabs. We should ensure the user isn’t confused between the two UX modes.
If we think forward to a time when popins have been shipped and adoption is significant, deprecating the riskiest parts of current popup and window.opener
behavior should be considered.
Should we launch unpartitioned popins as well?
Can the desktop-specific popup UI be removed?
Can we require all openers be severed or limited/defaulted to just postMessage
and closed
?
If purpose built APIs (e.g., FedCM) have been delivered and widely adopted, these things may be within our reach, since it eliminates backwards-compatibility risks for use cases like login.