事例紹介

Zoho がパスキーと認証情報マネージャーの統合によりログイン速度を 6 倍に向上

所要時間: 10 分

Android デベロッパーは、セキュリティの強化、ユーザー エクスペリエンスの向上、開発の効率化を常に追求しています。セキュリティとシームレスなエクスペリエンスに重点を置いた包括的なクラウドベースのソフトウェア スイートである Zoho は、OneAuth Android アプリにパスキーを採用することで大幅な改善を実現しました。

2024 年にパスキーを統合して以来、Zoho は以前の方法と比較してログイン速度を最大 6 倍に向上 させ、パスキーの導入率を前月比(MoM)で 31% 増加 させました。

この事例紹介では、認証の難しさに対応するために Zoho がパスキーと Android の Credential Manager API を採用した経緯について説明します。技術的な実装プロセスについて詳しく説明し、その効果的な結果を紹介します。

認証の課題を克服する

Zoho は、ユーザー アカウントを保護するために複数の認証方法を組み合わせて使用しています。これには、Zoho 独自の多要素認証(MFA)ソリューションである Zoho OneAuth が含まれます。このソリューションは、プッシュ通知、QR コード、時間ベースのワンタイム パスワード(TOTP)を使用したパスワードベースの認証とパスワードレス認証の両方をサポートしています。Zoho はフェデレーション ログインもサポートしており、Security Assertion Markup Language(SAML)やその他のサードパーティ ID プロバイダによる認証を可能にしています。

課題

Zoho は、多くの組織と同様に、運用上の負担を軽減しながら、認証のセキュリティとユーザー エクスペリエンスの向上を目指していました。パスキーの採用につながった主な課題は次のとおりです。

  • セキュリティの脆弱性: 従来のパスワードベースの方法では、ユーザーがフィッシング攻撃やパスワード漏洩の影響を受けやすくなっていました。
  • ユーザーの不満: パスワードの使いすぎにより、パスワードを忘れたり、不満を感じたり、面倒な復元プロセスに頼ることが増えていました。
  • 運用上の非効率性: パスワードの再設定や MFA の問題の処理には、多大なサポート費用がかかっていました。
  • スケーラビリティに関する懸念: ユーザーベースの拡大に伴い、より安全で効率的な認証ソリューションが求められていました。

パスキーへの移行の理由

Zoho のアプリにパスキーを実装したのは、セキュリティとユーザー エクスペリエンスを大幅に向上させるパスワードレス アプローチを提供することで、認証の課題に対処するためです。このソリューションは、フィッシング耐性のある認証、クラウドで同期された認証情報によるクロスデバイスでの簡単なアクセス、生体認証(指紋認証、顔認識など)、PIN、パターンによる安全なログインを活用することで、従来のパスワードに関連する脆弱性や不便さを軽減します。

認証情報マネージャーでパスキーを採用することで、Zoho はログイン時間を最大 6 倍 に短縮し、パスワード関連のサポート費用を削減し、強力な ユーザー導入を実現しました。パスキーによるログインは 4 か月で倍増 し、前月比 31% の成長 を遂げました。Zoho のユーザーは、より迅速かつ簡単にログインでき、フィッシング耐性のあるセキュリティ を利用できるようになりました。

ANDDM_Zoho_Quote_fabrice.png

Android での認証情報マネージャーの実装

では、Zoho はどのようにしてこれらの成果を達成したのでしょうか。Android での認証の実装に推奨される Jetpack ライブラリである Android の Credential Manager API を使用しました。

認証情報マネージャーは、さまざまな認証方法の処理を簡素化する統合 API を提供します。パスワード、パスキー、フェデレーション ログイン(Google でログインなど)に別々の API を使用するのではなく、単一のインターフェースを使用します。

Zoho でパスキーを実装するには、クライアント側とサーバー側の両方で調整が必要でした。パスキーの作成、ログイン、サーバーサイドの実装プロセスの詳細を以下に示します。

パスキーの作成

passkey.png

パスキーを作成するには、まずアプリが Zoho のサーバーから構成の詳細を取得します。このプロセスには、指紋認証や顔認証などの一意の検証が含まれます。この認証データ(requestJson 文字列としてフォーマット)は、アプリが CreatePublicKeyCredentialRequest を作成するために使用されます。次に、アプリは credentialManager.createCredential メソッドを呼び出します。これにより、デバイスの画面ロック(生体認証、指紋認証、PIN など)を使用して認証するようユーザーに求められます。

ユーザーの確認が成功すると、アプリは新しいパスキー認証情報データを受け取り、検証のために Zoho のサーバーに送信します。サーバーは、ユーザーのアカウントにリンクされたパスキー情報を保存します。プロセス中にエラーが発生した場合やユーザーがキャンセルした場合は、アプリによって検出され、処理されます。

ログイン

Zoho Android アプリは、Zoho のバックエンド サーバーから一意の challenge などのログイン オプションをリクエストすることで、パスキーによるログイン プロセスを開始します。次に、アプリはこのデータを使用して GetCredentialRequest を作成し、パスキーで認証することを示します。次に、このリクエストを使用して Android CredentialManager.getCredential() API を呼び出します。このアクションにより、標準化された Android システム インターフェースがトリガーされ、Zoho アカウント(複数のパスキーが存在する場合)を選択し、デバイスで構成された画面ロック(指紋認証、顔スキャン、PIN)を使用して認証するようユーザーに求められます。認証に成功すると、認証情報マネージャーは署名付きアサーション(ログインの証明)を Zoho アプリに返します。アプリはこのアサーションを Zoho のサーバーに転送します。サーバーは、ユーザーの保存された公開鍵に対して署名を検証し、チャレンジを検証して、安全なログイン プロセスを完了します。

サーバーサイドの実装

Zoho は、バックエンド システムがすでに FIDO WebAuthn に準拠していたため、パスキーのサポートへの移行が容易になり、サーバーサイドの実装プロセスが効率化されました。ただし、パスキー機能を完全に統合するには、特定の変更が必要でした。

最も大きな課題は、認証情報ストレージ システムの適応でした。Zoho の既存の認証方法では、主にパスワードと FIDO セキュリティ キーを使用して多要素認証を行っていましたが、暗号化公開鍵に基づくパスキーとは異なるストレージ アプローチが必要でした。これに対処するため、Zoho は WebAuthn プロトコルに従ってパスキーの公開鍵と関連データを安全に保存するために特別に設計された新しいデータベース スキーマを実装しました。この新しいシステムは、ユーザーとデバイスの情報に基づいて認証情報を検証して取得するルックアップ メカニズムとともに構築され、古い認証方法との下位互換性を確保しています。

