android - Firebase phone authentication recaptch - android

I'm working on an android app which require Firebase phone authentication, When I released my app, a recapture still required as in debug mode, I enabled Android Device Verification in google cloud console, also I added google play console SHA-256, SHA-1 to Firebase project setting.
Bellow my Kotlin code of Firebase authentication:
fun initFirebase() {
mAuth = FirebaseAuth.getInstance()
}
fun sendOtp(mobile: String, resend: Boolean) {
if (resend) resend(mobile)
else send(mobile)
}
private fun send(mobile: String) {
val options = PhoneAuthOptions.newBuilder(mAuth)
.setPhoneNumber(mobile)
.setTimeout(5L, TimeUnit.SECONDS)
.setActivity(requireActivity())
.setCallbacks(mCallbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
Also at many time I get VerificationFailed without getting reason of this failure
private val mCallbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(authCredential: PhoneAuthCredential) {}
override fun onVerificationFailed(e: FirebaseException) {
events.value = Constant.EVENT_SEND_FAILED
Log.v("TAG","VerificationFailed")
}
override fun onCodeSent(
verificationId: String,
forceResendingToken: PhoneAuthProvider.ForceResendingToken
) {
Log.v("TAG","Success")
}
and in verification fragment:
private lateinit var mAuth: FirebaseAuth
var mVerificationId: String = ""
fun verifyOtp(otp: String) {
val credential = PhoneAuthProvider.getCredential(
mVerificationId,
otp
)
mAuth.signInWithCredential(credential).addOnCompleteListener(requireActivity()) {
if (it.isSuccessful) {
mAuth.currentUser?.getIdToken(true)?.addOnCompleteListener { task ->
if (task.isSuccessful && task.result != null) {
val phoneVerificationToken = task.result.token
if (phoneVerificationToken != null) {
Log.v("TAG","Success")
}
} else {
Log.v("TAG","Error")
}
}
} else {
Log.v("TAG","Invalid code")
}
}
}

Related

How to handle those specific exceptions from repository? (Android Kotlin)

I'm new to android and I'm developing a few applications for studying.
I've been trying to improve a code that I have but I got stuck in the following problem:
I'm creating a new user, validating it with Google Firebase. I managed to create a user normally but I'm not able to handle with one exception from the register moment which is the "FirebaseAuthUserCollisionException".
I created a class to handle a most of exceptions from email/password mistakes:
class AddUser(private val repository: UserRepository) {
#Throws(InvalidUserException::class)
suspend operator fun invoke(user: UserModel) {
if(user.email.isEmpty()) {
throw InvalidUserException("Email cannot be empty")
}
if(!Patterns.EMAIL_ADDRESS.matcher(user.email).matches()) {
throw InvalidUserException("Email is not valid")
}
if(user.password.length <= 5) {
throw InvalidUserException("Password should contain at least 6 characters")
}
if(user.password.isEmpty()) {
throw InvalidUserException("Password cannot be empty")
}
if(user.confirmPassword.isEmpty()) {
throw InvalidUserException("Confirm password cannot be empty")
}
if(user.password != user.confirmPassword) {
throw InvalidUserException("Passwords does not match")
}
repository.insert(user)
}
}
My repository:
class UserRepositoryImpl: UserRepository {
private var auth: FirebaseAuth = Firebase.auth
private var database: DatabaseReference = FirebaseDatabase.getInstance().getReference("users")
override suspend fun insert(user: UserModel) {
auth = FirebaseAuth.getInstance()
auth.createUserWithEmailAndPassword(user.email, user.password).addOnCompleteListener {
if(it.isSuccessful) {
database.child(user.id.toString()).setValue(user)
} else {
//exception here
}
}
}
}
When this function is triggered, it navigates to another fragment and toasts the successful message, which is incorrect because the exception happens.
Fragment:
private fun configEventFlow() = lifecycleScope.launch {
viewModel.eventFlow.collectLatest { event ->
when(event) {
is RegisterViewModel.UiEvent.ShowToast -> {
toast(event.message)
}
is RegisterViewModel.UiEvent.SaveUser -> {
val action = RegisterFragmentDirections.actionRegisterFragmentToMainFragment()
findNavController().navigate(action)
toast(getString(R.string.register_successfully))
}
}
}
}
private fun configUserRegistration() = with(binding) {
fabRegister.setOnClickListener {
val email = editRegisterEmail.text.toString()
viewModel.onEvent(RegisterUserEvents.EnteredEmail(email))
val password = editRegisterPassword.text.toString()
viewModel.onEvent(RegisterUserEvents.EnteredPassword(password))
val confirmPassword = editRegisterPasswordConfirm.text.toString()
viewModel.onEvent(RegisterUserEvents.EnteredConfirmPassword(confirmPassword))
viewModel.onEvent(RegisterUserEvents.SaveUser)
}
}
ViewModel:
#HiltViewModel
class RegisterViewModel #Inject constructor(private val useCases: UserUseCases): ViewModel() {
private val _email = MutableStateFlow<ResourceState<String>>(ResourceState.Empty())
private val email: StateFlow<ResourceState<String>> = _email
private val _password = MutableStateFlow<ResourceState<String>>(ResourceState.Empty())
private val password: StateFlow<ResourceState<String>> = _password
private val _confirmPassword = MutableStateFlow<ResourceState<String>>(ResourceState.Empty())
private val confirmPassword: StateFlow<ResourceState<String>> = _confirmPassword
private val _eventFlow = MutableSharedFlow<UiEvent>()
val eventFlow = _eventFlow.asSharedFlow()
fun onEvent(event: RegisterUserEvents) {
when(event) {
is RegisterUserEvents.EnteredEmail -> {
_email.value = ResourceState.Success(event.value)
}
is RegisterUserEvents.EnteredPassword -> {
_password.value = ResourceState.Success(event.value)
}
is RegisterUserEvents.EnteredConfirmPassword -> {
_confirmPassword.value = ResourceState.Success(event.value)
}
is RegisterUserEvents.SaveUser -> {
viewModelScope.launch {
try {
useCases.addUser(
UserModel(
id = System.currentTimeMillis().toInt(),
email = email.value.data!!,
password = password.value.data!!,
confirmPassword = confirmPassword.value.data!!
)
)
_eventFlow.emit(UiEvent.SaveUser)
} catch(e: InvalidUserException) {
_eventFlow.emit(UiEvent.ShowToast(message = e.message!!))
}
}
}
}
}
sealed class UiEvent {
data class ShowToast(val message: String): UiEvent()
object SaveUser: UiEvent()
}
}
Is there a way that I can manage this specific exception in this pattern? Even if I catch the exception there, the action is completed and my application follows at it was registered but in the database it does not occur because of the exception. Im sure that I'll have to face it again when login to handle specific exceptions from Firebase, which I cannot create this way but I have to receive them and display to the user.
Any suggestions??
Sorry if it's missing any content, tell me and I update asap.
Thanks in advance.

How we can link multiple authentication providers for phone and email in jetpack compose?

I have mail and mobile authentication in my register app, in firebase when user sign up with mail and mobile phone it generate two different UID for a same user, what I wish to achieve is one user with one UID can login with email or login by phone number (Merge the phone number and the email in authentication). May I possible to achieve this at Firebase? I have example here but I am not understand it how to implement to jetpack compose, any idea?
viewmodelphone:
#HiltViewModel
class AuthenticationViewModel #Inject constructor(
) : ViewModel() {
private val mAuth = FirebaseAuth.getInstance()
var verificationOtp = ""
var popNotification = mutableStateOf<Event<String>?>(null)
private lateinit var baseBuilder: PhoneAuthOptions.Builder
fun setActivity(activity: Activity) {
baseBuilder =
PhoneAuthOptions.newBuilder().setActivity(activity)
}
fun send(mobileNum: String) {
val options = baseBuilder
.setPhoneNumber("+91$mobileNum")
.setTimeout(60L, TimeUnit.SECONDS)
.setCallbacks(object :
PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(p0: PhoneAuthCredential) {
handledException(customMessage = "Verification Completed")
}
override fun onVerificationFailed(p0: FirebaseException) {
handledException(customMessage = "Verification Failed")
}
override fun onCodeSent(otp: String, p1: PhoneAuthProvider.ForceResendingToken) {
super.onCodeSent(otp, p1)
verificationOtp = otp
handledException(customMessage = "Otp Send Successfully")
}
}).build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
fun otpVerification(otp: String) {
val credential = PhoneAuthProvider.getCredential(verificationOtp, otp)
FirebaseAuth.getInstance().signInWithCredential(credential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
handledException(customMessage = "Verification Successful")
} else {
handledException(customMessage = "Wrong Otp")
}
}
}
private fun handledException(exception: Exception? = null, customMessage: String = "") {
exception?.printStackTrace()
val errorMsg = exception?.message ?: ""
val message = if (customMessage.isEmpty()) {
errorMsg
} else {
"$customMessage: $errorMsg"
}
popNotification.value = Event(message)
}
}
I solve the problem when I add auth.currentUser?.linkWithCredential(credential) line of the code in otpVerification function.
fun otpVerification(otp: String) {
val credential = PhoneAuthProvider.getCredential(verificationOtp, otp)
auth.currentUser?.linkWithCredential(credential)
FirebaseAuth.getInstance().signInWithCredential(credential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
handledException(customMessage = "Verification Successful")
} else {
handledException(customMessage = "Wrong Otp")
}
}
}

firebase google sign in result code is always 0

I wanted to integrate google sign In in my app using firebase and I followed all the instructions from enabling google sign In in my firebase console and adding SHA-1 certificate fingerprint and adding the google-services.json file in the app directory.
But whenever I am trying to sign In after selecting the google account the response code is coming out be 0 always and hence unable to sign In.
Here is the Code:-
class SignInActivity : AppCompatActivity() {
private lateinit var googleSignInClient: GoogleSignInClient
private lateinit var auth:FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_in)
val gso= GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this,gso)
signInButton.setOnClickListener {
resultLauncher.launch(googleSignInClient.signInIntent)
}
}
private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){result->
Log.i("resultCode",result.resultCode.toString())
val intent=result.data
Log.i("intentData",intent.toString())
if(result.resultCode == Activity.RESULT_OK)
{
Log.i("resultCode","reachedHere")
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
handleSignInResult(task)
}
else{
Log.i("unsuccessfulSignIN",result.resultCode.toString())
}
}
private fun handleSignInResult(task: Task<GoogleSignInAccount>?) {
try{
val account= task?.getResult(ApiException::class.java)!!
Log.i("account","firebaseAuthWithGoogle:"+account.id)
firebaseAuthWithGoogle(account.idToken!!)
}catch (e : ApiException){
Log.i(ContentValues.TAG, "Google sign in failed", e)
}
}
private fun firebaseAuthWithGoogle(idToken: String) {
signInButton.visibility= View.GONE
progressBar.visibility= View.VISIBLE
val credential= GoogleAuthProvider.getCredential(idToken,null)
GlobalScope.launch(Dispatchers.IO) {
val auth=auth.signInWithCredential(credential).await()
val firebaseUser=auth.user
Log.i("user",firebaseUser.toString())
withContext(Dispatchers.Main){
updateUI(firebaseUser)
}
}
}
private fun updateUI(firebaseUser: FirebaseUser?) {
if(firebaseUser != null)
{
val intent= Intent(this,MainActivity::class.java)
startActivity(intent)
Log.i("intent","Intent Started")
// Toast.makeText(applicationContext,"Sign In Successful",Toast.LENGTH_SHORT).show()
finish()
}
else{
signInButton.visibility= View.VISIBLE
progressBar.visibility=View.GONE
Toast.makeText(this,"Sign In failed", Toast.LENGTH_SHORT).show()
}
}
}
Here is the logcat ( I have highlighted the result code that I logged)
I am very new to android development and this my first time working with firebase

Unable to integrate google sign in properly in android app

I wanted to add the sign in with google feature of firebase I connected my app to firebase then added the SHA-1 certificate fingerprint and wrote the below code.
Once I click on the sign in button the intent opens a chooser to select a google account but after selecting one account nothing happens even there is no exceptions or error in the logcat.
Code:
class SignInActivity : AppCompatActivity() {
private lateinit var googleSignInClient:GoogleSignInClient
private lateinit var auth:FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_in)
val gso= GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this,gso)
signInButton.setOnClickListener {
resultLauncher.launch(googleSignInClient.signInIntent)
}
}
private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
val intent=it.data
if(it.resultCode == Activity.RESULT_OK)
{
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
handleSignInResult(task)
}
}
private fun handleSignInResult(task: Task<GoogleSignInAccount>?) {
try{
val account= task?.getResult(ApiException::class.java)!!
Log.i("account","firebaseAuthWithGoogle:"+account.id)
firebaseAuthWithGoogle(account.idToken!!)
}catch (e : ApiException){
Log.i(TAG, "Google sign in failed", e)
}
}
private fun firebaseAuthWithGoogle(idToken: String) {
signInButton.visibility= View.GONE
progressBar.visibility=View.VISIBLE
val credential=GoogleAuthProvider.getCredential(idToken,null)
GlobalScope.launch(Dispatchers.IO) {
val auth=auth.signInWithCredential(credential).await()
val firebaseUser=auth.user
Log.i("user",firebaseUser.toString())
withContext(Dispatchers.Main){
updateUI(firebaseUser)
}
}
}
private fun updateUI(firebaseUser: FirebaseUser?) {
if(firebaseUser != null)
{
val intent=Intent(this,MainActivity::class.java)
startActivity(intent)
Log.i("intent","Intent Started")
// Toast.makeText(applicationContext,"Sign In Successful",Toast.LENGTH_SHORT).show()
finish()
}
else{
signInButton.visibility= View.VISIBLE
progressBar.visibility=View.GONE
Toast.makeText(this,"Sign In failed",Toast.LENGTH_SHORT).show()
}
}
}
Logcat:
I am very new to android development and this is my first time with firebase.

