Credential Manager は、ユーザー名 / パスワード、パスキー、フェデレーション ログイン ソリューション(Google でログインなど)のような複数のログイン方法を単一の API でサポートする Jetpack API です。これにより、デベロッパーにとっては統合が容易になります。
さらに、ユーザーにとっては、複数の認証方法でログイン インターフェースが統一されるため、どの方法を選択するかにかかわらず、アプリへのログインがわかりやすく簡単になります。
このページでは、パスキーのコンセプトと、Credential Manager API を使用して、パスキーなどの認証ソリューションのクライアント側サポートを実装する手順について説明します。よくある質問のページでも、より詳細で具体的な質問に対する回答をご覧いただけます。
皆様からのフィードバックは、Credential Manager API の改善に欠かせない要素です。次のリンクを使用して、見つかった問題や API の改善に向けたアイデアを共有してください。
パスキーについて
パスキーは、より安全で簡単なパスワードの代替方法です。パスキーにより、ユーザーは生体認証センサー(指紋認証や顔認証)、PIN、パターンを使用してアプリとウェブサイトにログインできます。これによってシームレスなログイン エクスペリエンスが実現し、ユーザーはユーザー名とパスワードを記憶する必要がなくなります。
パスキーは、FIDO Alliance と World Wide Web Consortium(W3C)が共同で開発した WebAuthn(Web Authentication)標準を採用しています。WebAuthn は公開鍵暗号を使用してユーザーを認証します。ユーザーがログインしているウェブサイトやアプリでは、公開鍵の確認と保管はできますが、秘密鍵が保管されることはありません。秘密鍵は秘密かつ安全に保管されます。この鍵は一意であり、ウェブサイトまたはアプリに関連付けられているため、パスキーはフィッシング攻撃から保護され、セキュリティが強化されます。
Credential Manager により、ユーザーはパスキーを作成して Google パスワード マネージャーに保存できます。
認証情報マネージャーでシームレスなパスキー認証フローを実装する方法については、パスキーによるユーザー認証をご覧ください。
前提条件
Credential Manager を使用するには、このセクションの手順を行います。
最新のプラットフォーム バージョンを使用する
Credential Manager は、Android 4.4(API レベル 19)以降でサポートされます。
アプリに依存関係を追加する
アプリ モジュールのビルド スクリプトに次の依存関係を追加します。
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.5.0-beta01") // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.5.0-beta01" // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation "androidx.credentials:credentials-play-services-auth:1.5.0-beta01" }
ProGuard ファイルでクラスを保持する
モジュールの proguard-rules.pro
ファイルで、次のディレクティブを追加します。
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
詳しくは、アプリの圧縮、難読化、最適化をご覧ください。
デジタル アセット リンクのサポートを追加する
Android アプリでパスキーのサポートを有効にするには、アプリが所有するウェブサイトとアプリとを関連付けます。この関連付けを宣言する手順は次のとおりです。
デジタル アセット リンクの JSON ファイルを作成します。たとえば、ウェブサイト
https://signin.example.com
とパッケージ名com.example
の Android アプリがログイン認証情報を共有できることを宣言するには、以下の内容を含むassetlinks.json
というファイルを作成します。[ { "relation" : [ "delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds" ], "target" : { "namespace" : "android_app", "package_name" : "com.example.android", "sha256_cert_fingerprints" : [ SHA_HEX_VALUE ] } } ]
relation
フィールドは、宣言される関係を記述する 1 つ以上の文字列の配列です。アプリとサイトがログイン認証情報を共有することを宣言するには、関係にdelegate_permission/handle_all_urls
とdelegate_permission/common.get_login_creds
を指定します。target
フィールドは、宣言が適用されるアセットを指定するオブジェクトです。以下のフィールドではウェブサイトを識別します。namespace
web
site
https://domain[:optional_port]
の形式でのウェブサイトの URL(例:https://www.example.com
)。domain は完全修飾する必要があります。HTTPS にポート 443 を使用する場合は、optional_port を省略する必要があります。
site
のターゲットにはルートドメインのみを指定できます。アプリの関連付けを特定のサブディレクトリに限定することはできません。URL にパス(末尾のスラッシュなど)を含めないでください。サブドメインは一致とは見なされません。つまり、domain を
www.example.com
と指定した場合、ドメインwww.counter.example.com
はアプリに関連付けられません。以下のフィールドでは Android アプリを識別します。
namespace
android_app
package_name
アプリのマニフェストで宣言されたパッケージ名。例: com.example.android
sha256_cert_fingerprints
アプリの署名証明書の SHA256 フィンガープリント。 デジタル アセット リンクの JSON ファイルを、ログイン ドメインの次の場所でホストします。
https://domain[:optional_port]/.well-known/assetlinks.json
たとえば、ログイン ドメインが
signin.example.com
の場合は、JSON ファイルをhttps://signin.example.com/.well-known/assetlinks.json
でホストします。デジタル アセット リンク ファイルの MIME タイプは JSON にする必要があります。サーバーがレスポンスで
Content-Type: application/json
ヘッダーを送信していることを確認します。ホストが Google にデジタル アセット リンク ファイルの取得を許可していることを確認します。
robots.txt
ファイルがある場合は、Googlebot エージェントに/.well-known/assetlinks.json
の取得を許可する必要があります。ほとんどのサイトでは、自動エージェントに/.well-known/
パス内のファイルの取得を許可すると、他のサービスがこれらのファイルのメタデータにアクセスできるようになります。User-agent: * Allow: /.well-known/
マニフェスト ファイルの
<application>
に次の行を追加します。<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
認証情報マネージャーを介してパスワード ログインを使用している場合は、次の手順に沿ってマニフェストにデジタル アセット リンクを設定します。パスキーのみを使用している場合は、この手順は必要ありません。
Android アプリで関連付けを宣言します。読み込む
assetlinks.json
ファイルを指定するオブジェクトを追加します。文字列で使用するアポストロフィや引用符はエスケープする必要があります。次に例を示します。<string name="asset_statements" translatable="false"> [{ \"include\": \"https://signin.example.com/.well-known/assetlinks.json\" }] </string>
> GET /.well-known/assetlinks.json HTTP/1.1 > User-Agent: curl/7.35.0 > Host: signin.example.com < HTTP/1.1 200 OK < Content-Type: application/json
Credential Manager を構成する
CredentialManager
オブジェクトを構成して初期化するには、次のようなロジックを追加します。
Kotlin
// Use your app or activity context to instantiate a client instance of // CredentialManager. val credentialManager = CredentialManager.create(context)
Java
// Use your app or activity context to instantiate a client instance of // CredentialManager. CredentialManager credentialManager = CredentialManager.create(context)
認証情報フィールドを指定する
Android 14 以降では、isCredential
属性を使用して、ユーザー名やパスワードのフィールドなどの認証情報フィールドを指定できます。この属性は、このビューが、認証情報マネージャーやサードパーティの認証情報プロバイダと連携することを目的とした認証情報フィールドであることを示します。これにより、自動入力サービスが適切な自動入力候補を提供できるようになります。アプリで Credential Manager API を使用すると、利用可能な認証情報が記載された認証情報マネージャーのボトムシートが表示されます。ユーザー名やパスワードの入力ダイアログを表示する必要はありません。同様に、アプリが認証情報の保存を Credential Manager API にリクエストするため、パスワードの自動入力の保存ダイアログを表示する必要はありません。
isCredential
属性を使用するには、関連するビューにこの属性を追加します。
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isCredential="true"
...
/>
ユーザーのログインを行う
ユーザーのアカウントに関連付けられているすべてのパスキーとパスワードのオプションを取得する手順は次のとおりです。
パスワードとパスキーの認証オプションを初期化します。
Kotlin
// Retrieves the user's saved password for your app from their // password provider. val getPasswordOption = GetPasswordOption() // Get passkey from the user's public key credential provider. val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( requestJson = requestJson )
Java
// Retrieves the user's saved password for your app from their // password provider. GetPasswordOption getPasswordOption = new GetPasswordOption(); // Get passkey from the user's public key credential provider. GetPublicKeyCredentialOption getPublicKeyCredentialOption = new GetPublicKeyCredentialOption(requestJson);
前の手順で取得したオプションを使用してログイン リクエストを作成します。
Kotlin
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Java
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
ログインフローを開始します。
Kotlin
coroutineScope.launch { try { val result = credentialManager.getCredential( // Use an activity-based context to avoid undefined system UI // launching behavior. context = activityContext, request = getCredRequest ) handleSignIn(result) } catch (e : GetCredentialException) { handleFailure(e) } } fun handleSignIn(result: GetCredentialResponse) { // Handle the successfully returned credential. val credential = result.credential when (credential) { is PublicKeyCredential -> { val responseJson = credential.authenticationResponseJson // Share responseJson i.e. a GetCredentialResponse on your server to // validate and authenticate } is PasswordCredential -> { val username = credential.id val password = credential.password // Use id and password to send to your server to validate // and authenticate } is CustomCredential -> { // If you are also using any external sign-in libraries, parse them // here with the utility functions provided. if (credential.type == ExampleCustomCredential.TYPE) { try { val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data) // Extract the required credentials and complete the authentication as per // the federated sign in or any external sign in library flow } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) { // Unlikely to happen. If it does, you likely need to update the dependency // version of your external sign-in library. Log.e(TAG, "Failed to parse an ExampleCustomCredential", e) } } else { // Catch any unrecognized custom credential type here. Log.e(TAG, "Unexpected type of credential") } } else -> { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential") } } }
Java
credentialManager.getCredentialAsync( // Use activity based context to avoid undefined // system UI launching behavior activity, getCredRequest, cancellationSignal, <executor>, new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { handleSignIn(result); } @Override public void onError(GetCredentialException e) { handleFailure(e); } } ); public void handleSignIn(GetCredentialResponse result) { // Handle the successfully returned credential. Credential credential = result.getCredential(); if (credential instanceof PublicKeyCredential) { String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson(); // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate } else if (credential instanceof PasswordCredential) { String username = ((PasswordCredential) credential).getId(); String password = ((PasswordCredential) credential).getPassword(); // Use id and password to send to your server to validate and authenticate } else if (credential instanceof CustomCredential) { if (ExampleCustomCredential.TYPE.equals(credential.getType())) { try { ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData()); // Extract the required credentials and complete the // authentication as per the federated sign in or any external // sign in library flow } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) { // Unlikely to happen. If it does, you likely need to update the // dependency version of your external sign-in library. Log.e(TAG, "Failed to parse an ExampleCustomCredential", e); } } else { // Catch any unrecognized custom credential type here. Log.e(TAG, "Unexpected type of credential"); } } else { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential"); } }
次の例は、パスキーを取得したときに JSON リクエストをフォーマットする方法を示しています。
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
}
次の例は、公開鍵認証情報の取得後の JSON レスポンスを示しています。
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
利用可能な認証情報がない場合に例外を処理する
場合によっては、ユーザーが利用可能な認証情報を持っていない場合や、ユーザーが利用可能な認証情報の使用に同意しないことがあります。getCredential()
が呼び出され、認証情報が見つからない場合は、NoCredentialException
が返されます。このような場合は、コードで NoCredentialException
インスタンスを処理する必要があります。
Kotlin
try {
val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
Log.e("CredentialManager", "No credential available", e)
}
Java
try {
Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
Log.e("CredentialManager", "No credential available", e);
}
Android 14 以降では、getCredential()
を呼び出す前に prepareGetCredential()
メソッドを使用して、アカウント セレクタを表示するときのレイテンシを短縮できます。
Kotlin
val response = credentialManager.prepareGetCredential(
GetCredentialRequest(
listOf(
<getPublicKeyCredentialOption>,
<getPasswordOption>
)
)
}
Java
GetCredentialResponse response = credentialManager.prepareGetCredential(
new GetCredentialRequest(
Arrays.asList(
new PublicKeyCredentialOption(),
new PasswordOption()
)
)
);
prepareGetCredential()
メソッドは UI 要素を呼び出しません。これは、後で getCredential()
API を使用して残りの get-credential オペレーション(UI を含む)を起動できるように準備作業を行ううえで役立ちます。
キャッシュされたデータは PrepareGetCredentialResponse
オブジェクトで返されます。既存の認証情報がある場合、結果はキャッシュに保存されます。後で残りの getCredential()
API を起動して、キャッシュされたデータでアカウント セレクタを起動できます。
登録フロー
パスキーまたはパスワードのいずれかを使用して、認証するユーザーを登録できます。
パスキーを作成する
ユーザーがパスキーを登録して再認証に使用できるようにするには、CreatePublicKeyCredentialRequest
オブジェクトを使用してユーザー認証情報を登録します。
Kotlin
fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) { val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest( // Contains the request in JSON format. Uses the standard WebAuthn // web JSON spec. requestJson = requestJson, // Defines whether you prefer to use only immediately available // credentials, not hybrid credentials, to fulfill this request. // This value is false by default. preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, ) // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. coroutineScope.launch { try { val result = credentialManager.createCredential( // Use an activity-based context to avoid undefined system // UI launching behavior context = activityContext, request = createPublicKeyCredentialRequest, ) handlePasskeyRegistrationResult(result) } catch (e : CreateCredentialException){ handleFailure(e) } } } fun handleFailure(e: CreateCredentialException) { when (e) { is CreatePublicKeyCredentialDomException -> { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(e.domError) } is CreateCredentialCancellationException -> { // The user intentionally canceled the operation and chose not // to register the credential. } is CreateCredentialInterruptedException -> { // Retry-able error. Consider retrying the call. } is CreateCredentialProviderConfigurationException -> { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } is CreateCredentialUnknownException -> ... is CreateCredentialCustomException -> { // You have encountered an error from a 3rd-party SDK. If you // make the API call with a request object that's a subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, then you // should check for any custom exception type constants within // that SDK to match with e.type. Otherwise, drop or log the // exception. } else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}") } }
Java
public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) { CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest = // `requestJson` contains the request in JSON format. Uses the standard // WebAuthn web JSON spec. // `preferImmediatelyAvailableCredentials` defines whether you prefer // to only use immediately available credentials, not hybrid credentials, // to fulfill this request. This value is false by default. new CreatePublicKeyCredentialRequest( requestJson, preferImmediatelyAvailableCredentials); // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. credentialManager.createCredentialAsync( // Use an activity-based context to avoid undefined system // UI launching behavior requireActivity(), createPublicKeyCredentialRequest, cancellationSignal, executor, new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() { @Override public void onResult(CreateCredentialResponse result) { handleSuccessfulCreatePasskeyResult(result); } @Override public void onError(CreateCredentialException e) { if (e instanceof CreatePublicKeyCredentialDomException) { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError()); } else if (e instanceof CreateCredentialCancellationException) { // The user intentionally canceled the operation and chose not // to register the credential. } else if (e instanceof CreateCredentialInterruptedException) { // Retry-able error. Consider retrying the call. } else if (e instanceof CreateCredentialProviderConfigurationException) { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } else if (e instanceof CreateCredentialUnknownException) { } else if (e instanceof CreateCredentialCustomException) { // You have encountered an error from a 3rd-party SDK. If // you make the API call with a request object that's a // subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, // then you should check for any custom exception type // constants within that SDK to match with e.type. // Otherwise, drop or log the exception. } else { Log.w(TAG, "Unexpected exception type " + e.getClass().getName()); } } } ); }
JSON リクエストをフォーマットする
パスキーを作成後、パスキーをユーザーのアカウントに関連付け、パスキーの公開鍵をサーバーに保存する必要があります。次のコード例は、パスキーの作成時に JSON リクエストをフォーマットする方法を示しています。
アプリにシームレスな認証を導入する方法について取り上げたこのブログ投稿では、パスキーの作成時とパスキーを使用した認証時に JSON リクエストをフォーマットする方法について説明しています。また、パスワードが効果的な認証ソリューションでない理由、既存の生体認証情報の活用方法、所有しているウェブサイトにアプリを関連付ける方法、パスキーを作成する方法、パスキーを使用した認証方法についても説明しています。
{
"challenge": "abc123",
"rp": {
"name": "Credential Manager example",
"id": "credential-manager-test.example.com"
},
"user": {
"id": "def456",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [
{"id": "ghi789", "type": "public-key"},
{"id": "jkl012", "type": "public-key"}
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
authenticatorAttachment の値を設定する
authenticatorAttachment
パラメータを設定できるのは、認証情報の作成時のみです。platform
または cross-platform
を指定するか、値なしにします。ほとんどの場合は、値なしにすることをおすすめします。
platform
: ユーザーの現在のデバイスを登録する、またはパスワードのユーザーにログイン後にパスキーへのアップグレードを求めるには、authenticatorAttachment
をplatform
に設定します。cross-platform
: 多要素認証情報を登録する場合によく使用される値で、パスキーのコンテキストでは使用されません。- 値なし: ユーザーが希望するデバイスで(アカウント設定などで)パスキーを柔軟に作成できるようにするには、ユーザーがパスキーの追加を選択する場合に
authenticatorAttachment
パラメータを指定しないでください。ほとんどの場合は、パラメータを指定しないでおくことをおすすめします。
重複するパスキーが作成されないようにする
オプションの excludeCredentials
配列に認証情報 ID をリストし、同じパスキーのプロバイダのパスキーがすでに存在する場合に新しいパスキーが作成されないようにします。
JSON レスポンスを処理する
次のコード スニペットは、公開鍵認証情報を作成するための JSON レスポンスの例を示しています。返された公開鍵認証情報を処理する方法をご確認ください。
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
クライアント データの JSON から送信元を確認する
origin
はリクエストの送信元のアプリまたはウェブサイトを表し、フィッシング攻撃から保護するためにパスキーで使用されます。アプリのサーバーは、クライアント データの送信元を承認済みアプリとウェブサイトの許可リストと照合する必要があります。サーバーが、認識できない送信元のアプリまたはウェブサイトからリクエストを受け取った場合、そのリクエストは拒否されます。
ウェブケースの場合、origin
は認証情報がログインした同一サイトの送信元を表します。たとえば、URL が https://www.example.com:8443/store?category=shoes#athletic
の場合、origin
は https://www.example.com:8443
です。
Android アプリの場合は、ユーザー エージェントが自動的に origin
を呼び出し元アプリの署名に設定します。パスキー API の呼び出し元を検証するには、この署名がサーバーで一致しているかどうかを検証する必要があります。Android origin
は、次のように APK 署名証明書の SHA-256 ハッシュから派生した URI です。
android:apk-key-hash:<sha256_hash-of-apk-signing-cert>
キーストアの署名証明書の SHA-256 ハッシュは、次のターミナル コマンドを実行して確認できます。
keytool -list -keystore <path-to-apk-signing-keystore>
SHA-256 ハッシュはコロン区切りの 16 進数形式(91:F7:CB:F9:D6:81…
)であり、Android の origin
値は base64url でエンコードされています。次の Python の例では、ハッシュ形式を互換性のあるコロンで区切られた 16 進数形式に変換する方法を示しています。
import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
fingerprint
の値は実際の値に置き換えます。次のような結果が表示されます。
android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU
その文字列を、サーバーで許可されている送信元として照合できます。複数の署名証明書(デバッグとリリース用の証明書や複数のアプリなど)がある場合は、このプロセスを繰り返して、すべての送信元をサーバーで有効として受け入れます。
ユーザーのパスワードを保存する
ユーザーがアプリで認証フローのユーザー名とパスワードを指定した場合は、ユーザーの認証に使用できるユーザー認証情報を登録できます。そのためには、CreatePasswordRequest
オブジェクトを作成します。
Kotlin
fun registerPassword(username: String, password: String) { // Initialize a CreatePasswordRequest object. val createPasswordRequest = CreatePasswordRequest(id = username, password = password) // Create credential and handle result. coroutineScope.launch { try { val result = credentialManager.createCredential( // Use an activity based context to avoid undefined // system UI launching behavior. activityContext, createPasswordRequest ) handleRegisterPasswordResult(result) } catch (e: CreateCredentialException) { handleFailure(e) } } }
Java
void registerPassword(String username, String password) { // Initialize a CreatePasswordRequest object. CreatePasswordRequest createPasswordRequest = new CreatePasswordRequest(username, password); // Register the username and password. credentialManager.createCredentialAsync( // Use an activity-based context to avoid undefined // system UI launching behavior requireActivity(), createPasswordRequest, cancellationSignal, executor, new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() { @Override public void onResult(CreateCredentialResponse result) { handleResult(result); } @Override public void onError(CreateCredentialException e) { handleFailure(e); } } ); }
認証情報の復元をサポートする
ユーザーが認証情報を保存したデバイスにアクセスできなくなった場合は、安全なオンライン バックアップからの復元が必要になることがあります。この認証情報復元プロセスをサポートする方法については、このブログ投稿 Google パスワード マネージャーでのパスキーのセキュリティの「アクセスの回復や新しいデバイスの追加」というセクションをご覧ください。
パスキー エンドポイントの well-known URL によるパスワード管理ツールのサポートを追加
パスワードと認証情報管理ツールとのシームレスな統合と将来的な互換性を確保するため、パスキー エンドポイントの well-known URL のサポートを追加することをおすすめします。これは、提携団体がパスキーのサポートを正式にアドバタイズし、パスキーの登録と管理のための直接リンクを提供するオープン プロトコルです。
- ウェブサイトに加えて Android アプリと iOS アプリを持つ
https://example.com
のリライング パーティの場合、well-known URL はhttps://example.com/.well-known/passkey-endpoints
になります。 この URL がクエリされた場合、レスポンスでは次のスキーマを使用しなければなりません。
{ "enroll": "https://example.com/account/manage/passkeys/create" "manage": "https://example.com/account/manage/passkeys" }
このリンクをウェブではなくアプリで直接開くには、Android アプリリンクを使用します。
詳細については、GitHub の「Passkey Endpoints Well Known URL」の説明をご覧ください。
パスキーを作成したプロバイダを表示して、ユーザーがパスキーを管理できるようにする
特定のアプリに関連付けられた複数のパスキーを管理する際にユーザーが直面する課題の 1 つは、編集または削除する正しいパスキーを特定することです。この問題を解決するために、アプリとウェブサイトでは、アプリの設定画面のパスキー リストに、認証情報を作成したプロバイダ、作成日、最終使用日などの追加情報を含めることをおすすめします。プロバイダ情報は、対応するパスキーに関連付けられた AAGUID を調べることで取得できます。AAGUID は、パスキーの認証情報データの一部として確認できます。
たとえば、ユーザーが Android デバイスで Google パスワード マネージャーを使用してパスキーを作成すると、RP は「ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4」のような AAGUID を受け取ります。信頼できる当事者は、パスキーリストでパスキーにアノテーションを付け、Google パスワード マネージャーを使用して作成されたことを示します。
AAGUID をパスキー プロバイダにマッピングするために、RP は AAGUID のコミュニティ提供リポジトリを使用できます。リストで AAGUID を検索して、パスキー プロバイダの名前とアイコンを確認します。
詳しくは、AAGUID の統合をご覧ください。
一般的なエラーのトラブルシューティング
一般的なエラーコード、説明、原因に関する情報については、認証情報マネージャーのトラブルシューティング ガイドをご覧ください。
参考情報
Credential Manager API とパスキーの詳細については、次のリソースをご覧ください。
- パスキー UX ガイド
- 動画: Android アプリでパスキーをサポートして、パスワードへの依存を減らす方法
- Codelab: Android アプリで Credential Manager API を使用して、認証プロセスを簡素化する方法を学習する
- サンプルアプリ: CredentialManager