事例紹介

WhatsApp がパスキーを使用して 10 億人のユーザーに安全でシームレスなログインを提供した方法

所要時間: 8 分

WhatsApp は、世界中の数十億人のユーザーにサービスを提供する、世界最大のメッセージ プラットフォームです。さまざまな地域の人々にとってデフォルトのコミュニケーション ツールであり、プライベートで信頼性が高く、安全なメッセージングを通じてユーザーをつないでいます。

「私が最も興奮しているのは、WhatsApp の影響力の大きさです。WhatsApp への小さな改善でも、世界中の何十億ものユーザーに影響を与えます」と、WhatsApp の登録およびアクセス チームの Android エンジニアで、WhatsApp のパスキーベースの認証の設計と実装を主導した Mayank Manuja 氏は述べています。

これほど大規模なユーザー層を対象に開発を行うには、さまざまなネットワーク条件、デバイス機能、デジタル リテラシーのレベルに対応する必要があります。WhatsApp は、パスキーの可能性を早期に認識し、2023 年にパスキーの採用を決定しました。これにより、このテクノロジーを統合した最初の大手ユーザーアプリの一つとなりました。パスキーを実装することで、WhatsApp は、アカウントの乗っ取りや認証情報の盗難に対する堅牢な保護を提供しながら、ユーザーの負担を大幅に軽減する、フィッシング耐性のある高速なオプションを提供することを目指しました。   

1787852638767.gif
WhatsApp でパスキーを作成して、より迅速かつ安全にログインできるようにしているユーザー。

パスキーの採用を決定

WhatsApp では、複数のアクセス方法を提供することが、ユーザーが接続を維持し、必要に応じてアクセスを回復できるようにするための鍵となります。パスキーを使用すると、ユーザーはワンタップでログインできるため、フィッシングのリスクを排除できます。また、OTP メッセージの配信が不安定な地域でも確実に機能します。

パスキーは、公開鍵と秘密鍵の暗号化を利用して、手動入力の代わりに生体認証または画面ロック認証を使用します。このワークフローでは、統合されたボトムシート インターフェースを介してプロセスを 1 回のタップに減らすことで、ログイン速度が大幅に向上し、ユーザーはアプリのコンテキスト内でエンゲージメントを維持できます。パスキーには 2 つのメリットがあります。1 つは、ユーザーに効率的なログイン エクスペリエンスを提供できること、もう 1 つは、フィッシング攻撃に対する堅牢なネイティブ保護を提供できることです。重要なのは、従来の SMS OTP 配信が不安定な地域でも、確実に機能することです。

unnamed.png
公開鍵暗号を使用してパスキーを保存し、認証に使用する方法
AANDDM_KARROT_Quote_02.png

堅牢で多様なアカウント アクセス方法を用意することで、ユーザーが重要なアカウントにアクセスできなくなる事態を防ぐことができます。

クライアントサイドの統合

WhatsApp のデベロッパーの観点から見ると、Credential Manager API は、基盤となる認証情報プロバイダの複雑さを抽象化した、クリーンで統一されたインターフェースを提供します。最初の統合フローがマッピングされると、API サーフェスはシンプルになり、認証情報の作成と取得は明確に定義されたリクエストとレスポンスのパターンに従うようになりました。実装ガイドについては、Android デベロッパー向けドキュメントをご覧ください。

ハッピーパスは最初から機能していましたが、OEM、複数の Android バージョン、さまざまなデバイス設定(PIN のみと生体認証、Android 13 と Android 14 以降など)にわたる多様なユーザーベースをナビゲートする中で、これまでにないエッジケースが表面化しました。これには、画面ロックが設定されていないユーザー、予期しない例外タイプ、古い Play 開発者サービス、一貫性のない認証情報プロバイダの動作などが含まれます。

これらの課題を克服するため、WhatsApp と Google のチームは緊密に連携し、いくつかの課題に取り組みました。

  • 認証情報の検索フローの最適化: 最初の検索フローでは、特にパスキーをまだ作成していないユーザーの場合、レイテンシが低下していました。WhatsApp ユーザーの大部分は初期段階でこのバケットに該当するため、ほぼすべてのログインで顕著な遅延が発生していました。コールパスを計測し、ボトルネックを特定することで、WhatsApp はプロセスを大幅に高速化し、最終的に Android エコシステム全体にメリットをもたらすパフォーマンスの向上を実現しました。
  • 一時的な状態の処理: WhatsApp は、パスワード マネージャーの可用性、画面ロックが設定されていない、断続的な接続性の問題、互換性のないハードウェア、古い Play 開発者サービスなど、デバイス固有のハードルを回避するために、包括的なエラー処理レイヤを構築し、例外を回復可能な状態と終了状態に分類しました。これにより、グレースフル デグラデーションが可能になり、パスキーフローが完了できなかった場合でも、システムは安全に従来の認証にフォールバックし、ユーザーが破損した状態になるのを防ぐことができました。
  • OS 固有の例外への対応: テレメトリーにより、特定の Android 13 デバイスでの GetPublicKeyCredentialDomException(認証情報の復号に失敗)や、Android 14 でのパスキー作成時の CreatePublicKeyCredentialDomException(同期アカウントを取得できない)など、デバイス固有のハードルが明らかになった場合、Google と WhatsApp チームは根本原因を調査し、プラットフォーム レベルの改善を実装して、作成フローをよりスムーズにしました。認証情報マネージャーに関連する一般的なエラーコードとその説明、およびその原因に関する情報を記載した包括的なエラーガイドは、こちらでご覧いただけます。
注: 詳しくは、パスキーのベスト プラクティスに関するブログで、パスキーを導入する際にユーザー エクスペリエンスを最適化する方法をご確認ください。

ユーザー エクスペリエンスの改善

2023 年初頭の時点では、パスキーはまったく新しいコンセプトであったため、パスキーの作成を促すための確立されたパターンはありませんでした。WhatsApp は、広範な A/B テストを通じて、最もメリットを得られるユーザーをターゲットとするコンテキスト フレームワークを開発しました。この戦略は継続的に進化しました。Android OS のフローが合理化された単一画面のエクスペリエンスに成熟するにつれて、WhatsApp は独自のプロンプトを簡素化し、冗長な UI や混乱を招く UI を回避しました。

Case-Study-1.png
WhatsApp の効率化された単一画面のパスキー作成フロー

サーバーサイドのアーキテクチャとクロスプラットフォームの課題

バックエンドでは、WhatsApp のサーバーが標準の WebAuthn/FIDO2 セレモニーを実装しています。バックエンドは Erlang で記述されており、ネイティブ インターフェースを介して Rust webauthn-rs ライブラリを呼び出します。この Rust ライブラリは、署名の検証と認証情報の解析を処理します。これにより、内部コードは、オーケストレーション、ストレージ、プロダクト ルール(利用資格、レート制限、認証情報のライフサイクルなど)に集中できます。

サーバー アーキテクチャは、4 つの主要なエントリ ポイントを介してこれらのコア セレモニーをオーケストレートします。これらのエントリ ポイントは、登録と認証の両方で Begin シーケンスと Finish シーケンスにペアリングされます。

1. パスキーの登録

このシーケンスでは、クライアントへの作成オプションの発行、クライアントが作成の成功を確認した後の構成証明の検証、認証情報の安全な永続化を処理します。

unnamed (1).png
パスキー登録時のサーバーとクライアントのインタラクション アーキテクチャ

アーラン: 登録を開始

begin_registration(UserId) ->
    Existing = list_credentials(UserId),
    %% reuse the existing user handle, or mint a new one
    {UserHandle, IsNew} = user_handle(Existing),
    %% returns the client creation options and the server-side challenge state
    #{client_safe := CreationOptions, server_only := ChallengeState} =
        webauthn:start_registration(UserId, UserHandle, rp_config()),
    %% excludeCredentials: the user's existing credential IDs, so the device won't re-enroll one
    Options = with_exclude_credentials(CreationOptions, credential_ids(Existing)),
    store_challenge(UserId, ChallengeState),          %% short TTL
    IsNew andalso reserve_user_handle(UserId, UserHandle),
    Options.
  • ユーザーを特定する: サーバーはまず、既存の認証情報を確認して、既存のユーザー ハンドルを再利用するか、新しいユーザー ハンドルを生成します。
  • オプションとチャレンジを生成する: WebAuthn ライブラリを呼び出して、クライアントの作成オプションとサーバーの安全なチャレンジ状態を生成します。
  • 重複を防ぐ: ユーザーの既存の認証情報 ID を明示的に除外することで、デバイスがすでに登録されているパスキーを誤って再登録しないようにします。
  • チャレンジを保存する: サーバーは、短い有効期間(TTL)でチャレンジを一時的に保存し、オプションをクライアント デバイスに返します。

Erlang: 登録を完了する

finish_registration(UserId, Attestation) ->
    ChallengeState = get_challenge(UserId),          %% must exist and be unexpired
    #{credential_id := CredId, public_key := PubKey} =
        webauthn:finish_registration(Attestation, ChallengeState, rp_config()),
    ok = index_credential(CredId, UserId),            %% map credential_id -> account
    case multi_passkey_enabled(UserId) of
        true  -> add_credential(UserId, CredId, PubKey);      %% append (oldest evicted past the cap)
        false -> replace_credential(UserId, CredId, PubKey)   %% single-passkey mode
    end,
    notify_client(UserId, {passkey_created, CredId}),
    ok.
  • チャレンジを取得する: サーバーは、保存されているチャレンジを取得し、それがまだ存在していて期限切れになっていないことを確認します。
  • 構成証明を検証する: クライアントのレスポンス(構成証明)とチャレンジを WebAuthn ライブラリに渡してリクエストを検証し、新しい認証情報 ID と公開鍵を抽出します。
  • 認証情報をインデックスに登録する: 新しい認証情報 ID がユーザーのアカウントに直接マッピングされ、後で迅速に検索できるようになります。
  • 保存と管理の上限: マルチパスキー機能が有効になっているかどうかに応じて、サーバーは新しい認証情報をユーザーのリストに追加するか(上限に達した場合は最も古い認証情報を削除)、シングルパスキー モードで既存の認証情報を置き換えます。

2. 認証情報の認証

作成と同様に、アプリサーバーはログイン シーケンスをオーケストレートして認証フローを処理します。これには、クライアント認証が成功した後のアサーションの検証や、WebAuthn が更新が必要であることを通知したときに保存された認証情報を動的に更新することが含まれます。

Erlang: 認証を開始する

begin_authentication(UserId) ->
    Credentials = list_valid_credentials(UserId),
    #{client_safe := RequestOptions, server_only := ChallengeState} =
        webauthn:start_authentication(Credentials, rp_config()),
    store_challenge(UserId, ChallengeState),          %% short TTL
    RequestOptions.
  • 有効な認証情報を取得する: サーバーは、ユーザーに関連付けられている現在有効な認証情報をすべて検索します。
  • チャレンジを生成する: これらの認証情報を使用してクライアントのリクエスト オプションを作成し、新しいサーバーサイド チャレンジを生成します。
  • 保存と返却: 登録と同様に、チャレンジは一時的に保存され、リクエスト オプションがクライアント アプリに渡されます。

Erlang: 認証を完了する

finish_authentication(UserId, Assertion) ->
    ChallengeState = get_challenge(UserId),
    Credentials = list_valid_credentials(UserId),
    case webauthn:finish_authentication(Credentials, Assertion, ChallengeState) of
        #{user_verified := true, credential_id := CredId, needs_update := NeedsUpdate} = Result ->
            %% webauthn tells us when the stored credential should be refreshed
            NeedsUpdate andalso refresh_credential(UserId, CredId, Result),
            mark_credential_used(UserId, CredId),
            {ok, CredId};
        _ ->
            {error, not_allowed}
    end.
  • アサーションを検証する: サーバーは、保存されているチャレンジと有効な認証情報を取得し、WebAuthn ライブラリにクライアントのアサーションを検証するようリクエストします。
  • 必要に応じて更新する: ユーザーが正常に確認されると、サーバーは needs_update フラグを確認します。WebAuthn ライブラリは、このフラグを使用して、保存されている認証情報の状態をサーバーで更新する必要があるかどうかを通知します。
  • 完了: サーバーは認証情報を使用済みとしてマークし、ログイン プロセスを正常に完了します。
Case-Study-2.png
WhatsApp アプリでのパスキー ログインの手順。

サーバー登録について詳しくは、こちらの統合ガイドをご覧ください。

高度なアーキテクチャに関する考慮事項 

サーバーにパスキーを大規模に実装するにあたっては、特にアカウント アーキテクチャとデバイスの同期に関して、独自の課題がありました。WhatsApp バックエンド チームの Ashish Choudhary 氏は、直面した主な課題として次の点を挙げました。

  1. アカウントごとに複数のパスキーへの移行: WhatsApp の以前のサーバー ロジックは、ユーザーごとに 1 つの認証情報を使用することを前提としていました。現代のマルチデバイスの現実に対応するため、上限に達すると最も古い認証情報をインテリジェントに削除する、制限付きリストシステムを設計しました。絶対的な安定性を確保するため、この大規模な構造シフトは、厳格なテストを通じて段階的に展開されました。
  2. 認証情報のライフサイクルのバランス調整: 認証情報の有効性を管理するには、慎重な対応が必要でした。認証情報の無効化を過度に行うと、不要な再登録が強制されることになります。一方、無効化を緩くしすぎると、古い認証情報が蓄積されることになります。WhatsApp は、バランスの取れたライフサイクル状態を実装することで、ユーザーに不満を与えることなく、厳格なセキュリティを維持しました。また、非アクティブなパスキーの自動バックグラウンド クリーンアップも行いました。

クロスデバイス同期の再考

この堅牢なマルチパスキー アーキテクチャにより、WhatsApp はクロスプラットフォームのユーザビリティを完全に再考することもできました。標準の WebAuthn クロスデバイス フローでは、1 台のデバイスで QR コードをスキャンし、もう 1 台のデバイスで Bluetooth 経由で認証する必要があります。しかし、WhatsApp は Bluetooth への依存が信頼性に欠けることを発見し、ユーザーは新しい QR コードを既存の WhatsApp Web リンク プロセスと混同することがよくありました。

WhatsApp では、脆弱なクロスデバイス転送メカニズムを強制するのではなく、Android の Google パスワード マネージャーや iOS の iCloud キーチェーンなど、複数のエコシステムにわたってパスキーをネイティブに保持できます。ユーザーが新しいプラットフォームに移行する場合は、次回のログイン時に新しいパスキーを生成するだけです。このアプローチはユーザーにとって完全に摩擦がなく、新しいマルチパスキー サーバー インフラストラクチャ上でシームレスに動作します。

今後の展望

パスキーのリリース以来、WhatsApp は膨大なユーザーベース全体で、オーガニックな導入が堅調に推移しています。従来の複数ステップのログイン プロセスを、1 回のシームレスな生体認証ジェスチャーに変換することで、アプリのユーザー エクスペリエンスが劇的に向上しました。この勢いに乗って、WhatsApp はパスキーの有用性を最初のログイン以外にも拡大し、パスキーで暗号化されたバックアップなど、機密性の高いアカウント操作のためのシームレスなアプリ内再認証を検討しています。

WhatsApp は今後、プラットフォーム パートナーと積極的に協力して、より摩擦の少ない認証情報作成パスを開拓していく予定です。デバイスの生体認証機能が拡大するにつれて、参入障壁は自然に減少すると予想されます。

大規模な開発を行うデベロッパー向けの推奨事項

パスキーの大規模な統合を準備しているデベロッパー向けに、WhatsApp チームは次の重要な推奨事項を共有しています。

  • エラーの分類に早期に投資する: 認証情報マネージャーのさまざまな例外を、回復可能な状態と回復不可能な状態に分類し、各シナリオに対して明確で適切なフォールバック パスを定義します。
  • 利用資格のファネルを把握する: 画面ロックの有無、生体認証ハードウェア、Google Play 開発者サービスのバージョンなど、デバイスの機能チェックを実装し、フローの途中で失敗するのではなく、利用資格のないユーザーを事前に除外するフローを設計します。
  • フォールバックに備えてアプリを準備する: 対応デバイスでは、最適なプライマリ認証方法としてパスキーを使用しますが、信頼性が高く汎用的なフォールバックとして、従来の方法も常に保持します。
  • OS バージョンの断片化を考慮する: パスキーの動作はオペレーティング システムによって異なる場合があります。Android 13、14、15 以降で十分にテストし、認証情報の選択 UI における OEM 固有のバリエーションを考慮します。
  • 状況に応じてアップセルと説明を行う: セキュリティ関連のアクション中に、パスキーの作成を自然に提案します。ユーザーの導入を促進するために、わかりやすい言葉で価値提案(スピードとセキュリティ)を明確に強調します。
  • プロアクティブにモニタリングする: エコシステムは OS のアップデートごとに進化します。レイテンシとエラーのパターンを継続的に追跡して、変化するデバイス環境に先手を打つ。
AANDDM_Passkeys_Quote_01.png

パスキーと認証情報マネージャーを使ってみる

Android でパスキーと認証情報マネージャーを実際に使用するには、統合ガイドと公開サンプルコードをご覧ください。

ご不明な点や問題がございましたら、Android Credentials Issue Tracker からお知らせください。

作成者:
続きを読む