Last active
February 14, 2025 10:52
-
-
Save SamuelWakoli/3235ba0a912d73b35e983e448f1ed84f to your computer and use it in GitHub Desktop.
A Firebase authentication manager for Android that integrates Google and email/password sign-in with credential management and password reset support.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ... | |
// Dependencies: | |
// Google ID: com.google.android.libraries.identity.googleid | |
// Android Credentials: androidx.credentials | |
// Auth Play Services: androidx.credentials.play.services.auth | |
import android.content.Context | |
import android.util.Log | |
import androidx.credentials.CreatePasswordRequest | |
import androidx.credentials.CredentialManager | |
import androidx.credentials.GetCredentialRequest | |
import androidx.credentials.GetCredentialResponse | |
import androidx.credentials.GetPasswordOption | |
import androidx.credentials.PasswordCredential | |
import androidx.credentials.exceptions.GetCredentialException | |
import androidx.credentials.exceptions.NoCredentialException | |
import com.google.android.libraries.identity.googleid.GetGoogleIdOption | |
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential | |
import com.google.firebase.auth.GoogleAuthProvider | |
import com.google.firebase.auth.ktx.auth | |
import com.google.firebase.ktx.Firebase | |
import com.kcauptti.auth.BuildConfig | |
import com.kcauptti.auth.domain.model.auth.SignInResult | |
import kotlinx.coroutines.tasks.await | |
class AuthManager { | |
private val auth = Firebase.auth | |
// Configuration for Google ID sign-in | |
private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() | |
.setFilterByAuthorizedAccounts(false) | |
.setServerClientId(BuildConfig.WEB_CLIENT_ID) | |
.setAutoSelectEnabled(true) | |
.setNonce(NonceGenerator.generateNonce()) | |
.build() | |
private val googleCredentialRequest: GetCredentialRequest = GetCredentialRequest.Builder() | |
.addCredentialOption(googleIdOption) | |
.build() | |
// Sign in with Google account | |
suspend fun signInWithGoogle(context: Context): SignInResult { | |
val credentialManager: CredentialManager = CredentialManager.create(context) | |
return try { | |
val result: GetCredentialResponse = credentialManager.getCredential( | |
request = googleCredentialRequest, | |
context = context | |
) | |
val credential = result.credential | |
val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data) | |
val idToken = googleIdTokenCredential.idToken | |
val googleCredentials = GoogleAuthProvider.getCredential(idToken, null) | |
val user = auth.signInWithCredential(googleCredentials).await().user | |
SignInResult(user = user, errorMessage = null) | |
} catch (e: NoCredentialException) { | |
Log.e(TAG, "No Google account found", e) | |
val customError = NoCredentialException(errorMessage = "No Google account found") | |
SignInResult(user = null, errorMessage = customError.message) | |
} catch (e: GetCredentialException) { | |
Log.e(TAG, "Google sign-in failed", e) | |
SignInResult(user = null, errorMessage = e.message) | |
} | |
} | |
// Handle sign-in button click (password or Google) | |
suspend fun onClickSignInButton(context: Context): SignInResult { | |
val credentialManager: CredentialManager = CredentialManager.create(context) | |
return try { | |
val multipleRequest = GetCredentialRequest.Builder() | |
.addCredentialOption(googleIdOption) | |
.addCredentialOption(GetPasswordOption()) | |
.build() | |
val result: GetCredentialResponse = credentialManager.getCredential( | |
request = multipleRequest, | |
context = context | |
) | |
val credential = result.credential | |
when (credential) { | |
is PasswordCredential -> { | |
val user = auth.signInWithEmailAndPassword( | |
credential.id, credential.password | |
).await().user | |
SignInResult(user = user, errorMessage = null) | |
} | |
is GoogleIdTokenCredential -> { | |
val googleIdTokenCredential = | |
GoogleIdTokenCredential.createFrom(credential.data) | |
val idToken = googleIdTokenCredential.idToken | |
val googleCredentials = GoogleAuthProvider.getCredential(idToken, null) | |
val user = auth.signInWithCredential(googleCredentials).await().user | |
SignInResult(user = user, errorMessage = null) | |
} | |
else -> SignInResult(user = null, errorMessage = "Unknown credential type") | |
} | |
} catch (e: NoCredentialException) { | |
val message = "No saved Google or Password credential found on the device" | |
Log.e(TAG, message, e) | |
val customError = NoCredentialException(errorMessage = message) | |
SignInResult(user = null, errorMessage = customError.message) | |
} catch (e: GetCredentialException) { | |
Log.e(TAG, "Google sign-in failed", e) | |
SignInResult(user = null, errorMessage = e.message) | |
} catch (e: Exception) { | |
SignInResult(user = null, errorMessage = e.message) | |
} | |
} | |
// Sign in with email and password | |
suspend fun signInWithPassword( | |
context: Context, | |
email: String, | |
password: String | |
): SignInResult { | |
val credentialManager: CredentialManager = CredentialManager.create(context) | |
return try { | |
val user = auth.signInWithEmailAndPassword(email, password).await().user | |
val passwordRequest = CreatePasswordRequest(id = email, password = password) | |
try { | |
// Attempt to create a credential if possible, but allow sign-in even without it. | |
credentialManager.createCredential(context = context, request = passwordRequest) | |
} catch (_: Exception) { | |
// Do nothing. Possibly user has an older device. | |
} | |
SignInResult(user = user, errorMessage = null) | |
} catch (e: Exception) { | |
SignInResult(user = null, errorMessage = e.message) | |
} | |
} | |
// Register with email and password | |
suspend fun registerWithPassword( | |
context: Context, | |
email: String, | |
password: String | |
): SignInResult { | |
val credentialManager: CredentialManager = CredentialManager.create(context) | |
return try { | |
val user = auth.createUserWithEmailAndPassword(email, password).await().user | |
val passwordRequest = CreatePasswordRequest(id = email, password = password) | |
try { | |
credentialManager.createCredential(context = context, request = passwordRequest) | |
} catch (_: Exception) { | |
// Do nothing. Possibly user has an older device. | |
} | |
SignInResult(user = user, errorMessage = null) | |
} catch (e: Exception) { | |
SignInResult(user = null, errorMessage = e.message) | |
} | |
} | |
// Forgot password | |
suspend fun forgotPassword( | |
email: String, | |
onSuccess: () -> Unit, | |
onFailure: (String) -> Unit | |
) { | |
try { | |
auth.sendPasswordResetEmail(email).await() | |
onSuccess() | |
} catch (e: Exception) { | |
onFailure(e.message ?: "Unknown error") | |
} | |
} | |
companion object { | |
private const val TAG = "AuthManager" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment