Firebase Authentication in Jetpack Compose
Sign in with Google
Learn how to authenticate user with Sign in with Google and Firebase Authentication
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
- In the Firebase console, open the Authentication section.
- On the Sign in method tab, click Add new provider, enable the Google provider and click 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:
- Use the
.GoogleIdTokenRequestOptions()
to enable and configure the Google ID token request (required for apps that use Google Sign-in). - 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?) - In
signInRequest
, only show the accounts that were previously used to sign in, by passingtrue
to.setFilterByAuthorizedAccounts(filterByAuthorizedAccounts:)
. - Enable automatically sign-in when exactly one credential is retrieved.
- In
SignUpRequest
, show all available accounts on the device by passingfalse
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:
- Start the sign-in process by calling
beginSignIn(beginSignInRequest:)
on theSignInClient
object and pass thesignInRequest
. - If the sign-in process failed because the user is not previously authorized, then start the sign-up process by calling
beginSignIn(beginSignInRequest:)
on theSignInClient
object and pass thesignUpRequest
.
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:
- Create a
AuthCredential
using thegoogleIdToken
from the givenSignInCredential
. - Then call the previously created
authenticateUser(credentials:)
and pass thegoogleCredential
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:
- Create
rememberLauncherForActivityResult
to register a request, it takes anIntentSender
sent from thebeginSignIn(beginSignInRequest:)
method. - If the
ActivityResult
returnedRESULT_OK
, then we get aSignInCredential
from the result intent and pass it tosignInWithGoogle(credential:)
. - Build an
IntentSenderRequest
fromsignInResult.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.
- In case of
Response.Loading
in any responses, show theAuthLoginProgressIndicator()
. - When the
oneTapSignInResponse
changes, check if the response isResponse.Success
then calllaunch(signInResult:)
and pass the result. - When the
googleSignInResponse
changes, check onResponse.Success
result, then dismiss theLoginScreen
.
// 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.
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.
Resources
“ Everyone has something to learn. Everyone has something to teach.” ~ Paul Hudson