The Origin API

Unofficial Proposal Draft,

More details about this document
This version:
https://mikewest.github.io/origin-api
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)

Abstract

An Origin object might be nice to have.

Status of this document

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.

The new Origin() constructor steps are:
  1. Set this’s [[origin]] to a unique opaque origin.

The 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:
  1. If serializedOrigin is "null":

    1. Set this’s [[origin]] to a unique opaque origin.

    2. Return.

  2. Let origin as url be the result of executing the basic URL parser on serializedOrigin.

  3. If origin as url is failure, throw a "TypeError" DOMException.

  4. If origin as url’s origin’s serialization is not serializedOrigin, throw a "TypeError" DOMException.

  5. 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.

Like 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:
  1. Let origin be a new Origin object.

  2. If serializedOrigin is "null":

    1. Set origin’s [[origin]] to a unique opaque origin.

    2. Return origin.

  3. Let origin as url be the result of executing the basic URL parser on serializedOrigin.

  4. If origin as url is failure, return null.

  5. If origin as url’s origin’s serialization is not serializedOrigin, return null.

  6. Set origin’s [[origin]] to origin as url’s origin.

  7. Return origin.

The static 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:
  1. Let parsed url be the result of running the basic URL parser on serializedURL.

  2. If parsed url is failure, return null.

  3. Let origin be a new Origin object.

  4. 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.

The 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.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[PSL]
Public Suffix List. Mozilla Foundation.

IDL Index

[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();
};

Issues Index

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]