1. Introduction
684 |This section is non-normative.
685 |Many web sites need to verify credentials (e.g. phone numbers and 686 | email addresses) as part of their authentication flows. They currently 687 | rely on sending one-time-passwords (OTP) to these communication channels to 688 | be used as proof of ownership. The one-time-password is manually 689 | handed back by the user (typically by copying/pasting) to the web app 690 | which is onerous and erroneous.
691 |This a proposal for a client side javascript API that enables web 692 | sites to request OTPs and a set of transport-specific conventions (we 693 | start with SMS while leaving the door open to others) that can be used 694 | in coordination with browsers.
695 |1.1. The client side API
696 |In this proposal, websites have the ability to call a browser API to 697 | request OTPs coming from specific transports (e.g. via SMS).
698 |The browser intermediates the receipt of the SMS and the handing off 699 | to the calling website (typically asking for the user’s consent), so 700 | the API returns a promise asynchronously.
701 |709 |let { code, type} = await navigator. credentials. get({ 704 | otp: { 705 | transport: [ "sms" ] 706 |} 707 |}); 708 |
1.2. The server side API
711 |Once the client side API is called, the website’s server can send 712 | OTPs to the client via the requested transport mechanisms. For each 713 | of these transport mechanism, a server side convention is set in 714 | place to guarantee that the OTP is delivered safely and 715 | programatically.
716 |For SMS, for example, servers should send origin-bound one-time code messages to clients. [sms-one-time-codes]
717 |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. 721 | 722 | @example.com #123456" 723 |724 |
1.3. Feature Detection
726 |Not all user agents necessarily need to implement the WebOTP API at 727 | the exact same moment in time, so websites need a mechanism to detect 728 | if the API is available.
729 |Websites can check for the presence of the OTPCredential global 730 | interface:
731 |738 |if ( ! window. OTPCredential) { 734 |// feature not available 735 |return ; 736 |} 737 |
1.4. Web Components
740 |For the most part, OTP verification largely relies on:
741 |-
742 |
-
743 |
input, forms and copy/paste, on the client side and
744 | -
745 |
third party frameworks to send SMS, on the server side.
746 |
We expect some of these frameworks to develop declarative versions of 748 | this API to facilitate the deployment of their customer’s existing 749 | code.
750 |759 |< script src = "sms-sdk.js" ></ script > 753 | 754 |< form > 755 |< input is = "one-time-code" required /> 756 |< input type = "submit" /> 757 |</ form > 758 |
And here is an example of how a framework could implement it 761 | using web components:
762 |customElements782 |. define( "one-time-code" , 765 |class extends HTMLInputElement{ 766 | connectedCallback() { 767 |this . receive(); 768 |} 769 |async receive() { 770 |let { code, type} = await navigator. credentials. get({ 771 | otp: { 772 | transport: [ "sms" ] 773 |} 774 |}); 775 |this . value= otp; 776 |this . form. submit(); 777 |} 778 |}, { 779 |extends : "input" 780 |}); 781 |
1.5. Abort API
784 |Many modern websites handle navigations on the client side. So, if a 785 | user navigates away from an OTP flow to another flow, the request 786 | needs to be cancelled so that the user isn’t bothered with a 787 | permission prompt that isn’t relevant anymore.
788 |To facilitate that, an abort controller can be passed to abort the 789 | request:
790 |806 |const abort= new AbortController(); 793 | 794 | setTimeout(() => { 795 |// abort after two minutes 796 | abort. abort(); 797 |}, 2 * 60 * 1000 ); 798 | 799 |let { code, type} = await navigator. credentials. get({ 800 | signal: abort. signal, 801 | otp: { 802 | transport: [ "sms" ] 803 |} 804 |}); 805 |
2. Client Side API
808 |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
811 | should be available without user mediation, and if it does not find
812 | exactly one of those, it then calls [[CollectFromCredentialStore]]()
OTPCredential.
to have
813 | 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
817 |The OTPCredential
interface extends Credential
and contains
818 | the attributes that are returned to the caller when a new one time
819 | password is retrieved.
OTPCredential
's interface object inherits Credential
's implementation of [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
, and defines its own
821 | implementation of [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
.
[827 |Exposed =Window ,SecureContext ] 823 |interface :
OTPCredential Credential { 824 |readonly attribute DOMString code ; 825 | }; 826 |
-
828 |
id
829 |-
830 |
This attribute is inherited from
831 |Credential
[[type]]
832 |-
833 |
The
835 |OTPCredential
interface object's[[type]]
internal slot's value is the string 834 | "otp
". code
, of type DOMString, readonly 836 |-
837 |
The retrieved one time password.
838 |
2.1.1. The [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
Method
840 | [[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:
842 |-
843 |
origin
844 |-
845 |
This argument is the relevant settings object's origin, as determined by the 846 | calling
847 |get()
implementation, i.e.,CredentialsContainer
's Request aCredential
abstract operation. options
848 |-
849 |
This argument is a
850 |CredentialRequestOptions
object whoseoptions.
member contains aotp
OTPCredentialRequestOptions
object specifying the desired attributes of the OTP to retrieve. sameOriginWithAncestors
851 |-
852 |
This argument is a Boolean value which is
853 |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. 854 | See § 2.5 Permissions Policy integration.
855 |
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:
858 |-
859 |
- 860 | 861 |
-
862 |
Let options be the value of
863 |options.
.otp
-
864 |
Let callerOrigin be
866 |origin
. 865 | If callerOrigin is an opaque origin, return aDOMException
whose name is "NotAllowedError
", and terminate this algorithm. -
867 |
Let effectiveDomain be the callerOrigin’s effective domain. 868 | If effective domain is not a valid domain, then return a
869 |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, 870 | such as domain, ipv4 address, ipv6 address, opaque host, or empty host. 871 | Only the domain format of host is allowed here. This is for simplification and also is 872 | in recognition of various issues with using direct IP address identification in concert with 873 | PKI-based security.
874 | -
875 |
If the
877 |options.
is present and its aborted flag is set tosignal
true
, return aDOMException
whose name is "AbortError
" 876 | and terminate this algorithm. -
878 |
TODO(goto): figure out how to connect the dots here with the transport algorithms.
879 |
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.
881 |2.2. CredentialRequestOptions
882 | To support obtaining OTPs via navigator.credentials.get()
,
883 | this document extends the CredentialRequestOptions
dictionary as follows:
888 |partial dictionary CredentialRequestOptions { 885 |OTPCredentialRequestOptions otp ; 886 | }; 887 |
-
890 |
otp
, of type OTPCredentialRequestOptions 891 |-
892 |
This OPTIONAL member is used to make WebOTP requests.
893 |
2.3. OTPCredentialRequestOptions
896 | The OTPCredentialRequestOptions
dictionary supplies navigator.credentials.get()
with the data it needs to retrieve an
897 | OTP.
902 |dictionary { 899 |
OTPCredentialRequestOptions sequence <OTPCredentialTransportType >transport = []; 900 | }; 901 |
-
904 |
transport
, of type sequence<OTPCredentialTransportType>, defaulting to[]
905 |-
906 |
This OPTIONAL member contains a hint as to how the server might receive the OTP. 907 | The values SHOULD be members of
908 |OTPCredentialTransportType
but client platforms MUST ignore unknown values.
2.4. OTPCredentialTransportType
911 | 915 |enum { 912 |
OTPCredentialTransportType "sms" , 913 | }; 914 |
-
920 |
sms
921 |-
922 |
Indicates that the OTP is expected to arrive via SMS.
923 |
2.5. Permissions Policy integration
926 |This specification defines one policy-controlled feature identified by
927 | the feature-identifier token "otp-credentials
".
928 | 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"]}})
.
930 | 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
932 | The WebOTP API is available in inner frames when the origins match but it’s disabled by default in cross-origin iframe
s.
933 | 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.
935 |3. Transports
936 |We expect a variety of different transport mechanisms to enable OTPs 937 | to be received, most notably via SMS, email and hardware devices.
938 |Each of these transport mechanisms will need their own conventions on 939 | how to provide OTPs to the browser.
940 |In this draft, we leave the API surface to be extensible to any number 941 | of transports.
942 |3.1. SMS
943 |One of the most commonly used transport mechanisms for OTP is via 944 | SMS messages, allowing developers to verify phone numbers. They 945 | are typically sent embedded in an SMS message, which gets copied and 946 | pasted by users.
947 |[sms-one-time-codes] defines origin-bound one-time code messages, a format for sending OTPs over SMS and associating them with origins.
948 |4. Security
949 |From a security perspective, there are two considerations with this 950 | API:
951 |-
952 |
-
953 |
tampering: preventing attacks based on the modification of the message.
954 |
4.1. Availability
956 |This API is only available on:
957 |-
958 |
-
959 |
https (or localhost, for development purposes)
960 |
This API is also only available via https or localhost (for development 962 | purposes). We don’t entirely adopt the concept of trustworthy urls 963 | because it covers more schemes (e.g. data://123) than we would like to 964 | (our initial intuition is that (a) https and localhost covers most 965 | cases and (b) it needs to be clear to the user what public facing 966 | entity its sending the SMS).
967 |4.2. Addressing
968 |Each transport mechanism is responsible for guaranteeing that the 969 | browser has enough information to route the OTP appropriately to the 970 | intended origin.
971 |For example, origin-bound one-time code messages explicitly 972 | identify the origin on which the OTP can be used.
973 |The addressing scheme must be enforced by the agent to guarantee that 974 | it gets routed appropriately.
975 |4.3. Tampering
976 |There isn’t any built-in cryptographic guarantee that the OTP that is 977 | being handed back by this API hasn’t been tampered with. For example, 978 | an attacker could send an origin-bound one-time code message to the 979 | user’s phone with an arbitrary origin which the agent happilly passes 980 | back to the requesting call.
981 | 988 |It is the responsibility for the caller to:
989 |-
990 |
-
991 |
put in place the checks necessary to verify that the OTP that was received 992 | is a valid one, for example:
993 |-
994 |
-
995 |
parsing it carefully according to its known formatting expectations 996 | (e.g. only alpha numeric values),
997 | -
998 |
storing and checking OTPs that were sent on a server side database.
999 |
-
995 |
-
1001 |
degrade gracefully when an invalid OTP is received (e.g. re-request one).
1002 |
4.4. Visibility Considerations for Embedded Usage
1004 |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
1006 |From a privacy perspective, the most notable consideration is for a user agent 1007 | to enforce the consensual exchange of information between the user and the 1008 | website.
1009 |Specifically, this API allows the programatic verification of personally 1010 | identifiable attributes of the user, for example email addresses and phone 1011 | numbers.
1012 |The attack vector that is most frequently raised is a targeted attack: 1013 | websites trying to find a very specific user accross all of its user 1014 | base. In this attack, if left unattended, a website can use this API to 1015 | try to find a specific user that owns a specific phone number by 1016 | sending all / some (depending on the confidence level) of its users an origin-bound one-time code message and detecting when one is received.
1017 |Notably, this API doesn’t help with the acquisition of the personal 1018 | information, but rather with its verification. That is, this API helps verifying whether the user owns a specific phone number, but doesn’t 1019 | help acquiring the phone number in the first place (it assumes that the 1020 | website already has access to it).
1021 |Nonetheless, the verification of the possession of these attributes is extra 1022 | information about the user and should be handled responsibly by a user 1023 | agent, typically via permission prompts before handing back the OTP 1024 | to the website.
1025 |6. Acknowledgements
1026 |Many thanks to 1027 | Steven Soneff, 1028 | Ayu Ishii, 1029 | Reilly Grant, 1030 | Eiji Kitamura, 1031 | Alex Russell, 1032 | Owen Campbell-Moore, 1033 | Joshua Bell, 1034 | Ricky Mondello and 1035 | Mike West 1036 | for helping craft this proposal.
1037 |Special thanks to Tab Atkins, Jr. for creating and maintaining Bikeshed, the specification 1038 | authoring tool used to create this document, and for his general 1039 | authoring advice.
1040 |