CredentialManagerを使用したGoogleサインインの実装


## 概要
FirebaseとGoogle Cloud Consoleを使用し、OAuth 2.0 クライアントIDでGoogleログインを実装しました

## やること
com.google.android.gms:play-services-authを使用したGoogleサインインが非推奨となるためCredential Manager APIでGoogleサインインを実装する

## 環境
PC：**Mac**
Android Studio：**Android Studio Ladybug Feature Drop | 2024.2.2**


## 参考資料
Android Document 以前の Google ログインから Credential Manager と AuthorizationClient に移行する
https://developer.android.com/identity/sign-in/legacy-gsi-migration?hl=ja&utm_source=chatgpt.com

Google サインインの移行から始めるCredential Manager 活用
https://2024.droidkaigi.jp/timetable/694563/

Androidドキュメント　Auth
https://developer.android.com/identity/sign-in/credential-manager?hl=ja&utm_source=chatgpt.com

一番分かりやすい OAuth の説明
https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be

## 実装
### Console周りの設定

#### Firebase　Consoleの設定

##### 1\.デバッグキーストアをターミナルから取得
```zsh
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android
```
##### 2\.アプリレベルのbuild.gradleのapplicationIdを確認しておく
```Kotlin
 defaultConfig {
        applicationId = "com.kisayama.TestForFirebase"
```


##### 3\.Firebaseでプロジェクトを作成する
<dl>
<dt>アプリの登録</dt>
<dd>Androidパッケージ名には2で確認したapplicationIdを入力する</dd>
<dd>デバッグ用の署名証明書SHA-1に1を入力する</dd>
<dt>構成ファイルをダウンロードして追加</dt>
<dd>google-service.jsonファイルをダウンロードして画面の表示に従いモジュールのルートディレクトリに移動する</dd>
<dt>FirebaseSDK</dt>
<dd>Firebase関連の依存関係を追加しておく</dd>
<dt>ログイン方法のGoogleを有効化する</dt>
<dd>登録したプロジェクトに移動し、ログイン方法→新しいプロバイダを追加→Googleを追加する</dd>
</dl>

##### 4\.Google Cloud Console
Google Cloud Consoleが自動作成されているのでプロジェクトIDを確認しておく

Google Cloud Console
![スクリーンショット 2025-01-31 23.15.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3951769/24270dd2-c549-4e27-f9f9-a77c923d1079.png)


Firebase Console
![スクリーンショット 2025-01-31 23.05.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3951769/2cbcd564-dc1e-2877-7553-12826d405658.png)

GoogleClodConsoleとFirebase ConsoleのプロジェクトIDが一致していればOK

### 実装
##### 1\.依存関係
Credentials関連の依存関係を追加する

```Kotlin:build.gradle.kts(Project)
id("com.google.gms.google-services") version "4.4.2" apply false
```

```Kotlin:build.gradle.kts(app)
//Googleサービスプラグイン
plugins {
    id("com.google.gms.google-services")
}
```

```Kotlin:buikd.gradle.kts
 //認証機能を管理するためのAPI
 implementation("androidx.credentials:credentials:1.5.0-rc01")
 
 //以下はAndroid 14（API 34）未満で使用されるAPI
 //GooglePlayServicesを使用した認証をサポートする
 implementation("androidx.credentials:credentials-play-services-auth:1.5.0-rc01")
 // 認証したGoogleアカウント情報を取得する
 implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
```
##### 2.コード
タップしたら認証を開始するボタンを用意する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3951769/27ea09f6-f13d-0625-ba27-76dc25e88a79.png)

```XML
    <com.google.android.gms.common.SignInButton
        android:id="@+id/btn_google_sign_in"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />
```


```Kotlin
suspend fun getSignInWithGoogle(
    context: Context,//アクティビティのConText
    onSuccess: (user: AuthUserData?) -> Unit,//成功した時の処理
    onFailure: (Exception) -> Unit//失敗した時の処理
) {
    try {
        // FirebaseAuthインスタンスの取得
        val auth: FirebaseAuth = FirebaseAuth.getInstance()

        // CredentialManager（資格情報管理）
        val credentialManager = CredentialManager.create(context)

        // gradle.propertiesからクライアントIDを取得
        val googleClientId = BuildConfig.google_client_id

        // Googleサインインリクエストを作成
        val googleIdOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
            .Builder(googleClientId) // nonceがあれば設定
            .build()

        // GetCredentialRequestを作成
        val request: GetCredentialRequest = GetCredentialRequest.Builder()
            .addCredentialOption(googleIdOption)
            .build()

        // 非同期で資格情報を取得
        val credential = withContext(Dispatchers.IO) {
            try {
                val result = credentialManager.getCredential(context = context, request = request)
                result.credential
            } catch (e: Exception) {
                throw e
            }
        }

        // 取得した資格情報がGoogle IDトークンであるか確認
        if (credential is CustomCredential && credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
            val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)

            // IDトークンを取得
            val googleIdToken = googleIdTokenCredential.idToken

            // Firebaseの認証情報を作成
            val firebaseCredential = GoogleAuthProvider.getCredential(googleIdToken, null)

            // Firebaseでサインイン処理を実行
            val authResult = auth.signInWithCredential(firebaseCredential).await()

            // サインイン成功時のユーザー情報を取得
            val firebaseUser = authResult.user
            if (firebaseUser != null) {
                val user = AuthUserData(
                    userId = firebaseUser.uid,
                    userName = firebaseUser.displayName
                )
                onSuccess(user)
        }{
        //省略(アカウント情報が見つからない時の処理)
            
        } catch (e: Exception) {
        onFailure(e)
    }
}

```

```Kotlin
  // CredentialManager（資格情報管理）
        val credentialManager = CredentialManager.create(context)
```
依存関係部分でも触れましたが
Android14以上（APIレベル34）ではネイティブAPI(androidxで始まるAPI)が使用されます。
```Kotlin:build.gradle(:app)
implementation("androidx.credentials:credentials:1.5.0-rc01")
```

14未満ではネイティブAPIとGooglePlay開発者サービスから提供されるライブラリを組み合わせて認証を行います。

```Kotlin:build.gradle(:app)
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-rc01")
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
```

##### 3.実装の注意点
```Kotlin
// Googleサインインリクエストを作成
val googleIdOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
        .Builder(googleClientId) // nonceがあれば設定
        .build()
```
私がかなり悩んだ部分は上記のgoogleClientIDでした。
必ず**ウェブアプリケーション用のウェブクライアントIDを使用**してください。

:::note warn
Android用クライアントIDを設定すると、Google Cloud Consoleの設定が不完全であることを意味するエラー **"androidx.credentials.exceptions.GetCredentialCustomException: [28444]Developer console is not set up correctly."** が発生します。
:::

##### 4.追記
1\.リリース用キーストアの生成

release用のキーストアはAndroidStudio→Build → [Generate Signed Bundle or APK]→**App Bundle**か**APK**のどちらかを選択し必要情報を入力したら生成できます。

生成したキーストアはFirebase Consoleに登録しておきます。

2\.リリース用キーストアをコードに記入
個人開発の場合、gradle.properties に以下を記入する。build.gradleに直接書くと、Gitにコミットしてしまうリスクがあるので分離しておきます。

:::note info
gradle.propertiesは必ず.gitignoreに追加すること
:::
gradle.propertiesに必要情報を記入
```gradle.properties
RELEASE_STORE_FILE=releasekeyStoreのフルパス
RELEASE_STORE_PASSWORD=キーストアのパス
RELEASE_KEY_ALIAS=キーのエイリアス
RELEASE_KEY_PASSWORD=別途設定していなければキーストアのパス
```
アプリレベルのgradleファイルにプロパティを使用してリリース用キーストアを宣言しておきます。
```build.gradle.kts(app)
signingConfigs {
        create("release") {
            storeFile = file(project.properties["RELEASE_STORE_FILE"] as String)
            storePassword = project.properties["RELEASE_STORE_PASSWORD"] as String
            keyAlias = project.properties["RELEASE_KEY_ALIAS"] as String
            keyPassword = project.properties["RELEASE_KEY_PASSWORD"] as String
        }
    }
```

### 終わりに
play-services-authの代わりにCredential Managerを使うことで従来のGoogleサインインを使い続けることができます。
また、Googleサインインにセキュリティ強化のためのパスキーやパスワードを付加するサインイン方法もCredential Managerでまとめて実装できます。