WebOTP API

Draft Community Group Report,

This version:
http://wicg.github.io/WebOTP
Test Suite:
https://github.com/web-platform-tests/wpt/tree/master/sms
Issue Tracking:
GitHub
Editor:
(Google Inc.)

Abstract

A Javascript API to request one time passwords for verifying credentials (e.g. phone numbers, emails).

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

logo

1. Introduction

This section is non-normative.

Many web sites need to verify credentials (e.g. phone numbers and email addresses) as part of their authentication flows. They currently rely on sending one-time-passwords (OTP) to these communication channels to be used as proof of ownership. The one-time-password is manually handed back by the user (typically by copying/pasting) to the web app which is onerous and erroneous.

This a proposal for a client side javascript API that enables web sites to request OTPs and a set of transport-specific conventions (we start with SMS while leaving the door open to others) that can be used in coordination with browsers.

1.1. The client side API

In this proposal, websites have the ability to call a browser API to request OTPs coming from specific transports (e.g. via SMS).

The browser intermediates the receipt of the SMS and the handing off to the calling website (typically asking for the user’s consent), so the API returns a promise asynchronously.

let {code, type} = await navigator.credentials.get({
  otp: {
    transport: ["sms"]
  }
});

1.2. The server side API

Once the client side API is called, the website’s server can send OTPs to the client via the requested transport mechanisms. For each of these transport mechanism, a server side convention is set in place to guarantee that the OTP is delivered safely and programatically.

For SMS, for example, servers should send origin-bound one-time code messages to clients. [sms-one-time-codes]

In the following origin-bound one-time code message, the host is "example.com", the code is "123456", and the explanatory text is "Your authentication code is 123456.\n".

"Your authentication code is 123456.

@example.com #123456"

1.3. Feature Detection

Not all user agents necessarily need to implement the WebOTP API at the exact same moment in time, so websites need a mechanism to detect if the API is available.

Websites can check for the presence of the OTPCredential global interface:

if (!window.OTPCredential) {
  / feature not available
  return;
}

1.4. Web Components

For the most part, OTP verification largely relies on:

We expect some of these frameworks to develop declarative versions of this API to facilitate the deployment of their customer’s existing code.

Web Component Polyfills
<script src="sms-sdk.js"></script>

<form>
  <input is="one-time-code" required />
  <input type="submit" />
</form>

And here is an example of how a framework could implement it using web components:

Web Component Polyfills
customElements.define("one-time-code",
  class extends HTMLInputElement {
    connectedCallback() {
      this.receive();
    }
    async receive() {
      let {code, type} = await navigator.credentials.get({
        otp: {
         transport: ["sms"]
        }
      });
      this.value = otp;
      this.form.submit();
    }
  }, {
    extends: "input"
});

1.5. Abort API

Many modern websites handle navigations on the client side. So, if a user navigates away from an OTP flow to another flow, the request needs to be cancelled so that the user isn’t bothered with a permission prompt that isn’t relevant anymore.

To facilitate that, an abort controller can be passed to abort the request:

const abort = new AbortController();

setTimeout(() => {
  / abort after two minutes
  abort.abort();
}, 2 * 60 * 1000);
  
let {code, type} = await navigator.credentials.get({
  signal: abort.signal,
  otp: {
    transport: ["sms"]
  }
});

2. Client Side API

Websites call navigator.credentials.get({otp:..., ...}) to retrieve an OTP.

The algorithm of Request a Credential abstract operation.

In that operation, it finds OTPCredential which inherits from credentials that should be available without user mediation, and if it does not find exactly one of those, it then calls OTPCredential.[[DiscoverFromExternalSource]]() to have the user select a credential source and fulfill the request.

Since this specification requires an authorization gesture to create OTP Credential.[[CollectFromCredentialStore]](), of returning an empty set.

It is then the responsibility of OTPCredential.[[DiscoverFromExternalSource]]() to provide an OTP.

2.1. The OTPCredential Interface

The OTPCredential interface extends Credential and contains the attributes that are returned to the caller when a new one time password is retrieved.

OTPCredential's [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors), and defines its own implementation of [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors).

[SecureContext]
interface OTPCredential : Credential {
    readonly attribute DOMString code;
};
id

This attribute is inherited from Credential

[[type]]

The OTPCredential [[type]] internal slot's value is the string "otp".

code, of type DOMString, readonly

The retrieved one time password.

2.1.1. The [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) Method

This method is called every time navigator.credentials.get({otp:..., ...}) and is responsible for returning an OTP when one is requested (i.e. when options.otp is passed).

This internal method accepts three arguments:

origin