サーバーサイドのもう 1 つの調整は、Android デバイスからのリクエストを処理する機能の実装でした。Android アプリから送信されるパスキー リクエストは、URI ベースの形式(https://example.com/app)を使用する標準のウェブオリジンとは異なる一意のオリジン形式(android:apk-key-hash:example)を使用します。この形式を正しく解析し、アプリの署名証明書の SHA-256 指紋ハッシュを抽出し、事前に登録されたリストと照合するには、サーバー ロジックを更新する必要がありました。この検証ステップにより、認証リクエストが Zoho の Android アプリから実際に送信されたものであることが保証され、フィッシング攻撃から保護されます。

次のコード スニペットは、サーバーが Android 固有のオリジン形式を確認し、証明書ハッシュを検証する方法を示しています。

val origin: String = clientData.getString("origin")

if (origin.startsWith("android:apk-key-hash:")) { 
    val originSplit: List<String> = origin.split(":")
    if (originSplit.size > 3) {
               val androidOriginHashDecoded: ByteArray = Base64.getDecoder().decode(originSplit[3])

                if (!androidOriginHashDecoded.contentEquals(oneAuthSha256FingerPrint)) {
            throw IAMException(IAMErrorCode.WEBAUTH003)
        }
    } else {
        // Optional: Handle the case where the origin string is malformed    }
}

エラー処理

Zoho は、ユーザー向けとデベロッパー向けの両方のエラーを管理するための堅牢なエラー処理メカニズムを実装しました。一般的なエラーである CreateCredentialCancellationException は、ユーザーがパスキーの設定を手動でキャンセルしたときに発生しました。Zoho は、このエラーの発生頻度を追跡して、UX の改善の可能性を評価しました。Android の UX に関する推奨事項に基づいて、Zoho はパスキーについてユーザーに詳しく説明し、パスキーの利用可能性をユーザーに認識させ、その後のログイン試行時にパスキーの採用を促進するための措置を講じました。

次のコード例は、Zoho が最も一般的なパスキー作成エラーを処理した方法を示しています。

private fun handleFailure(e: CreateCredentialException) {
    val msg = when (e) {
        is CreateCredentialCancellationException -> {
            Analytics.addAnalyticsEvent(eventProtocol: "PASSKEY_SETUP_CANCELLED", GROUP_NAME)
            Analytics.addNonFatalException(e)
            "The operation was canceled by the user."
        }
        is CreateCredentialInterruptedException -> {
            Analytics.addAnalyticsEvent(eventProtocol: "PASSKEY_SETUP_INTERRUPTED", GROUP_NAME)
            Analytics.addNonFatalException(e)
            "Passkey setup was interrupted. Please try again."
        }
        is CreateCredentialProviderConfigurationException -> {
            Analytics.addAnalyticsEvent(eventProtocol: "PASSKEY_PROVIDER_MISCONFIGURED", GROUP_NAME)
            Analytics.addNonFatalException(e)
            "Credential provider misconfigured. Contact support."
        }
        is CreateCredentialUnknownException -> {
            Analytics.addAnalyticsEvent(eventProtocol: "PASSKEY_SETUP_UNKNOWN_ERROR", GROUP_NAME)
            Analytics.addNonFatalException(e)
            "An unknown error occurred during Passkey setup."
        }
        is CreatePublicKeyCredentialDomException -> {
            Analytics.addAnalyticsEvent(eventProtocol: "PASSKEY_WEB_AUTHN_ERROR", GROUP_NAME)
            Analytics.addNonFatalException(e)
            "Passkey creation failed: ${e.domError}"
        }
        else -> {
            Analytics.addAnalyticsEvent(eventProtocol: "PASSKEY_SETUP_FAILED", GROUP_NAME)
            Analytics.addNonFatalException(e)
            "An unexpected error occurred. Please try again."
        }
    }
}

イントラネット環境でのパスキーのテスト

Zoho は、閉鎖されたイントラネット環境でパスキーをテストする際に、最初の課題に直面しました。パスキーの Google パスワード マネージャー検証プロセスでは、証明書利用者(RP)ドメインを検証するためにパブリック ドメインへのアクセスが必要です。しかし、Zoho の内部テスト環境にはこのパブリック インターネット アクセスがなかったため、検証プロセスが失敗し、パスキー認証のテストが成功しませんでした。この問題を解決するため、Zoho はパブリックにアクセス可能なテスト環境を作成しました。これには、アセットリンク ファイルとドメイン検証を備えた一時サーバーのホスティングが含まれます。

Zoho のパブリック テスト環境で使用されている assetlinks.json ファイルの次の例は、パスキー検証のために証明書利用者ドメインを指定された Android アプリに関連付ける方法を示しています。

[
    {
        "relation": [
            "delegate_permission/common.handle_all_urls",
            "delegate_permission/common.get_login_creds"
        ],
        "target": {
            "namespace": "android_app",
            "package_name": "com.zoho.accounts.oneauth",
            "sha256_cert_fingerprints": [
                "SHA_HEX_VALUE" 
            ]
        }
    }
]

既存の FIDO サーバーと統合する

Android のパスキー システムは、最新の FIDO2 WebAuthn 標準を使用しています。この標準では、特定の JSON 形式のリクエストが必要であり、ネイティブ アプリケーションとウェブ プラットフォームの一貫性を維持するのに役立ちます。Android のパスキーのサポートを有効にするため、Zoho は、必要な FIDO2 JSON 構造に準拠したリクエストを正しく生成して処理するために、互換性と構造をわずかに変更しました。

このサーバーの更新には、いくつかの具体的な技術的な調整が含まれていました。

1. エンコード変換: サーバーは、関連データを保存する前に、Base64 URL エンコード(認証情報 ID などのフィールドで WebAuthn で一般的に使用される)を標準の Base64 エンコードに変換します。次のスニペットは、rawId を標準の Base64 にエンコードする方法を示しています。

// Convert rawId bytes to a standard Base64 encoded string for storage
val base64RawId: String = Base64.getEncoder().encodeToString(rawId.toByteArray())

2. 転送リスト形式: データ処理の一貫性を確保するため、サーバー ロジックは転送メカニズム(認証システムの通信方法を指定する USB、NFC、Bluetooth など)のリストを JSON 配列として処理します。

3. クライアント データの調整: Zoho チームは、サーバーが clientDataJson フィールドをエンコードおよびデコードする方法を調整しました。これにより、データ構造が Zoho の既存の内部 API の想定と正確に一致します。次の例は、サーバーが処理する前にクライアント データに適用される変換ロジックの一部を示しています。

private fun convertForServer(type: String): String {
    val clientDataBytes = BaseEncoding.base64().decode(type)
    val clientDataJson = JSONObject(String(clientDataBytes, StandardCharsets.UTF_8))
    val clientJson = JSONObject()
    val challengeFromJson = clientDataJson.getString("challenge")
    // 'challenge' is a technical identifier/token, not localizable text.
    clientJson.put("challenge", BaseEncoding.base64Url()
        .encode(challengeFromJson.toByteArray(StandardCharsets.UTF_8))) 

    clientJson.put("origin", clientDataJson.getString("origin"))
    clientJson.put("type", clientDataJson.getString("type"))
    clientJson.put("androidPackageName", clientDataJson.getString("androidPackageName"))
    return BaseEncoding.base64().encode(clientJson.toString().toByteArray())
}

ユーザー ガイダンスと認証設定

Zoho のパスキー戦略の中心は、さまざまな組織の要件に合わせて柔軟に対応しながら、ユーザーの導入を促進することでした。これは、慎重な UI 設計とポリシー管理によって実現されました。

Zoho は、組織によってセキュリティのニーズが異なることを認識していました。これに対応するため、Zoho は次の機能を実装しました。

  • 管理者の適用: 管理者は、Zoho Directory 管理パネルを使用して、組織全体の必須のデフォルト認証方法としてパスキーを指定できます。このポリシーが有効になっている場合、従業員は次回ログイン時にパスキーを設定し、それ以降はパスキーを使用する必要があります。
  • ユーザー チョイス: 組織が特定のポリシーを適用していない場合、個々のユーザーが管理を維持します。ログイン時に、認証設定でパスキーまたはその他の構成済みオプションから、希望する認証方法を選択できます。

Zoho は、エンドユーザーがパスキーを簡単に導入できるように、次の機能を実装しました。

  • 簡単な設定: Zoho は、パスキーの設定を Zoho OneAuth モバイルアプリ(AndroidiOS の両方で利用可能)に直接統合しました。ユーザーは、アプリ内でいつでもパスキーを簡単に構成できるため、移行がスムーズになります。
  • 一貫したアクセス: パスキーのサポートは、主要なユーザー接点に実装され、ユーザーは次の方法でパスキーを使用して登録と認証を行うことができます。
  • Zoho OneAuth モバイルアプリ(Android、iOS)
  • Zoho ウェブ アカウント ページ。

この方法により、パスキーの設定と使用のプロセスは、管理者によって義務付けられているか、ユーザーが選択したかに関係なく、すでに使用しているプラットフォームにアクセスして統合できるようになりました。パスキー認証のスムーズなユーザーフローを作成する方法について詳しくは、包括的なパスキーのユーザー エクスペリエンス ガイドをご覧ください。

デベロッパーの速度と統合効率への影響

認証情報マネージャーは統合 API であるため、以前のログインフローと比較してデベロッパーの生産性も向上しました。複数の認証方法と API を個別に処理する複雑さが軽減され、統合が数か月から数週間に短縮され、実装エラーが減少しました。これにより、ログイン プロセスが効率化され、全体的な信頼性が向上しました。

認証情報マネージャーでパスキーを実装することで、Zoho は全体的に大幅な改善を実現しました。

  • 大幅な速度向上
    • 従来のパスワード認証と比較してログインが2 倍高速化
    • メールまたは SMS OTP 認証によるユーザー名または携帯電話番号と比較してログインが4 倍高速化
    • ユーザー名、パスワード、SMS または認証システム OTP 認証と比較してログインが6 倍高速化
  • サポート費用の削減
    • パスワード関連のサポート リクエストの減少(特にパスワードを忘れた場合)。
    • 既存のユーザーはパスキーで直接オンボーディングできるため、SMS ベースの 2FA に関連するコストの削減
  • ユーザーの導入の促進とセキュリティの強化:
    • パスキーによるログインはわずか 4 か月で倍増 し、ユーザーの受け入れ度が高いことが示されました。
    • パスキーに移行するユーザーは、一般的なフィッシングやパスワード漏洩の脅威から完全に保護 されます。
    • 前月比 31% の導入率の増加により、フィッシングや SIM スワップなどの脆弱性に対するセキュリティ強化のメリットを毎日享受するユーザーが増えています。

推奨事項とベスト プラクティス

Android でパスキーを正常に実装するには、デベロッパーは次のベスト プラクティスを検討する必要があります。

  • Android の Credential Manager API を活用する:
    • 認証情報マネージャーは認証情報の取得を簡素化し、デベロッパーの労力を軽減し、統合された認証エクスペリエンスを実現します。
    • パスワード、パスキー、フェデレーション ログインフローを単一のインターフェースで処理します。
  • 他の FIDO 認証ソリューションから移行する際に、データ エンコードの整合性を確保する:
    • FIDO セキュリティ キーなどの他の FIDO 認証ソリューションから移行する際に、すべての入力/出力で一貫した形式を処理していることを確認します。
  • エラー処理とロギングを最適化する:
    • シームレスなユーザー エクスペリエンスを実現するために、堅牢なエラー処理を実装します。
    • ローカライズされたエラー メッセージを提供し、詳細なログを使用して予期しない障害をデバッグして解決します。
  • パスキーの復元オプションについてユーザーに説明する:
    • 復元オプションについてユーザーに事前に説明することで、ロックアウト シナリオを防ぎます。
  • 導入指標とユーザー フィードバックをモニタリングする:
    • ユーザー エンゲージメント、パスキーの導入率、ログイン成功率を追跡して、ユーザー エクスペリエンスを継続的に最適化します。
    • さまざまな認証フローで A/B テストを実施して、コンバージョンと定着率を向上させます。

パスキーと Android Credential Manager API を組み合わせることで、セキュリティを強化しながらユーザー エクスペリエンスを簡素化する、強力な統合認証ソリューションが実現します。パスキーは、フィッシングのリスク、認証情報の盗難、不正アクセスを大幅に軽減します。デベロッパーは、アプリでこのエクスペリエンスを試して、最も安全な認証をユーザーに提供することをおすすめします。

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

公開サンプルコードを使用して、Android でパスキーと認証情報マネージャーを実際に試してみましょう。

ご不明な点や問題がございましたら、Android Credentials の問題トラッカーからお知らせください。

作成者:

続きを読む