WebOTP API

草案コミュニティグループ報告書、

このバージョン:
http://wicg.github.io/WebOTP
テストスイート:
https://github.com/web-platform-tests/wpt/tree/master/sms
課題追跡:
GitHub
編集者:
(Google Inc.)

概要

資格情報(例:電話番号、メールアドレス)を認証するためのワンタイムパスワードを要求するJavascript APIです。

この文書のステータス

この仕様は Web Platform Incubator Community Group によって公開されました。 これはW3Cの標準ではなく、W3C標準トラックにもありません。 W3C Community Contributor License Agreement (CLA) のもと、限定的なオプトアウトやその他の条件が適用されることにご注意ください。 W3C Community and Business Groups についてさらに詳しく学ぶことができます。

logo

1. 導入

このセクションは規範的ではありません。

多くのウェブサイトは認証フローの一部として資格情報(例:電話番号やメールアドレス)の検証を必要とします。現在は、これらの通信チャネルにワンタイムパスワード(OTP)を送信し、所有権の証明として利用しています。ワンタイムパスワードは手動でユーザーがウェブアプリに渡す必要があり(通常はコピー&ペースト)、これは面倒で誤りも発生しやすいです。

これは、ウェブサイトがOTPを要求できるクライアントサイドのJavaScript APIと、ブラウザと協調して利用できる一連のトランスポート固有の規約(まずはSMSから開始し、他の手段にも拡張可能)を提案するものです。

1.1. クライアントサイドAPI

この提案では、ウェブサイトは特定のトランスポート(例:SMS)から受信するOTPを取得するためにブラウザAPIを呼び出すことができます。

ブラウザはSMSの受信と呼び出し元ウェブサイトへの受け渡しを仲介し(通常はユーザーの同意を求める)、APIは非同期でPromiseを返します。

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

1.2. サーバーサイドAPI

クライアントサイドAPIが呼び出されると、ウェブサイトのサーバーは要求されたトランスポート手段を使ってOTPをクライアントに送信できます。各トランスポート手段ごとに、安全かつプログラム的にOTPが配信されることを保証するサーバーサイドの規約が設定されます。

SMSの場合、サーバーはオリジンバウンドワンタイムコードメッセージをクライアントに送信する必要があります。[sms-one-time-codes]

次のオリジンバウンドワンタイムコードメッセージでは、ホストは"example.com"、コードは"123456"、説明文は"Your authentication code is 123456.\n"です。

"Your authentication code is 123456.

@example.com #123456"

1.3. 機能検出

すべてのユーザーエージェントが必ずしも同じタイミングでWebOTP APIを実装するわけではないため、ウェブサイトはAPIが利用可能かどうかを検出する仕組みが必要です。

ウェブサイトはOTPCredentialグローバルインターフェースの有無を確認できます:

if (!window.OTPCredential) {
  // 機能が利用できません
  return;
}

1.4. Webコンポーネント

主にOTP認証は以下に依存しています:

これらのフレームワークの一部は、顧客の既存コードの導入を容易にするために、このAPIの宣言的バージョンを開発することが期待されます。

Webコンポーネントのポリフィル
<script src="sms-sdk.js"></script>

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

また、フレームワークがWebコンポーネントを使ってどのように実装できるかの例です:

Webコンポーネントのポリフィル
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

多くのモダンなウェブサイトはクライアントサイドでナビゲーションを処理します。そのため、ユーザーがOTPフローから他のフローへ移動した場合、リクエストをキャンセルして、不要な権限プロンプトでユーザーが煩わされないようにする必要があります。

そのために、リクエストを中断するためのabort controllerを渡すことができます:

const abort = new AbortController();

setTimeout(() => {
  // 2分後に中断
  abort.abort();
}, 2 * 60 * 1000);
  
let {code, type} = await navigator.credentials.get({
  signal: abort.signal,
  otp: {
    transport: ["sms"]
  }
});

2. クライアントサイドAPI

ウェブサイトは navigator.credentials.get({otp:..., ...}) を呼び出してOTPを取得します。

navigator.credentials.get() のアルゴリズムは、Request a Credential 抽象操作において、 Credential を継承するすべてのインターフェースを調べます。

その操作内で、OTPCredential を見つけ、これは Credential を継承しています。 そして OTPCredential.[[CollectFromCredentialStore]]() を呼び出して、資格情報のうち ユーザー操作なしで利用可能なものを収集します。もしそのような資格情報がちょうど1つ見つからなかった場合、 OTPCredential.[[DiscoverFromExternalSource]]() を呼び出して、ユーザーに資格情報のソースを選択させリクエストを完了させます。

この仕様はOTP 認可ジェスチャ の作成を要求するため、 OTPCredential.[[CollectFromCredentialStore]]() 内部メソッドCredential.[[CollectFromCredentialStore]]() のデフォルト動作(空集合を返す)を継承します。

したがって OTPCredential.[[DiscoverFromExternalSource]]() がOTPを提供する責任を持ちます。

2.1. OTPCredentialインターフェース

OTPCredential インターフェースは Credential を拡張し、 新しいワンタイムパスワードが取得された際に呼び出し元へ返される属性を含みます。

OTPCredentialインターフェースオブジェクトCredential[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) の実装を継承し、 独自実装の [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) を定義します。

[Exposed=Window, SecureContext]
interface OTPCredential : Credential {
    readonly attribute DOMString code;
};
id

この属性は Credential から継承されます。

[[type]]

OTPCredentialインターフェースオブジェクトにおける [[type]] 内部スロットの値は "otp" です。

code, 型は DOMString、readonly

取得されたワンタイムパスワードです。

2.1.1. [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) メソッド

このメソッドは navigator.credentials.get({otp:..., ...}) が呼び出されるたびに呼び出され、OTPが要求された際(すなわち options.otp が渡された場合)にOTPを返す役割を持ちます。

この 内部メソッド は3つの引数を受け取ります:

origin

この引数は、呼び出し元 get() 実装で決定される 関連設定オブジェクトorigin です。すなわち CredentialsContainerRequest a Credential 抽象操作です。

options

この引数は CredentialRequestOptions オブジェクトであり、 options.otp メンバーが取得したいOTPの属性を指定する OTPCredentialRequestOptions オブジェクトを含みます。

sameOriginWithAncestors

この引数は、呼び出し元の 環境設定オブジェクト祖先と同一オリジンである場合に限り true となる真偽値です。呼び出し元がクロスオリジンの場合は false です。

注: この 内部メソッド の呼び出しは permissions policy により許可されていることを示します。これは [CREDENTIAL-MANAGEMENT-1] のレベルで評価されます。 § 2.5 Permissions Policy integration 参照。

注: このアルゴリズムは同期的です: Promise のresolve/rejectは navigator.credentials.get() によって処理されます。

このメソッドが呼び出された際、ユーザーエージェントは次のアルゴリズムを実行しなければなりません:

  1. 確認: options.otp存在すること。

  2. optionsoptions.otp の値とする。

  3. callerOriginorigin とする。 callerOrigin不透明オリジンの場合は、名前が "NotAllowedError" の DOMException を返し、このアルゴリズムを終了する。

  4. effectiveDomaincallerOrigin有効ドメイン とする。 有効ドメイン有効なドメインでない場合は、名前が "SecurityError" の DOMException を返し、このアルゴリズムを終了する。

    注: 有効ドメインホストに解決される場合があり、ドメイン、IPv4アドレスIPv6アドレス不透明ホスト空ホストなど様々な表現が可能です。 ここでは ドメイン形式のみ許可されます。これは簡素化のため、またPKIベースのセキュリティで 直接IPアドレス識別を使用することの諸問題を考慮したものです。

  5. options.signal存在し、その aborted flagtrue の場合は、 名前が "AbortError" の DOMException を返し、このアルゴリズムを終了する。

  6. TODO(goto): トランスポートアルゴリズムとの接続方法を検討。

上記の処理中、ユーザーエージェントはユーザーがオリジンとOTPを共有するプロセスをガイドするUIを表示するべきです。

2.2. CredentialRequestOptions

OTPを navigator.credentials.get() で取得できるようにするため、本書では CredentialRequestOptions 辞書を次のように拡張します:

partial dictionary CredentialRequestOptions {
    OTPCredentialRequestOptions otp;
};
otp, 型は OTPCredentialRequestOptions

このオプションのメンバーはWebOTPリクエストに使用されます。

2.3. OTPCredentialRequestOptions

OTPCredentialRequestOptions 辞書は、 navigator.credentials.get() に OTPを取得するために必要なデータを提供します。

dictionary OTPCredentialRequestOptions {
  sequence<OTPCredentialTransportType> transport = [];
};
transport, 型は sequence<OTPCredentialTransportType>、デフォルト値は []

このオプションのメンバーは、サーバーがOTPをどのように受け取るかのヒントを含みます。 値は OTPCredentialTransportType のメンバーであるべきですが、クライアントプラットフォームは未知の値を無視しなければなりません。