This argument is the origin, as determined by the calling Request a Credential abstract operation.

options

This argument is a CredentialRequestOptions object whose options.otp member contains a OTPCredentialRequestOptions object specifying the desired attributes of the OTP to retrieve.

sameOriginWithAncestors

This argument is a Boolean value which is true if and only if the caller’s same-origin with its ancestors. It is false if caller is cross-origin.

Note: Invocation of this internal method indicates that it was allowed by permissions policy, which is evaluated at the [CREDENTIAL-MANAGEMENT-1] level. See § 2.5 Permissions Policy integration.

Note: This algorithm is synchronous: the navigator.credentials.get().

When this method is invoked, the user agent MUST execute the following algorithm:

  1. Assert: options.otp is present.

  2. Let options be the value of options.otp.

  3. Let callerOrigin be origin. If callerOrigin is an NotAllowedError", and terminate this algorithm.

  4. Let effectiveDomain be the callerOrigin’s effective domain. If SecurityError" and terminate this algorithm.

    Note: An host, which can be represented in various manners, such as opaque host, or empty host. Only the host is allowed here. This is for simplification and also is in recognition of various issues with using direct IP address identification in concert with PKI-based security.

  5. If the options.AbortError" and terminate this algorithm.

  6. TODO(goto): figure out how to connect the dots here with the transport algorithms.

During the above process, the user agent SHOULD show some UI to the user to guide them in the process of sharing the OTP with the origin.

2.2. CredentialRequestOptions

To support obtaining OTPs via navigator.credentials.get(), this document extends the CredentialRequestOptions dictionary as follows:

partial dictionary CredentialRequestOptions {
    OTPCredentialRequestOptions otp;
};
otp, of type OTPCredentialRequestOptions

This OPTIONAL member is used to make WebOTP requests.

2.3. OTPCredentialRequestOptions

The OTPCredentialRequestOptions dictionary supplies navigator.credentials.get() with the data it needs to retrieve an OTP.

dictionary OTPCredentialRequestOptions {
  sequence<OTPCredentialTransportType> transport = [];
};
transport, of type sequence<OTPCredentialTransportType>, defaulting to []

This OPTIONAL member contains a hint as to how the server might receive the OTP. The values SHOULD be members of OTPCredentialTransportType but client platforms MUST ignore unknown values.

2.4. OTPCredentialTransportType

enum OTPCredentialTransportType {
    "sms",
};
User Agents may implement various transport mechanisms to allow the retrieval of OTPs. This enumeration defines hints as to how user agents may communicate with the transport mechanisms.
sms

Indicates that the OTP is expected to arrive via SMS.

2.5. Permissions Policy integration

This specification defines one policy-controlled feature identified by the feature-identifier token "otp-credentials". Its default allowlist is 'self'. [Permissions-Policy]

A allowed to successfully invoke the WebOTP API, i.e., via navigator.credentials.get({otp: { transport: ["sms"]}}). If disabled in any document, no content in the document will be return an error.

2.6. Using WebOTP within iframe elements

The WebOTP API is available in inner frames when the origins match but it’s disabled by default in cross-origin iframes. To override this default policy and indicate that a cross-origin allow attribute’s value.

Relying Parties utilizing the WebOTP API in an embedded context should review § 4.4 Visibility Considerations for Embedded Usage regarding UI redressing and its possible mitigations.

3. Transports

We expect a variety of different transport mechanisms to enable OTPs to be received, most notably via SMS, email and hardware devices.

Each of these transport mechanisms will need their own conventions on how to provide OTPs to the browser.

In this draft, we leave the API surface to be extensible to any number of transports.

3.1. SMS

One of the most commonly used transport mechanisms for OTP is via SMS messages, allowing developers to verify phone numbers. They are typically sent embedded in an SMS message, which gets copied and pasted by users.

[sms-one-time-codes] defines origin-bound one-time code messages, a format for sending OTPs over SMS and associating them with origins.

4. Security

From a security perspective, there are two considerations with this API:

  • tampering: preventing attacks based on the modification of the message.

4.1. Availability

This API is only available on:

  • https (or localhost, for development purposes)

This API is also only available via https or localhost (for development purposes). We don’t entirely adopt the concept of trustworthy urls because it covers more schemes (e.g. data://123) than we would like to (our initial intuition is that (a) https and localhost covers most cases and (b) it needs to be clear to the user what public facing entity its sending the SMS).

4.2. Addressing

Each transport mechanism is responsible for guaranteeing that the browser has enough information to route the OTP appropriately to the intended origin.

For example, origin-bound one-time code messages explicitly identify the origin on which the OTP can be used.

The addressing scheme must be enforced by the agent to guarantee that it gets routed appropriately.

4.3. Tampering

There isn’t any built-in cryptographic guarantee that the OTP that is being handed back by this API hasn’t been tampered with. For example, an attacker could send an origin-bound one-time code message to the user’s phone with an arbitrary origin which the agent happilly passes back to the requesting call.

Your verification code is: MUAHAHAHA

@example.com #MUAHAHAHA

It is the responsibility for the caller to:

  • put in place the checks necessary to verify that the OTP that was received is a valid one, for example:

    • parsing it carefully according to its known formatting expectations (e.g. only alpha numeric values),

    • storing and checking OTPs that were sent on a server side database.

  • degrade gracefully when an invalid OTP is received (e.g. re-request one).

4.4. Visibility Considerations for Embedded Usage

Simplistic use of WebOTP in an embedded context, e.g., within Clickjacking". This is where an attacker overlays their own UI on top of a Relying Party's intended UI and attempts to trick the user into performing unintended actions with the Relying Party. For example, using these techniques, an attacker might be able to trick users into purchasing items, transferring money, etc.

5. Privacy

From a privacy perspective, the most notable consideration is for a user agent to enforce the consensual exchange of information between the user and the website.

Specifically, this API allows the programatic verification of personally identifiable attributes of the user, for example email addresses and phone numbers.

The attack vector that is most frequently raised is a targeted attack: websites trying to find a very specific user accross all of its user base. In this attack, if left unattended, a website can use this API to try to find a specific user that owns a specific phone number by sending all / some (depending on the confidence level) of its users an origin-bound one-time code message and detecting when one is received.

Notably, this API doesn’t help with the acquisition of the personal information, but rather with its verification. That is, this API helps verifying whether the user owns a specific phone number, but doesn’t help acquiring the phone number in the first place (it assumes that the website already has access to it).

Nonetheless, the verification of the possession of these attributes is extra information about the user and should be handled responsibly by a user agent, typically via permission prompts before handing back the OTP to the website.

6. Acknowledgements

Many thanks to Steven Soneff, Ayu Ishii, Reilly Grant, Eiji Kitamura, Alex Russell, Owen Campbell-Moore, Joshua Bell, Ricky Mondello and Mike West for helping craft this proposal.

Special thanks to Tab Atkins, Jr. for creating and maintaining Bikeshed, the specification authoring tool used to create this document, and for his general authoring advice.

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.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification

Terms defined by reference

  • [CREDENTIAL-MANAGEMENT-1] defines the following terms:
    • Credential
    • CredentialRequestOptions
    • CredentialsContainer
    • Request a Credential
    • [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
    • [[type]]
    • get()
    • id
    • same-origin with its ancestors
    • signal
    • user mediation
  • [DOM] defines the following terms:
    • Document
    • aborted flag
    • host
  • [FETCH] defines the following terms:
    • credentials
  • [HTML] defines the following terms:
    • allow
    • allowed to use
    • domain
    • effective domain
    • environment settings object
    • iframe
    • opaque origin
    • origin
    • permissions policy
    • relevant settings object
  • [Permissions-Policy] defines the following terms:
    • default allowlist
    • policy-controlled feature
  • [sms-one-time-codes] defines the following terms:
    • origin-bound one-time code message
  • [URL] defines the following terms:
    • empty host
    • ipv4 address
    • ipv6 address
    • opaque host
  • [WebIDL] defines the following terms:
    • AbortError
    • DOMException
    • DOMString
    • Exposed
    • NotAllowedError
    • Promise
    • SecureContext
    • SecurityError
    • interface object
    • sequence

References

Normative References

[CREDENTIAL-MANAGEMENT-1]
Mike West. https://www.w3.org/TR/credential-management-1/
[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/
[HTML]
Anne van Kesteren; et al. https://html.spec.whatwg.org/multipage/
[Permissions-Policy]
Ian Clelland. https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
S. Bradner. https://tools.ietf.org/html/rfc2119
[SMS-ONE-TIME-CODES]
https://wicg.github.io/sms-one-time-codes/
[WebIDL]
Boris Zbarsky. https://heycam.github.io/webidl/

Informative References

[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/

IDL Index

[SecureContext]
interface OTPCredential : Credential {
    readonly attribute DOMString code;
};

partial dictionary CredentialRequestOptions {
    OTPCredentialRequestOptions otp;
};

dictionary OTPCredentialRequestOptions {
  sequence<OTPCredentialTransportType> transport = [];
};

enum OTPCredentialTransportType {
    "sms",
};

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant