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.
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:
1.4. Web Components
For the most part, OTP verification largely relies on:
-
input, forms and copy/paste, on the client side and
-
third party frameworks to send SMS, on the server side.
We expect some of these frameworks to develop declarative versions of this API to facilitate the deployment of their customer’s existing code.
< 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:
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 navigator.credentials.get()
looks through all of the interfaces that inherit from Credential
in the Request a Credential
abstract operation.
In that operation, it finds OTPCredential
which inherits from Credential
. It calls OTPCredential.
to collect any credentials that
should be available without user mediation, and if it does not find
exactly one of those, it then calls [[CollectFromCredentialStore]]()
OTPCredential.
to have
the user select a credential source and fulfill the request.[[DiscoverFromExternalSource]]()
Since this specification requires an authorization gesture to create OTP credentials, the OTPCredential.
internal method inherits the default behavior of [[CollectFromCredentialStore]]()
Credential.[[CollectFromCredentialStore]]()
, of returning an empty set.
It is then the responsibility of OTPCredential.
to provide an OTP.[[DiscoverFromExternalSource]]()
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 interface object inherits Credential
's implementation of [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
, and defines its own
implementation of [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
.
[Exposed =Window ,SecureContext ]interface :
OTPCredential Credential {readonly attribute DOMString code ; };
id
-
This attribute is inherited from
Credential
[[type]]
-
The
OTPCredential
interface object's[[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
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
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.
is passed).otp
This internal method accepts three arguments:
origin
-
This argument is the relevant settings object's origin, as determined by the calling
get()
implementation, i.e.,CredentialsContainer
's Request aCredential
abstract operation. options
-
This argument is a
CredentialRequestOptions
object whoseoptions.
member contains aotp
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 environment settings object is same-origin with its ancestors. It isfalse
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 Promise
resolution/rejection is handled by navigator.credentials.get()
.
When this method is invoked, the user agent MUST execute the following algorithm:
-
Let options be the value of
options.
.otp
-
Let callerOrigin be
origin
. If callerOrigin is an opaque origin, return aDOMException
whose name is "NotAllowedError
", and terminate this algorithm. -
Let effectiveDomain be the callerOrigin’s effective domain. If effective domain is not a valid domain, then return a
DOMException
whose name is "SecurityError
" and terminate this algorithm.Note: An effective domain may resolve to a host, which can be represented in various manners, such as domain, ipv4 address, ipv6 address, opaque host, or empty host. Only the domain format of 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.
-
If the
options.
is present and its aborted flag is set tosignal
true
, return aDOMException
whose name is "AbortError
" and terminate this algorithm. -
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" , };
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 Document
's permissions policy determines whether any content in that document is 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 allowed to use the foregoing methods: attempting to do so will 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 iframe
s.
To override this default policy and indicate that a cross-origin iframe
is allowed to invoke the WebOTP API's [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
method, specify the allow
attribute on the iframe
element and include the otp-credentialst
feature-identifier token in the 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.
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 iframe
s as described in § 2.6 Using WebOTP within iframe elements, may make users vulnerable to UI Redressing attacks, also known as "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.