Firebase Authentication in Jetpack Compose

Sign in with Google

Learn how to authenticate user with Sign in with Google and Firebase Authentication

Marwa Diab
6 min readDec 11, 2023
Photo by Brett Jordan on Unsplash

In the previous part of this series, we setup Firebase and implemented Firebase Anonymous Authentication, and prepared the AuthRepository class for signing in and linking accounts.

You can let your users authenticate with Firebase using their Google Accounts.
~ Firebase Documentation

Set up Sign in with Google

In the app-level build.gradle file, add the Google Play services dependency:

// Add the dependency for the Google Play services library
implementation("com.google.android.gms:play-services-auth:20.7.0")

Sync project with gradle files, then build.

If you haven’t yet specified your app’s SHA fingerprint, I explained how to get it from your app in this tutorial, under the Register Android app section.

Go to Firebase Console, then Project settings, scroll down to your Android app, and click on Add fingerprint and paste your app’s SHA fingerprint.

Enable Google Sign-In for your Firebase project

  1. In the Firebase console, open the Authentication section.
  2. On the Sign in method tab, click Add new provider, enable the Google provider and click Save.
Click on Add new Provider to add Google.
Enable Google provider and Save.

When prompted in the console, download the latest google-services.json file, which now contains the OAuth client information required for the Google sign-in.

You can provide seamless authentication flows to your users with Google’s one tap sign-in and sign-up APIs.
With One Tap sign-up, users are prompted to create an account with a dialog that’s inline with your app’s content, so they’re never taken out of context by a sign-up screen.
With just one tap, they get a secure, token-based, passwordless account with your service, protected by their Google Account. And, of course, since there’s such little sign-up friction, users are much more likely to register.
~ Overview of One Tap sign-in on Android

Implement One Tap Sign-in

First, provide a SignInClient instance that will be used to begin the sign-in process.
Then provide two instances of BeginSignInRequest, one for the sign-in request annotated with @Named(“signInRequest”), and one for the sign out request annotated with @Named(“signUpRequest”).

So in AppModule, add the following code:

@Provides
fun provideOneTapClient(
@ApplicationContext
context: Context
) = Identity.getSignInClient(context)

@Provides
@Named("signInRequest")
fun provideSignInRequest(
app: Application
) = BeginSignInRequest.Builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest
// 1.
.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// 2.
.setServerClientId(app.getString(R.string.web_client_id))
// 3.
.setFilterByAuthorizedAccounts(true)
.build()
)
// 4.
.setAutoSelectEnabled(true)
.build()

@Provides
@Named("signUpRequest")
fun provideSignUpRequest(
app: Application
) = BeginSignInRequest.Builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest
// 1.
.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// 2.
.setServerClientId(app.getString(R.string.web_client_id))
// 5.
.setFilterByAuthorizedAccounts(false)
.build()
)
.build()

Here is what happens in this code snippet:

  1. Use the .GoogleIdTokenRequestOptions() to enable and configure the Google ID token request (required for apps that use Google Sign-in).
  2. Use the .setServerClientId(serverClientId) to pass your server’s Client ID, not your Android client ID, which we can get from the Firebase Console. (Explained below → How to get the Web Client ID from Firebase?)
  3. In signInRequest, only show the accounts that were previously used to sign in, by passing true to .setFilterByAuthorizedAccounts(filterByAuthorizedAccounts:).
  4. Enable automatically sign-in when exactly one credential is retrieved.
  5. In SignUpRequest, show all available accounts on the device by passing false to .setFilterByAuthorizedAccounts(filterByAuthorizedAccounts:), this is the case that theh user does not have a previously authorized account.

How to get the Web Client ID from Firebase?

  • Open the Authentication section, in the Sign-in method tab, select Google to expand, then select Web SDK configuration to expand, and copy the Web client ID value as shown below 👇🏻:

Next, In Response sealed class add the following typealias:

typealias OneTapSignInResponse = Response<BeginSignInResult>

And in DataProvider add the following:

var oneTapSignInResponse by mutableStateOf<OneTapSignInResponse>(Success(null))

In AuthRepository interface add the following functions declaration:

suspend fun onTapSignIn(): OneTapSignInResponse

suspend fun signInWithGoogle(credential: SignInCredential): FirebaseSignInResponse

Next, in AuthRepositoryImpl, implement members, and inject the following parameters in the constructor:

private var oneTapClient: SignInClient,
@Named("signInRequest") private var signInRequest: BeginSignInRequest,
@Named("signUpRequest") private var signUpRequest: BeginSignInRequest,

In onTapSignIn() function add the following:

  1. Start the sign-in process by calling beginSignIn(beginSignInRequest:) on the SignInClient object and pass the signInRequest.
  2. If the sign-in process failed because the user is not previously authorized, then start the sign-up process by calling beginSignIn(beginSignInRequest:) on the SignInClient object and pass the signUpRequest.
override suspend fun onTapSignIn(): OneTapSignInResponse {
return try {
// 1.
val signInResult = oneTapClient.beginSignIn(signInRequest).await()
Response.Success(signInResult)
} catch (e: Exception) {
try {
// 2.
val signUpResult = oneTapClient.beginSignIn(signUpRequest).await()
Response.Success(signUpResult)
} catch(e: Exception) {
Response.Failure(e)
}
}
}

In signInWithGoogle(credential:) function add the following:

  1. Create a AuthCredential using the googleIdToken from the given SignInCredential.
  2. Then call the previously created authenticateUser(credentials:) and pass the googleCredential we just defined to link or sign-in with Firebase.
override suspend fun signInWithGoogle(credential: SignInCredential): FirebaseSignInResponse {
// 1.
val googleCredential = GoogleAuthProvider
.getCredential(credential.googleIdToken, null)
// 2.
return authenticateUser(googleCredential)
}

In signOut() function, replace // TODO: oneTapClient sign out with oneTapClient.signOut().await().

Next, in AuthViewModel, replace // TODO: with val oneTapClient: SignInClient in the constructor, then create the following functions:

fun oneTapSignIn() = CoroutineScope(Dispatchers.IO).launch {
DataProvider.oneTapSignInResponse = Response.Loading
DataProvider.oneTapSignInResponse = repository.onTapSignIn()
}

fun signInWithGoogle(credentials: SignInCredential) = CoroutineScope(Dispatchers.IO).launch {
DataProvider.googleSignInResponse = Response.Loading
DataProvider.googleSignInResponse = repository.signInWithGoogle(credentials)
}

Next, in LoginScreen and the following code before the Scaffold composable:

  1. Create rememberLauncherForActivityResult to register a request, it takes an IntentSender sent from the beginSignIn(beginSignInRequest:) method.
  2. If the ActivityResult returned RESULT_OK, then we get aSignInCredential from the result intent and pass it to signInWithGoogle(credential:).
  3. Build an IntentSenderRequest from signInResult.pendingIntent.intentSender, to send it to the Activity launcher.
@Composable
fun LoginScreen(
authViewModel: AuthViewModel
) {
// 1.
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
try {
// 2.
val credentials = authViewModel.oneTapClient.getSignInCredentialFromIntent(result.data)
authViewModel.signInWithGoogle(credentials)
}
catch (e: ApiException) {
Log.e("LoginScreen:Launcher","Login One-tap $e")
}
}
else if (result.resultCode == Activity.RESULT_CANCELED){
Log.e("LoginScreen:Launcher","OneTapClient Canceled")
}
}

fun launch(signInResult: BeginSignInResult) {
// 3.
val intent = IntentSenderRequest.Builder(signInResult.pendingIntent.intentSender).build()
launcher.launch(intent)
}

// Scaffold content...
}

After the OneTapSignIn and then Firebase authentication with Google, check on the Response returned from each process, It’s a good place to change the screen or do any actions in your app.

  1. In case of Response.Loading in any responses, show the AuthLoginProgressIndicator().
  2. When the oneTapSignInResponse changes, check if the response is Response.Success then call launch(signInResult:) and pass the result.
  3. When the googleSignInResponse changes, check on Response.Success result, then dismiss the LoginScreen.
// Scaffold content...

when(val oneTapSignInResponse = DataProvider.oneTapSignInResponse) {
// 1.
is Response.Loading -> {
Log.i("Login:OneTap", "Loading")
AuthLoginProgressIndicator()
}
// 2.
is Response.Success -> oneTapSignInResponse.data?.let { signInResult ->
LaunchedEffect(signInResult) {
launch(signInResult)
}
}
is Response.Failure -> LaunchedEffect(Unit) {
Log.e("Login:OneTap", "${oneTapSignInResponse.e}")
}
}

when (val signInWithGoogleResponse = DataProvider.googleSignInResponse) {
// 1.
is Response.Loading -> {
Log.i("Login:GoogleSignIn", "Loading")
AuthLoginProgressIndicator()
}
// 3.
is Response.Success -> signInWithGoogleResponse.data?.let { authResult ->
Log.i("Login:GoogleSignIn", "Success: $authResult")
loginState?.let { it.value = false }
}
is Response.Failure -> {
Log.e("Login:GoogleSignIn", "${signInWithGoogleResponse.e}")
}
}

Take it for a spin

Run the app 📲 on your phone or Emulator. You should now be able to sign-in and link with Google, and Sign out again.

One Tap Sign-in.
Firebase user with Google provider.

Note

If you authenticated anonymously and then linked with Google credentials, Firebase will not create a new user, but will update identifier and providers on the anonymous user.

Next Steps

Now that user can authenticate using Google, let’s handle Firebase auth link errors next.

--

--