JavaScript API for Device Bound Session Credentials

Unofficial Proposal Draft,

More details about this document
This version:
https://explainers-by-googlers.github.io/dbsc-js-api
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)
Not Ready For Implementation

This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion.

Before attempting to implement this spec, please contact the editors.


Abstract

An API for client-side Device Bound Session Credentials (DBSC) Session State Management.

Status of this document

1. Introduction

For now, see the explainer.

This spec is an early-stage draft. Missing details and remaining TODOs are called out with Issue: blocks.

2. Security considerations

3. Privacy considerations

4. Framework

4.1. Observer store

The user agent maintains an observer store. It is a set of weak references to DeviceBoundSessionsObserver objects. The store is initially empty.

When an observer has been reclaimed by garbage collection, it must be removed from the store.

4.2. Registration buffer and store

The user agent maintains a registration attempts store. It is a map from storage key to registration buffer. The store is initially empty.

Should we make this a formal storage endpoint?

A registration buffer is a list of DeviceBoundSessionRegistrationReports. Each buffer is initially empty.

User agents should put a reasonable limit on the number of attempts stored in each buffer and their time-to-live.

5. The DeviceBoundSessionsObserver interface

This interface can be used to observe device bound sessions and registration attempts.

[Exposed=Window, SecureContext]
interface DeviceBoundSessionsObserver {
  constructor(
    DeviceBoundSessionsObserverCallback callback,
    optional DeviceBoundSessionsObserverOptions options = {}
  );
  undefined observe();
  undefined disconnect();
  sequence<DeviceBoundSessionsReport> takeRecords();
};

callback DeviceBoundSessionsObserverCallback = undefined (
  sequence<DeviceBoundSessionsReport> reports,
  DeviceBoundSessionsObserver observer
);

A DeviceBoundSessionsObserver has an associated callback (a DeviceBoundSessionsObserverCallback) and an associated options (a DeviceBoundSessionsObserverOptions).

When the DeviceBoundSessionsObserver(callback, options) constructor is called:
  1. Set this’s callback to callback.

  2. Set this’s options to options.

To inform a DeviceBoundSessionsObserver observer of a list of DeviceBoundSessionsReports reports:
  1. Assert: reports is not empty.

  2. Queue a global task on the networking task source given observer’s relevant global object to run these steps:

    1. If observer store does not contain observer, then return.

    2. Invoke observer’s callback with « reports, observer » and "report", and callback this value observer.

Callers of this algorithm must verify that observer is allowed to know about reports under the user agent’s current policies, notably storage partitioning.

5.1. DeviceBoundSessionsObserverOptions dictionary

dictionary DeviceBoundSessionsObserverOptions {
  AbortSignal signal;
};
signal, of type AbortSignal

The abort event stops the observer from receiving further reports.

5.2. observe() method

When observe() is called:
  1. Let signal be this’s options[signal].

  2. If signal exists and is aborted, then return.

  3. If observer store contains this, then return.

  4. Append a weak reference to this to observer store.

  5. If signal exists, then add the following abort steps to signal:

    1. Call this’s disconnect().

  6. Let reports be the result of collecting past reports for this.

  7. If reports is not empty, then inform this of reports.

To collect past reports for a DeviceBoundSessionsObserver observer:
  1. Let environment be observer’s relevant settings object.

  2. Let observer key be the result of running obtain a storage key for non-storage purposes given environment.

  3. Let reports be a new list.

  4. If registration attempts store[observer key] exists, then set reports to a clone of registration attempts store[observer key].

  5. For each sessions by id of session store’s values:

    1. For each session of sessions by id’s values:

      1. If determine session visibility given environment and session does not return "visible", then continue.

      2. Let session report be the result of creating an established session report given session.

      3. If reports contains a DeviceBoundSessionRegistrationReport registration such that registration[session] exists and equals session report, then continue.

      4. Append session report to reports.

  6. Return reports.

5.3. disconnect() method

When disconnect() is called:
  1. Remove this from observer store.

User agents may want to also clean up previously set up abort steps and remove garbage-collected references from the store.

5.4. takeRecords() method

When takeRecords() is called:
  1. Let reports be the result of collecting past reports for this.

  2. Let observer key be the result of running obtain a storage key for non-storage purposes given this’s relevant settings object.

  3. Remove registration attempts store[observer key].

  4. Return reports.

5.5. Report types

enum DeviceBoundSessionsReportKind {
  "session",
  "registration"
};

dictionary DeviceBoundEstablishedSessionReport {
  required DeviceBoundSessionsReportKind type;
  required DOMString sessionOrigin;
  required DOMString sessionId;
  required DOMString refreshUrl;
  required DOMHighResTimeStamp creationTime;
};

