androidx.credentialsを利用して新たなパスワード管理を試す

:::note warn
alphaが出たばかりなので、API等変更される可能性があります
:::

# 新たなライブラリがリリースされた

2023年1月11日、androidx.credentials の[1.0.0-alpha01](https://developer.android.com/jetpack/androidx/releases/credentials?hl=ja#1.0.0-alpha01) がリリースされました。
これはパスワードやパスキーなどを管理し、シームレスでセキュアなログイン体験を提供してくれるライブラリになっています。

本記事では実際にどういうことが可能かを試してみています。

# 関連クラス
## CredentialManager
https://developer.android.com/reference/androidx/credentials/CredentialManager

ユーザーの認証フローを管理するものです。
アプリはこのクラスにより、認証情報の登録や保存済みの認証情報の取得を、UIフローから利用することができます。

`CredentialManager` は端末で利用できる `CredentialProvider` に実際の処理を委譲します。

## CredentialProvider
https://developer.android.com/reference/androidx/credentials/CredentialProvider
実際に認証情報の登録・取得処理を行っています。
これ自身はinterfaceで、OEMがライブラリなどとして実装することが想定されているようです。
ハードウェアメーカーが端末にセキュアに情報を保存する方法を提供するための方法ということでしょうか。

SDK version 33(Android 13)以下では開発者側はそのライブラリを依存に追加した上で、このインターフェースを実装するクラスをmeta-dataとして追加する必要があります。

SDK version 33より上では `CredentialProvider` の提供者はフレームワークに直接登録する形となるようです。


# 実装する
では実際に使ってみましょう。

## 依存関係
まずは依存関係に `androidx.credentials` を追加します。
実際に `CredentialProvider` の実装がなければ使えないので、現状使える `credentials-play-ervices-auth` も追加します。

```gradle: build.gradle
dependencies {
    implementation 'androidx.credentials:credentials:1.0.0-alpha01'
    implementation 'androidx.credentials:credentials-play-services-auth:1.0.0-alpha01'
}
```

## ~~AndroidManifest~~
~~`AndroidManifest` には、どの `CredentialProvider` を使うかを登録します。
今回はPlayServiceのもの(Googleのパスワードマネージャー)を表す`CredentialProviderPlayServicesImpl` を指定します。~~

```xml:AndroidManifest.xml
<service android:name="androidx.credentials.playservices.CredentialProviderMetadataHolder">
  <meta-data
    android:name="androidx.credentials.CREDENTIAL_PROVIDER_KEY"
    android:value="androidx.credentials.playservices.CredentialProviderPlayServicesImpl" />
</service>
```

追記: `CredentialProviderMetadataHolder` の宣言はライブラリ側のAndroidManifestで行われています。
つまり `androidx.credentials:credentials-play-services-auth` が入っていれば勝手にAndroidManifestにマージされるため、自前で書く必要はありません。

## 認証情報登録
認証情報の登録は `CredentialManager#executeCreateCredential()` で行います。
id/パスワードが空文字だと例外が出るので注意します。
またUIを使ったフロー中にキャンセルされると例外が出るので処理します。

```kotlin:CredentialActivity.kt
lifecycleScope.launch {
  if(id.isNullOrEmpty() || password.isNullOrEmpty()) return@launch

  val request = CreatePasswordRequest(id, password)
  val credentialManager = CredentialManager.create(this)
  try {
    credentialManager.executeCreateCredential(request, this@CredentialActivity)
  } catch (e: CreateCredentialCancellationException) {
    Toast.makeText(this@CredentialActivity, "キャンセルされました", Toast.LENGTH_LONG).show()
  }
}
```

## 認証情報取得
認証情報の取得は `CredentialManager#executeGetCredential()` で行います。

認証情報は `Bundle` に入ってきますので、 `PasswordCredential.BUNDLE_KEY_ID` `PasswordCredential.BUNDLE_KEY_PASSWORD` で取得します。

また以下のケースで例外が投げられるのでこちらも処理します。
- `GetCredentialCancellationException`
    - UI上で閉じるボタンを押すなどキャンセルされた場合
- `GetCredentialInterruptedException` 
    - このアプリに紐づく認証情報が保存されていない場合

```kotlin:CredentialActivity.kt
lifecycleScope.launch {
  val text = try {
    val request = GetCredentialRequest(listOf(GetPasswordOption()))
    val response = credentialManager.executeGetCredential(request, this@CredentialActivity)
    val credential = response.credential
    val id = credential.data.getString(PasswordCredential.BUNDLE_KEY_ID, "")
    val password = credential.data.getString(PasswordCredential.BUNDLE_KEY_PASSWORD, "")
"id: $id pass: $password"
  } catch (e: GetCredentialCancellationException) {
    "キャンセルされました"
  } catch (e: GetCredentialInterruptedException) {
    "エラーが発生しました"
  }
  Toast.makeText(this@CredentialActivity, text, Toast.LENGTH_LONG).show()
}
```

# 完成
実際にUIも含めて作ったのがこちら。
https://github.com/verno3632/WorkingDog/tree/main/credential

認証情報保存の際に、Googleのパスワードマネージャーに登録されます。
![set.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743/b0645ab8-9af5-108a-bb1f-0dde67cf505f.gif)


同様にパスワードマネージャーから取得もできます
![get.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743/2f8654de-a774-fbf3-e267-28b518b4af4b.gif)


パスワードマネージャーを覗いてみると、アプリのパッケージ名(アプリケーションID)で保存されていました。
もちろん端末を跨いで共有することが出来ます。

# 終わりに
まだ1.0.0-alpha01ということで、絶賛開発中のようです。コード中にもTODOの部分が多くありました。


33以下では認証情報管理のライブラリを明示的に依存関係に追加しなければいけませんが、34以上では扱いが違うようなことがドキュメントから読み取れます。
34以上で端末で利用できるパスワードマネージャーからユーザーが好きに選択して使えるとかになるなど、Androidへの機能追加があるんじゃないか？と思ってます。

今後とも注視していきたいですね :eyes: