1. Introduction
The origin is a fundamental component of the web’s implementation, essential to both the security and privacy boundaries which user agents maintain. The concept is well-defined between HTML and URL, along with widely-used adjacent concepts like "site".
Origins, however, are not directly exposed to web developers. Though there are various origin
getters on various objects, each of those returns the ASCII serialization of an origin, not the origin itself. This has a few negative implications. Practically, developers attempting to do same-origin or same-site comparisons when handling serialized origins often get things wrong in ways that lead to vulnerabilities (see PMForce: Systematically Analyzing postMessage Handlers at Scale (Steffens and Stock, 2020) as one illuminating study). Philosophically, it seems like a missing security primitive that developers struggle to polyfill accurately.
We can address this gap in the platform by introducing an Origin
object that encapsulates the origin concept, and provides helpful methods for comparison, serialization, parsing, and etc.
2. The Origin
Object
This section is probably best read as a patch to HTML, adding a new subsection to 7.1.1 Origins. If there’s support for the idea, we should turn it into a PR rather than a standalone document. [whatwg/html Issue #11534]
[Exposed=*]interface {
Origin constructor ();constructor (USVString );
serializedOrigin static Origin ?parse (USVString );
serializedOrigin static Origin ?fromURL (USVString );
serializedURL readonly attribute boolean opaque ;boolean isSameOrigin (Origin );
other boolean isSameSite (Origin );
other stringifier USVString toJSON (); };
An Origin
object has an [[origin]]
internal slot, which
holds an origin.
new Origin()
constructor steps are:
-
Set this’s
[[origin]]
to a unique opaque origin.
new Origin(serializedOrigin)
constructor accepts a USVString
serializedOrigin which contains the ASCII serialization of an origin. If serializedOrigin is not a valid serialization, the constructor will throw a
TypeError
. Otherwise, the constructed object will hold the deserialized origin:
-
If serializedOrigin is "
null
":-
Set this’s
[[origin]]
to a unique opaque origin. -
Return.
-
-
Let origin as url be the result of executing the basic URL parser on serializedOrigin.
-
If origin as url is failure, throw a "
TypeError
"DOMException
. -
If origin as url’s origin’s serialization is not serializedOrigin, throw a "
TypeError
"DOMException
. -
Set this’s
[[origin]]
to origin as url’s origin.
Note: The algorithm above parses serializedOrigin as a URL, then compares it against the serialization of that URL’s origin. This seems like the cheapest way to reuse all the infrastructure in [URL], while still ensuring that we require a validly serialized origin. It would of course be possible to extract a strict origin parser from the algorithms in URL, but it seems at least somewhat likely that they might drift apart at some point in the future. Reusing the existing algorithms, then checking the result for correctness, seems robust and straightforward enough to rely upon.
Origin
’s Origin(serializedOrigin)
constructor, the static
parse(serializedOrigin)
method
accepts a USVString
serializedOrigin which contains the ASCII serialization of an origin. If serializedOrigin is not a valid serialization, the method will return null
.
Otherwise, it will return a newly-constructed Origin
holding the deserialized origin:
-
Let origin be a new
Origin
object. -
If serializedOrigin is "
null
":-
Set origin’s
[[origin]]
to a unique opaque origin. -
Return origin.
-
-
Let origin as url be the result of executing the basic URL parser on serializedOrigin.
-
If origin as url is failure, return
null
. -
If origin as url’s origin’s serialization is not serializedOrigin, return
null
. -
Set origin’s
[[origin]]
to origin as url’s origin. -
Return origin.
fromURL(serializedURL)
method accepts a USVString
serializedURL which contains the serialization
of a URL. If serializedURL is not a valid serialization, the method will return null
.
Otherwise, it will return a newly-constructed Origin
object holding the deserialized URL’s
origin:
-
Let parsed url be the result of running the basic URL parser on serializedURL.
-
If parsed url is failure, return
null
. -
Let origin be a new
Origin
object. -
Set origin’s
[[origin]]
to parsed url’s origin.
Note: Unlike URL.parse
, this method does not accept a base, but expects the
complete serialization of a URL. That seems clearer, and guides developers towards constructing
a URL
object in some well-understood way that’s distinct from their work with Origin
.
The opaque
attribute getter steps are to return true
if this’s [[origin]]
is an opaque origin, and false
otherwise.
The isSameOrigin(other)
method steps are to return true
if this’s [[origin]]
is same origin with other’s
[[origin]]
, and false
otherwise.
isSameSite(other)
method steps are to return true
if this’s [[origin]]
is same site with other’s
[[origin]]
, and false
otherwise.
Note: This is a same site, not schemelessly same site, comparison.
The toJSON()
method steps are to return the
serialization of this’s [[origin]]
.
3. Security Considerations
The isSameSite(other)
method exposes each particular user agent’s understanding of an
origin’s site. As this understanding generally depends on a specific snapshot of
the Public Suffix List [PSL], the deliniation of a site can and does differ between user
agents, and even between versions of one user agent. Developers are encouraged to exercise caution
when making decisions based on sites, and are likewise encouraged to rely upon
isSameOrigin(other)
when making security decisions.