dictionary DeviceBoundSessionRegistrationReport {
  required DeviceBoundSessionsReportKind type;
  required DOMString registrationUrl;
  required boolean success;
  required DOMHighResTimeStamp endTime;
  DeviceBoundEstablishedSessionReport session;
  DOMString reason;
  unsigned short code;
  DOMString providerUrl;
  DOMString providerSessionId;
};

typedef (
  DeviceBoundSessionRegistrationReport
  or DeviceBoundEstablishedSessionReport) DeviceBoundSessionsReport;

5.5.1. Registration attempt report

A DeviceBoundSessionRegistrationReport represents the outcome of the multi-stage process started by process a session registration. See § 7.1 Changes to the DBSC specification.

type, of type DeviceBoundSessionsReportKind

Must be "registration".

registrationUrl, of type DOMString

The serialized registration endpoint URL to which the registration request was sent.

success, of type boolean

True if the registration attempt resulted in an established device bound session; false otherwise.

endTime, of type DOMHighResTimeStamp

The duration from the Unix epoch to the coarsened wall time when the registration process concluded.

Using the wall clock means endTime is not monotonic across registration reports. For example, system clock changes could modify the apparent order of reports.

An alternative would be to compute these timestamps using the estimated monotonic time of the Unix epoch here.

This would in turn have the drawback of inconsistent timestamps between a successful registration report’s endTime and its session[creationTime].

session, of type DeviceBoundEstablishedSessionReport

The DeviceBoundEstablishedSessionReport for the newly created device bound session, present when success is true.

reason, of type DOMString

A human-readable or categorical description of why the registration attempt failed, present when success is false.

Replace the free-form string with a WebIDL enum of standardized failure reasons.

code, of type unsigned short

The HTTP status code received from the registration endpoint, present when the attempt failed due to an HTTP response status.

providerUrl, of type DOMString

For a federated session registration attempt, the serialized provider URL specified in the `Secure-Session-Registration` header.

providerSessionId, of type DOMString

For a federated session registration attempt, the provider session identifier specified in the `Secure-Session-Registration` header.

To record a registration attempt given a URL registration URL, a boolean success, an optional device bound session resulting session, an optional string reason, an optional integer code, an optional URL provider URL, and an optional string provider session id:
  1. Let report be a DeviceBoundSessionRegistrationReport with:

    type

    "registration".

    registrationUrl

    The URL serializer of registration URL.

    success

    success.

    endTime

    The duration from the Unix epoch to the current coarsened wall time.

  2. If resulting session was given, then set report[session] to the result of creating an established session report given resulting session.

  3. If reason was given, then set report[reason] to reason.

  4. If code was given, then set report[code] to code.

  5. If provider URL was given, then set report[providerUrl] to the URL serializer of provider URL.

  6. If provider session id was given, then set report[providerSessionId] to provider session id.

  7. Report report with resulting session (if given).

To report a DeviceBoundSessionRegistrationReport report given an optional device bound session resulting session:
  1. Let registration key be the storage key of report.

  2. For each observer of observer store:

    1. Let environment be the relevant settings object for observer.

    2. Let observer key be the result of running obtain a storage key for non-storage purposes given environment.

    3. If observer key equals registration key, then inform observer of « report », and continue.

    4. If resulting session was not given, then continue.

    5. If determine session visibility given environment and resulting session does not return "visible", then continue.

    6. Inform observer of « report[session] ».

  3. If registration attempts store[registration key] does not exist, then set registration attempts store[registration key] to a new registration buffer.

  4. Append report to registration attempts store[registration key].

The storage key of a DeviceBoundSessionRegistrationReport registration is defined as:
  1. Let url be the result of running the URL parser on registration[registrationUrl].

  2. Let origin be url’s origin.

  3. Let site be obtain a site given origin.

  4. Return a storage key whose origin is origin and top-level site is site.

5.5.2. Established session report

A DeviceBoundEstablishedSessionReport represents a device bound session. Note that DeviceBoundSessionRegistrationReport also contains a session: if an observer is informed of an established session via a (buffered) DeviceBoundSessionRegistrationReport, it must not be informed of the same session a second time via a DeviceBoundEstablishedSessionReport.

type, of type DeviceBoundSessionsReportKind

Must be "session".

sessionOrigin, of type DOMString

The serialized origin of the device bound session’s session scope.

sessionId, of type DOMString

The device bound session’s session identifier.

refreshUrl, of type DOMString

The serialized refresh URL of the device bound session.

creationTime, of type DOMHighResTimeStamp

The duration from the Unix epoch to the device bound session’s creation time.

To create an established session report given a device bound session session:
  1. Let report be a new DeviceBoundEstablishedSessionReport dictionary with:

    type

    "session".

    sessionOrigin

    The serialization of an origin given session’s session scope’s origin.

    sessionId

    session’s session identifier.

    refreshUrl

    The URL serializer of session’s refresh URL.

    creationTime

    The duration from the Unix epoch to session’s creation time.

  2. Return report.

A DeviceBoundEstablishedSessionReport a equals another DeviceBoundEstablishedSessionReport b if all of the following are true:

6. Session reports visibility

To determine session visibility given an environment settings object environment and a device bound session session:
  1. Let url be environment’s creation URL.

  2. If url is not session’s refresh URL:

    1. If the result of running determine if a URL is in scope with url and session is false, then return "hidden".

  3. If environment has storage access, then return "visible".

  4. Return "hidden".

Consider making this a WPT instead.

Given the following (incomplete) session definition:

{
  "session_identifier": "session_id",
  "refresh_url": "/refresh",
  "scope": {
    "origin": "https://example.com",
    "include_site": true,
    "scope_specification" : [
      { "type": "exclude", "domain": "sites.example.com", "path": "/" },
      { "type": "exclude", "domain": "*.example.com", "path": "/static" }
    ]
  },
  "credentials": [{
    "type": "cookie",
    "name": "auth_cookie",
    "attributes": "Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict"
  }]
}

The following table illustrates whether session_id is visible to various execution environments:

Creation URL Storage access Visible? Reason
https://example.com/account Top-level window true In scope of session_id, has storage access.
https://sub.example.com/app Top-level window true Also in scope of session_id, by its include_site.
https://example.com/refresh Top-level window true Refresh URL bypasses scope check.
https://example.com/refresh Embedded without storage access false No access to 1P example.com storage.
https://sites.example.com/a N/A false URL matches the sites.example.com/ exclusion rule.
https://app.example.com/static/index.html N/A false URL matches the *.example.com/static exclusion rule.

7. Changes to other specifications

7.1. Changes to the DBSC specification

Avoid deep monkey-patching here: open a PR against [dbsc-1], then remove this section.

In device bound session, add the following item:

creation time

The current coarsened wall time at which this session was created.

In process a session registration:

  1. Modify the step that checks if key pair is "failure" to record a registration attempt on failure.

  2. Pass provider URL and provider session id as additional optional inputs to send a request.

Note: The next two changes mention "if session id is null" to send a report only when send a request creates a new session. When that algorithm runs for a refresh, session id is known.

In send a request:

  1. Accept an optional provider URL and an optional provider session id to adequately construct a federation attempt report when applicable.

  2. If session id is null, then:

    1. Whenever returning early due to a network error or an invalid HTTP status code, record a registration attempt.

    2. Pass provider URL and provider session id to create a session.

In create a session:

  1. Accept an optional provider URL and an optional provider session id.

  2. After setting the expiration timestamp, also set creation time.

  3. Whenever invoking terminate the session and returning (cf. the last "credential" loop), if session id is null, then record a registration attempt with an appropriate failure reason, code, etc.

  4. When adding a session to the session store, if session id is null, then record a registration attempt (successful, passing session).

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.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DBSC-1]
Ian Clelland; Daniel Rubery; thefrog t. Device Bound Session Credentials. URL: https://w3c.github.io/webappsec-dbsc/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HR-TIME-3]
Yoav Weiss. High Resolution Time. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[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
[STORAGE]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[STORAGE-ACCESS]
The Storage Access API. Editor's Draft. URL: https://privacycg.github.io/storage-access/
[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/

IDL Index

[Exposed=Window, SecureContext]
interface DeviceBoundSessionsObserver {
  constructor(
    DeviceBoundSessionsObserverCallback callback,
    optional DeviceBoundSessionsObserverOptions options = {}
  );
  undefined observe();
  undefined disconnect();
  sequence<DeviceBoundSessionsReport> takeRecords();
};

callback DeviceBoundSessionsObserverCallback = undefined (
  sequence<DeviceBoundSessionsReport> reports,
  DeviceBoundSessionsObserver observer
);

dictionary DeviceBoundSessionsObserverOptions {
  AbortSignal signal;
};

enum DeviceBoundSessionsReportKind {
  "session",
  "registration"
};

dictionary DeviceBoundEstablishedSessionReport {
  required DeviceBoundSessionsReportKind type;
  required DOMString sessionOrigin;
  required DOMString sessionId;
  required DOMString refreshUrl;
  required DOMHighResTimeStamp creationTime;
};

dictionary DeviceBoundSessionRegistrationReport {
  required DeviceBoundSessionsReportKind type;
  required DOMString registrationUrl;
  required boolean success;
  required DOMHighResTimeStamp endTime;
  DeviceBoundEstablishedSessionReport session;
  DOMString reason;
  unsigned short code;
  DOMString providerUrl;
  DOMString providerSessionId;
};

typedef (
  DeviceBoundSessionRegistrationReport
  or DeviceBoundEstablishedSessionReport) DeviceBoundSessionsReport;

Issues Index

Should we make this a formal storage endpoint? ↵
Replace the free-form string with a WebIDL enum of standardized failure reasons. ↵
Consider making this a WPT instead. ↵
Avoid deep monkey-patching here: open a PR against [dbsc-1], then remove this section. ↵