FirebaseUser cannot reauthenitcate to delete account

As the title suggests I am having trouble deleting a Firebase user. I have 2 sign in types enabled in Firebase console :
Anonymous
Google
These providers types are mirrored in the application and signing in is not an issue using firebase-ui-auth
listOf(
IdpConfig.AnonymousBuilder().build(),
IdpConfig.GoogleBuilder().build())
I want the user to be able to delete their account, this works fine for Anonymous users but fails for Users that signed in with a google account using a GoogleAuthCredential. In order to do this the documentation states you need to "reauthenticate" : FirebaseUser::reauthenticate. This is where I am having trouble and re-authentication always fails with :
FirebaseAuthInvalidCredentialsException
ERROR_INVALID_CREDENTIAL
The supplied auth credential is malformed or has expired. [ Invalid id_token in IdP response: <token provided in request>, error: id token is not issued by Google. ]
I have checked the token is within the UTC expiry time, and my device clock is set correctly.
Current Code (using coroutines):
class UserActions internal constructor(
private val context: Context,
private val authUI: AuthUI,
private val auth: FirebaseAuth) {
suspend fun signOut(): Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.signOut(context)) }
suspend fun delete(): Boolean {
auth.currentUser
?.takeIf { user -> !user.isAnonymous }
?.let { user ->
val tokenResult: GetTokenResult = suspendCoroutine { cont -> cont.suspendTask(user.getIdToken(true)) }
val credential : AuthCredential = GoogleAuthProvider.getCredential(tokenResult.token, null)
// Point of failure - always returns false with above error.
val success: Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(user.reauthenticate(credential)) }
if (!success) return false
}
return suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.delete(context)) }
}
private fun <R> Continuation<R>.suspendTask(task: Task<R>) {
task.addOnSuccessListener { this.success(it) }
.addOnFailureListener { this.failure(it) }
}
private fun Continuation<Boolean>.suspendCompletableTask(task: Task<Void>) {
task.addOnSuccessListener { this.success() }
.addOnFailureListener { this.failure() }
}
private fun Continuation<Boolean>.success() = resume(true)
private fun Continuation<Boolean>.failure() = resume(false)
private fun <R> Continuation<R>.success(r : R) = resume(r)
private fun <R> Continuation<R>.failure(t : Exception) = resumeWithException(t)
}
I thought that maybe I had the token incorrectly added as a parameter arguemnt for :
GoogleAuthProvider.getCredential(tokenResult.token, null)
So swapped to :
GoogleAuthProvider.getCredential(null, tokenResult.token)
But said I had an invalid value in the error description so I have the argument correct for the AuthCredential and a "valid" id token as far as I can see.
What am I doing wrong here?
Solved : Simple answer to this one in the end.
Both FirebaseUser and GoogleSigInAccount refer to having a idToken. I was using the former for the token here :
val tokenResult: GetTokenResult = suspendCoroutine { cont -> cont.suspendTask(user.getIdToken(true)) }
val credential : AuthCredential = GoogleAuthProvider.getCredential(tokenResult.token, null)
The AuthCredential was now using the incorrect token. What I should have used was :
val token = GoogleSignIn.getLastSignedInAccount(context)?.idToken.orEmpty()
val credential : AuthCredential = GoogleAuthProvider.getCredential(token, null)
So the error was correct - I was using the FirebaseUser token when reauthenticating a GoogleAuthCredential which should be using the GoogleSigInAccount token.
Update
As tokens are short lived the above can still fail if the token becomes stale. the solution is to perform a "silent sign in" to refresh the token. This cannot be done through FirebaseAuth::silentSignin as this fails if a FirebaseUser is already signed in. It requires a call to GoogleSignInClient::silentSignIn.
Revised full code :
class UserActions internal constructor(
private val context: Context,
private val authUI: AuthUI,
private val auth: FirebaseAuth) {
suspend fun signOut(): Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.signOut(context)) }
suspend fun delete(): Boolean {
auth.currentUser
?.takeIf { user -> !user.isAnonymous }
?.let { user ->
val credential: AuthCredential = GoogleAuthProvider.getCredential(getFreshGoogleIdToken(), null)
val success: Boolean = suspendCoroutine { cont -> cont.suspendCompletableTask(user.reauthenticate(credential)) }
if (!success) return false
}
return suspendCoroutine { cont -> cont.suspendCompletableTask(authUI.delete(context)) }
}
private suspend fun getFreshGoogleIdToken(): String = suspendCoroutine<GoogleSignInAccount> { cont ->
cont.suspendTask(
GoogleSignIn.getClient(
context,
GoogleSignInOptions.Builder()
.requestProfile()
.requestId()
.requestIdToken(context.getString(R.string.default_web_client_id))
.build())
.silentSignIn())
}.idToken.orEmpty()
private fun <R> Continuation<R>.suspendTask(task: Task<R>) {
task.addOnSuccessListener { this.success(it) }
.addOnFailureListener { this.failure(it) }
}
private fun Continuation<Boolean>.suspendCompletableTask(task: Task<Void>) {
task.addOnSuccessListener { this.success() }
.addOnFailureListener { this.failure() }
}
private fun Continuation<Boolean>.success() = resume(true)
private fun Continuation<Boolean>.failure() = resume(false)
private fun <R> Continuation<R>.success(r: R) = resume(r)
private fun <R> Continuation<R>.failure(t: Exception) = resumeWithException(t)
}

Categories

Resources