1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JetpackComposeでFirebaseAuth(GoogleOneTap認証)

Last updated at Posted at 2023-07-25

GoogleアカウントのOneTap+FirebaseAuth with JetpackCompose

JetpackComposeで、GoogleアカウントのOneTap+FirebaseAuthを実装していきます。

関連記事

エンドポイント

GoogleアカウントのOneTap

エンドポイント  
SignInClient.beginSignin(BeginSignInRequest) OneTap認証画面を表示します
signInClient.getSignInCredentialFromIntent(resultData: Intent) OneTap認証画面から戻ってきたときのResultDataをGoogleアカウントのCredentialに変換します

FirebaseAuth

エンドポイント  
GoogleAuthProvider.getCredential(googleCredential) GoogleアカウントのCredentialをFirebaseAuthのCredentialに変換します
Firebase.auth.signInWithCredential(firebaseCredential) FirebaseAuthのCredentialでサインイン処理を行います

処理の流れ

流れはフローチャートにしました。
複雑ではないのですが、OneTap認証画面の起動と結果処理のところに、JetpackComposeのクセを感じます。
Frame 3.png

dependencies

app/build.gradle
android {
    buildFeatures {
        buildConfig = true
    }

+    def properties = new Properties()
+    properties.load(project.rootProject.file('local.properties').newDataInputStream())
+    def googleOauthServerClientId = properties.getProperty('google_oauth_server_client_id')

    defaultConfig {
        // ...なんかいろいろ設定...
+        buildConfigField("String", "GOOGLE_OAUTH_SERVER_CLIENT_ID", "${googleOauthServerClientId}")
    }
}


dependencies {
+    // for GoogleSignIn
+    implementation 'com.google.android.gms:play-services-auth:20.6.0'
}

def googleOauthServerClientId = properties.getProperty('google_oauth_server_client_id')
buildConfigField("String", "GOOGLE_OAUTH_SERVER_CLIENT_ID", "${googleOauthServerClientId}")

Googleアカウントでの認証には、GoogleCloudConsole>対象アプリ>認証情報>OAuth 2.0 クライアント IDのうち、Web clientのクライアントIDが必要です。
Firebaseコンソールでアプリを作っていると、勝手にできていると思いますので、GoogleCloudConsoleから拾ってきます。
秘密の情報にしておいた方が無難かと思いますので、クライアントIDは「local.properties」に保存しておいて、BuildConfig経由で取得しようと思います。
そのための設定が引用部分になります。

implementation 'com.google.android.gms:play-services-auth:20.6.0'

Googleアカウントの認証には、play-services-authが必要です。

インフラ層の実装

前回作成したクラスを拡張していきます。

AccountRepositoryImpl.kt
+    override suspend fun requestGoogleOneTapAuth(signInClient: SignInClient): PendingIntent {
+        val result = signInClient.beginSignIn(signInRequest).await()
+        return result.pendingIntent
+    }

+    override suspend fun signinGoogleOneTapAuth(signInClient: SignInClient, resultData: Intent) {
+        val googleCredential = signInClient.getSignInCredentialFromIntent(resultData)
+        val firebaseCredential = GoogleAuthProvider.getCredential(googleCredential.googleIdToken, null)
+        auth.signInWithCredential(firebaseCredential).await()
+    }
関数 役割
requestGoogleOneTapAuth OneTap認証画面を起動するためのPendingIntentを取得する
signinGoogleOneTapAuth OneTap認証画面からの戻り値を解析し、FirebaseAuthにSignInする

ここの説明は、処理の流れと同じですので割愛します。

UseCase層の実装

23/7/28追記
引数のActivityをやめて、ApplicationContextをDIするように修正しました

GoogleSigninCase.kt
class GoogleSigninCase @Inject constructor(
    @ApplicationContext private val context: Context,
    private val accountRepository: AccountRepository,
) {
    suspend fun signinOneTap(launcher: ActivityResultLauncher<IntentSenderRequest>) {
        val signInClient = Identity.getSignInClient(context)
        val pendingIntent = accountRepository.requestGoogleOneTapAuth(signInClient)
        val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent).build()
        launcher.launch(intentSenderRequest)
    }

    suspend fun onResultSigninOneTap(result: ActivityResult) {
        if (result.resultCode != Activity.RESULT_OK) {
            Log.d("SigninViewModel", "onResultGoogleSignIn ${SigninOneTapError(result.data).resultMessage}")
            return
        }

        val resultData = result.data ?: let {
            Log.d("SigninViewModel", "onResultGoogleSignIn result.data is null")
            return
        }

        val signInClient = Identity.getSignInClient(context)
        accountRepository.signinGoogleOneTapAuth(signInClient, resultData)
    }
}

(ViewModelとUseCaseの責務の範囲に関していろいろ迷いました...)

関数 役割
signinOneTap OneTap認証画面の起動
onResultSigninOneTap OneTap認証画面の結果解析とSignIn処理

signinOneTap

Identity.getSignInClient(activity)で、@ComposableでLocalContext.currentの値をActivityにキャストしたものを引数に、SignInClientを取得します。
Identity.getSignInClient(context)で、ApplicationContextをDIしたものを引数に、SignInClientを取得します。
取得したSignInClientを使用して、OneTap認証画面を起動するためのPendingIntentを取得します。
受け取ったPendingIntentを持つIntentSenderRequestを生成して、ActivityResultLauncherで画面起動処理を行うと、OneTap認証画面が起動します。

onResultSigninOneTap

OneTap認証画面が閉じたときに、ActivityResultが返ってきますので、ActivityResult.resultCodeがOKの場合は、SignIn処理を行います。

ViewModel層の実装

SigninViewModel.kt
+    fun onClickSignInWithGoogleOneTap(launcher: ActivityResultLauncher<IntentSenderRequest>) {
+        viewModelScope.launch(
+            context = CoroutineExceptionHandler { _, throwable ->
+                val message = throwable.toSnackbarMessage()
+                SnackbarManager.showMessage(throwable.toSnackbarMessage())
+            },
+            block = {
+                googleSigninCase.signinOneTap(launcher)
+            },
+        )
+    }

+    fun onResultSignInWithGoogleOneTap(result: ActivityResult) {
+        viewModelScope.launch(
+            context = CoroutineExceptionHandler { _, throwable ->
+                val message = throwable.toSnackbarMessage()
+                SnackbarManager.showMessage(throwable.toSnackbarMessage())
+            },
+            block = {
+                googleSigninCase.onResultSigninOneTap(result)
+            },
+        )
+    }

これはUseCaseを呼び出すだけなので説明は割愛です。

Screenの実装

SigninScreen.kt
/* ...いろいろ実装...*/

+val startForResultGoogleSignin = rememberLauncherForActivityResult(
+    contract = ActivityResultContracts.StartIntentSenderForResult(),
+    onResult = { result -> viewModel.onResultSignInWithGoogleOneTap(result) },
+)

+BasicButton(
+    text = R.string.sign_in_with_google,
+    modifier = Modifier.basicButton(),
+    action = { viewModel.onClickSignInWithGoogleOneTap(startForResultGoogleSignin) },
+)

rememberLauncherForActivityResultを使って、ActivityResultLauncherを作成します。
ActivityResultLauncherのonResultに、「viewModel.onResultSignInWithGoogleOneTap」を指定して、結果処理を行います。

BasicButtonのactionが、onClickの時に動く処理です。
「viewModel.onClickSignInWithGoogleOneTap」を指定して、OneTop認証画面の起動を行います。

テストでキャンセルしすぎた場合は、「##66382723##」に電話をかけましょう。制限がオフになります。
オンに戻すときは同じ番号にもう一度電話をかけましょう
https://developers.google.com/identity/one-tap/android/get-saved-credentials?hl=ja#disable-one-tap

いざ実行

コードはこちらです。
23/7/28追記
Identity.getSignInClientの引数をActivityからApplicationContextに変更したソースはこちらです。

ちょっと実名が出ちゃうので、動画はなしで。。。
onetap_1.pngonetap_2.pngonetap_3.pngonetap_4.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?