2.4. OTPCredentialTransportType

enum OTPCredentialTransportType {
    "sms",
};
ユーザーエージェントはOTPの取得のために様々なトランスポート手段を実装する場合があります。この列挙体は、ユーザーエージェントがトランスポート手段とどう通信するかのヒントを示します。
sms

OTPがSMSで届くことが期待されることを示します。

2.5. Permissions Policyとの統合

この仕様は、feature-identifierトークン "otp-credentials" で識別される ポリシー制御機能 を1つ定義します。 その デフォルト許可リスト は 'self' です。[Permissions-Policy]

Documentpermissions policy は、その 文書内のコンテンツが WebOTP APIを正常に使用できるか を決定します。すなわち navigator.credentials.get({otp: { transport: ["sms"]}}) の呼び出しが可能かどうかです。 いずれかの文書で無効化されている場合、その文書内の全コンテンツはこれらのメソッドを 使用できません。使用しようとすると エラーが返されます

2.6. iframe要素でWebOTPを利用する

WebOTP APIは、オリジンが一致していれば内部フレームで利用可能ですが、クロスオリジン iframe ではデフォルトで無効となっています。 このデフォルトポリシーを上書きし、クロスオリジン iframe でも WebOTP API[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) メソッドを使用可能にするには、 allow 属性を iframe 要素に指定し、さらに otp-credentials feature-identifierトークンを allow 属性値に含めます。

WebOTP APIを埋め込み環境で利用するRelying Partiesは、§ 4.4 埋め込み利用時の可視性に関する考慮事項で説明されている UIの改ざんとその対策も確認してください。

3. トランスポート

OTPを受信可能にするため、さまざまなトランスポート手段が想定されます。代表的なものはSMS、メール、ハードウェアデバイスです。

それぞれのトランスポートには、OTPをブラウザへ提供するための独自の規約が必要です。

このドラフトでは、APIの表面は任意の数のトランスポートに拡張可能な設計となっています。

3.1. SMS

OTPの最も一般的なトランスポート手段の一つがSMSメッセージです。これにより開発者は電話番号の検証が可能となります。OTPは通常SMSメッセージに埋め込まれて送信され、ユーザーがコピー&ペーストして使用します。

[sms-one-time-codes]は、OTPをSMS経由で送信しオリジンと紐付けるための オリジンバウンドワンタイムコードメッセージの形式を定義しています。

4. セキュリティ

セキュリティの観点から、このAPIには2つの考慮点があります:

4.1. 可用性

このAPIが利用可能なのは以下の場合のみです:

このAPIはhttpsまたはlocalhost(開発用)でのみ利用可能です。信頼できるURLの概念は完全には採用していません。なぜならその概念は、私たちが想定しないスキーム(例:data://123)も含むためです。(初期の直感としては、(a) httpsとlocalhostでほとんどのケースをカバーできること、(b) SMSを送信する公開側のエンティティがユーザーに明確である必要があること、が挙げられます。)

4.2. アドレス指定

各トランスポートは、ブラウザがOTPを適切なオリジンにルーティングできるだけの情報を保証する責任があります。

たとえば、オリジンバウンドワンタイムコードメッセージは、OTPを使用できるオリジンを明示的に識別します。

アドレス指定方式は、エージェントによって強制され、適切にルーティングされることが保証されなければなりません。

4.3. 改ざん

このAPIが返すOTPが改ざんされていないという暗号的な保証は組み込まれていません。たとえば、攻撃者がユーザーの電話に任意のオリジンで オリジンバウンドワンタイムコードメッセージ を送り、エージェントがそれをリクエスト元へそのまま返してしまう可能性があります。

あなたの認証コードは: MUAHAHAHA

@example.com #MUAHAHAHA

呼び出し元が責任を持って、以下を行う必要があります:

4.4. 埋め込み利用時の可視性に関する考慮事項

WebOTPを埋め込み環境で単純に利用(例:iframe内で § 2.6 iframe要素内でのWebOTP利用 を参照)すると、ユーザーが UIレドレッシング攻撃(別名「クリックジャッキング」)の被害に遭う可能性があります。これは攻撃者が Relying Party の本来意図したUIの上に自身のUIを重ね、ユーザーに意図しない操作をさせる手法です。例として、こうした技法により攻撃者がユーザーに商品の購入や送金などをさせる可能性があります。

5. プライバシー

プライバシーの観点で最も重要なのは、ユーザーとウェブサイト間で情報交換が合意に基づいて行われるようユーザーエージェントが強制することです。

具体的には、このAPIにより、ユーザーの個人識別可能な属性(例:メールアドレスや電話番号)のプログラムによる検証が可能となります。

最も頻繁に挙げられる攻撃ベクトルは、ターゲット型攻撃です。すなわち、ウェブサイトが全ユーザーの中から特定のユーザーを探そうとするケースです。この攻撃では、放置するとウェブサイトがこのAPIを使い、全ユーザーや一部(信頼度による)に オリジンバウンドワンタイムコードメッセージ を送信し、誰かが受信したタイミングを検出することで 特定の電話番号を持つユーザーを探すことができます。

重要なのは、このAPIが個人情報の取得を助けるものではなく、その検証を支援するものだという点です。つまり、このAPIはユーザーが特定の電話番号を所有しているか検証するためのものであり、電話番号そのものを取得するものではありません(ウェブサイトがすでにアクセス権を持っていることが前提です)。

それでも、これら属性の所有の検証はユーザーの追加情報となるため、ユーザーエージェントは責任を持って管理すべきです。通常はOTPをウェブサイトへ返す前に権限プロンプトでユーザーの同意を得るべきです。

6. 謝辞

Steven Soneff, Ayu Ishii, Reilly Grant, Eiji Kitamura, Alex Russell, Owen Campbell-Moore, Joshua Bell, Ricky Mondello, Mike West の皆様に、この提案の作成にご協力いただき感謝申し上げます。

特にTab Atkins, Jr.氏には、仕様作成ツールBikeshedの開発・保守、そして執筆に関するアドバイスをいただき感謝いたします。

適合性

文書の慣例

適合性要件は、記述的なアサーションとRFC 2119の用語の組み合わせで表現されています。 規範的な部分における “MUST”、 “MUST NOT”、 “REQUIRED”、 “SHALL”、 “SHALL NOT”、 “SHOULD”、 “SHOULD NOT”、 “RECOMMENDED”、 “MAY”、 “OPTIONAL” というキーワードは、RFC 2119の定義通りに解釈されます。 ただし、読みやすさのため、本仕様書ではこれらの語はすべて大文字では記載されていません。

この仕様書のすべてのテキストは規範的ですが、 明示的に非規範的と記載されたセクション、例、および注記は除きます。[RFC2119]

この仕様書の例は「例えば」で始まるか、または規範的な本文から class="example" のように区別されています。 例:

これは情報提供的な例です。

情報提供的な注記は「注」として始まり、 class="note" により規範的な本文から区別されます。 例:

注: これは情報提供的な注記です。

適合するアルゴリズム

アルゴリズムの一部として命令形で表現された要件(例: "先頭の空白文字を除去する" や "falseを返してこれらの手順を中止する")は、 キーワード("must"、"should"、"may"など)がアルゴリズムの導入部で使われている意味で解釈されます。

アルゴリズムや特定の手順として表現された適合性要件は、どのような方法で実装しても構いませんが、 最終的な結果が同等であれば問題ありません。 特に、この仕様書で定義されたアルゴリズムは理解しやすいことを意図しており、性能を重視したものではありません。 実装者は最適化を推奨します。

索引

本仕様で定義される用語

参照によって定義される用語

参考文献

規定参考文献

[CREDENTIAL-MANAGEMENT-1]
Mike West. Credential Management Level 1. 2019年1月17日. WD. URL: https://www.w3.org/TR/credential-management-1/
[DOM]
Anne van Kesteren. DOM標準. 現行標準. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch標準. 現行標準. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; 他. HTML標準. 現行標準. URL: https://html.spec.whatwg.org/multipage/
[Permissions-Policy]
Ian Clelland. Permissions Policy. 2020年7月16日. WD. URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
S. Bradner. RFCで要件レベルを示すためのキーワード. 1997年3月. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SMS-ONE-TIME-CODES]
SMSで配信されるオリジンバウンドワンタイムコード. cg-draft. URL: https://wicg.github.io/sms-one-time-codes/
[WebIDL]
Boris Zbarsky. Web IDL. 2016年12月15日. ED. URL: https://heycam.github.io/webidl/

参考情報

[URL]
Anne van Kesteren. URL標準. 現行標準. URL: https://url.spec.whatwg.org/

IDL索引

[Exposed=Window, SecureContext]
interface OTPCredential : Credential {
    readonly attribute DOMString code;
};

partial dictionary CredentialRequestOptions {
    OTPCredentialRequestOptions otp;
};

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

enum OTPCredentialTransportType {
    "sms",
};