Getting wrong access token from registerCallback method of facebook LoginButton - firebase

I can successfully log into facebook by calling this signIn() method.
private fun signIn() {
loginBtn?.registerCallback(callBackManager,object: FacebookCallback<LoginResult>{
override fun onSuccess(result: LoginResult?) {
handleFacebookAccessToken(result!!.accessToken)
}
override fun onCancel() {
}
override fun onError(error: FacebookException?) {
Log.d("MainActivity:", "onError "+error?.message)
}
})
}
private fun handleFacebookAccessToken(accessToken: AccessToken?) {
val authCredential = FacebookAuthProvider.getCredential(accessToken!!.token)
firebaseAuth?.signInWithCredential(authCredential)
?.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d("MainActivity:", "signInWithCredential:success")
} else {
// If sign in fails, display a message to the user.
Log.w("dsds", "signInWithCredential:failure ", task.exception)
Toast.makeText(baseContext, "Authentication failed.",
Toast.LENGTH_SHORT).show()
}
}
}
But I am not able to authenticate with firebase to sign into firebase by calling handleFacebookAccessToken(accessToken: AccessToken?) method because it is giving invalid access token.
If I manually get an access token from a test user by going to the app in facebook developer console and
hardcode it in the handleFacebookAccessToken(token: String?) method below then it works.
private fun handleFacebookAccessToken(token: String?) {
val authCredential = FacebookAuthProvider.getCredential(token)
firebaseAuth?.signInWithCredential(authCredential)
?.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d("MainActivity:", "signInWithCredential:success")
} else {
// If sign in fails, display a message to the user.
Log.w("dsds", "signInWithCredential:failure ", task.exception)
Toast.makeText(baseContext, "Authentication failed.",
Toast.LENGTH_SHORT).show()
}
}
}
I am getting this exception below.
com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The supplied auth credential is malformed or has expired. [ Remote site 5XX from facebook.com for VERIFY_CREDENTIAL ]
at com.google.android.gms.internal.firebase-auth-api.zztt.zza(com.google.firebase:firebase-auth##20.0.4:28)
at com.google.android.gms.internal.firebase-auth-api.zzvb.zza(com.google.firebase:firebase-auth##20.0.4:9)
at com.google.android.gms.internal.firebase-auth-api.zzvc.zzk(com.google.firebase:firebase-auth##20.0.4:1)
at com.google.android.gms.internal.firebase-auth-api.zzuz.zzh(com.google.firebase:firebase-auth##20.0.4:25)
at com.google.android.gms.internal.firebase-auth-api.zztq.zzk(com.google.firebase:firebase-auth##20.0.4:1)
at com.google.android.gms.internal.firebase-auth-api.zzpr.zza(com.google.firebase:firebase-auth##20.0.4:2)
at com.google.android.gms.internal.firebase-auth-api.zzvg.zza(com.google.firebase:firebase-auth##20.0.4:25)
at com.google.android.gms.internal.firebase-auth-api.zzuq.zzd(com.google.firebase:firebase-auth##20.0.4:4)
at com.google.android.gms.internal.firebase-auth-api.zzpy.zzc(com.google.firebase:firebase-auth##20.0.4:4)
at com.google.android.gms.internal.firebase-auth-api.zztu.zzd(com.google.firebase:firebase-auth##20.0.4:5)
at com.google.android.gms.internal.firebase-auth-api.zzsg.zzd(com.google.firebase:firebase-auth##20.0.4:3)
at com.google.android.gms.internal.firebase-auth-api.zzsf.accept(Unknown Source:6)
at com.google.android.gms.common.api.internal.zacj.doExecute(com.google.android.gms:play-services-base##17.1.0:2)
at com.google.android.gms.common.api.internal.zaf.zac(com.google.android.gms:play-services-base##17.1.0:6)
at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zac(com.google.android.gms:play-services-base##17.1.0:167)
at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zab(com.google.android.gms:play-services-base##17.1.0:139)
at com.google.android.gms.common.api.internal.GoogleApiManager$zaa.zaa(com.google.android.gms:play-services-base##17.1.0:105)
at com.google.android.gms.common.api.internal.GoogleApiManager.handleMessage(com.google.android.gms:play-services-base##17.1.0:145)
at android.os.Handler.dispatchMessage(Handler.java:103)
at com.google.android.gms.internal.base.zar.dispatchMessage(com.google.android.gms:play-services-base##17.1.0:8)
at android.os.Looper.loop(Looper.java:224)
at android.os.HandlerThread.run(HandlerThread.java:67)

Login to https://developers.facebook.com/apps/
Select app and confirm App Type is Consumer
Solution referred from https://github.com/FirebaseExtended/flutterfire/issues/4524#issuecomment-764610739

Related

Android Studio Kotlin Firebase Authentication issues with email formatting

First of all I'm very new to coding, so perhaps this will be easy to explain, but I've been stuck on it for several weeks and I can't seem to find the solution.
I'm doing a tutorial on Firebase authentication, and I'm trying to create a user with email and password. I keep getting this Log: "com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The email address is badly formatted." I'm using a pixel5 emulator, latest version of Android Studio and compileSdk 33.
I will show you my code below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
auth = Firebase.auth
val email = binding.enterEmailtxt.toString().trim()
val password = binding.enterPasstxt.toString().trim()
binding.CreateAcctBtn.setOnClickListener {
Log.d("EMAIL STRING:", email)
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this) {
task -\>
if (task.isSuccessful) {
var user: FirebaseUser? = auth.currentUser
if (user != null) {
Log.d("USER:", user.email.toString())
}
} else {
Log.d("USER FAIL:", task.exception.toString())
}
}
}
//I have this user in firebase already
auth.signInWithEmailAndPassword("email#gmail.com", "password123")
.addOnCompleteListener(this) {
task -\>
if (task.isSuccessful) {
//Sign is was successful
Log.d("SUCCESS:", "User signed in")
Toast.makeText(this, "Signed in Successfully", Toast.LENGTH_LONG).show()
}else {
//Not successful
Log.d("FAILURE:", "User not signed in")
Toast.makeText(this, "Sign in not successful", Toast.LENGTH_LONG).show()
}
}
}
public override fun onStart() {
super.onStart()
// Check if user is signed in (non-null) and update UI accordingly.
val currentUser = auth.currentUser
if (currentUser != null) {
Toast.makeText(this, "User is logged in", Toast.LENGTH_LONG).show()
Log.d("LOGIN SUCCESS:", "User is logged in")
}else {
Toast.makeText(this, "User is logged out", Toast.LENGTH_LONG).show()
Log.d("LOGIN FAILURE:", "User is logged out")
}
the "EMAIL STRING:" log is so I could see what email string Firebase is actually getting, and in logcat this is what I am seeing:
EMAIL STRING: com.example.introtokotlinfirebase D androidx.appcompat.widget.AppCompatEditText{8c1ff5b VFED..CL. ......I. 0,0-0,0 #7f0800ba app:id/enterEmailtxt}
Maybe that's normal?? Like I said I'm very new at this. I do have the input type as textEmailAddress in the XML.
I tried creating a user with several different email addresses that I know are legit, 1 is a gmail address.
I did have a lot of extra code commented out that I deleted just FYI
Thank you! in advance :)

Microsoft Login window not popping up using Firebase Auth in SwiftUI

I am using SwiftUI with the Firebase SDK. I am trying to implement Sign In with Microsoft/Azure in my app. I am following what the docs say but when I get to the part that is supposed to make the login window pop up, nothing shows. There are no errors printed to the console. Here is my login function:
func signInWithMicrosoft() {
print("Microsoft Login")
let auth = Auth.auth()
let provider = OAuthProvider(providerID: "microsoft.com", auth: auth)
provider.customParameters = [
"prompt": "consent",
"login_hint": "user#firstadd.onmicrosoft.com"
]
provider.scopes = ["mail.read", "openid.read"]
provider.getCredentialWith(_: nil) { credential, error in
if error != nil {
return
}
if credential != nil {
self.authenticateUserWithMicrosoft(for: credential)
}
}
}
and here is my auth function:
private func authenticateUserWithMicrosoft(for credential: AuthCredential?) {
Auth.auth().signIn(with: credential!) { [unowned self] (_, error) in
if let error = error {
print(error.localizedDescription)
} else {
state = .signedIn
}
}
}
I have tried many fixes such as putting my Auth code inside my AppDelegate class but nothing seems to have worked. Any help would be appreciated.

Apple sign in causes FIRAuthErrorUserInfoNameKey=ERROR_EMAIL_ALREADY_IN_USE (Code = 17007)

Using SwiftUI, Xcode12.5.1, Swift5.4.2, iOS14.7.1,
My Firebase-Email/Password Login-page shall be extended with other Login possibilities such as Apple-Login (eventually Google-login, Facebook-login etc).
My steps:
log in with Email/Password to Firebase
log out
log in with "Sign in with Apple"
--> Then I get the following error:
Error Domain=FIRAuthErrorDomain Code=17007
"The email address is already in use by another account."
UserInfo={NSLocalizedDescription=The email address is already in use by another account.,
FIRAuthErrorUserInfoNameKey=ERROR_EMAIL_ALREADY_IN_USE}
What I intended to do is to link the existing Email/Password-Firebase-Account to the Sign in with Apple-Account (as described here and here).
But for doing that I would need the error FIRAuthErrorUserInfoUpdatedCredentialKey that allows to retrieve the old user eventually.
In my case, I get ERROR_EMAIL_ALREADY_IN_USE which does not lead to any old user to be linked.
What do I have to do ?
Here is my code:
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
return
}
print("signed in with Apple...")
do {
// if user did log in with Email/Password previously
if let email = try THKeychain.getEmail(),
let password = try THKeychain.getPassword() {
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
if let user = authResult?.user {
// here I am trying to link the existing Firebase-Email/Password account to the just signed-in with Apple account
user.link(with: credential) { (result, linkError) in
print(linkError) // this is where I get FIRAuthErrorUserInfoNameKey=ERROR_EMAIL_ALREADY_IN_USE
// unfortunately, the two accounts are not linked as expected due to this error !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// What is missing ??????????????????
loginStatus = true
}
}
} else {
loginStatus = true
}
} catch {
print(error.localizedDescription)
}
}
On the Firebase-documentation it sais:
Sign in with Apple will not allow you to reuse an auth credential to link to an existing account. If you want to link a Sign in with Apple credential to another account, you must first attempt to link the accounts using the old Sign in with Apple credential and then examine the error returned to find a new credential. The new credential will be located in the error's userInfo dictionary and can be accessed via the FIRAuthErrorUserInfoUpdatedCredentialKey key.
What does the part "...If you want to link a Sign in with Apple credential to another account, you must first attempt to link the accounts using the old Sign in with Apple credential..." exactly mean ? WHAT IS THE old Sign in with Apple credential ????????
And how would I do that ?
In fact, at the linking-call, I actually expected some sort of linkError.userInfo with an updated user to sign in with. But the linkError in my example only gives me the ERROR_EMAIL_ALREADY_IN_USE error without further userInfo.
As Peter Friese mentions in his Blog, I should somehow be able to retrieve a AuthErrorUserInfoUpdatedCredentialKey from the error.userInfo. But in my case, the linkError does not have any kind of such information - unfortunately!
Here is an excerpt of Peter's example: (again not applicable in my case for some unknown reason?????)
currentUser.link(with: credential) { (result, error) in // (1)
if let error = error, (error as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue { // (2)
print("The user you're signing in with has already been linked, signing in to the new user and migrating the anonymous users [\(currentUser.uid)] tasks.")
if let updatedCredential = (error as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as? OAuthCredential {
print("Signing in using the updated credentials")
Auth.auth().signIn(with: updatedCredential) { (result, error) in
if let user = result?.user {
// TODO: handle data migration
self.doSignIn(appleIDCredential: appleIDCredential, user: user) // (3)
}
}
}
}
}
Reversing the order of linking made me advance a tiny bit.
If I press the Sign in with Apple button, my code now logs in with Firebase-Email/Password first (i.e. the necessary credentials are taken from the Keychain). And on a second step, links with the Apple-credentials. And by doing so, the linking finally gives me the desired AuthErrorUserInfoUpdatedCredentialKey in the link-callback.
There I retrieve the updatedCredential to log in with Apple.
See code below.
HOWEVER, I STILL DON'T KNOW WHY AFTER LOGIN THIS WAY, MY DATA IS STILL MISSING ???????
HOW DOES THIS DATA-MIGRATION STEP WORK ???
Shouldn't the user.link(with: appleCredentials) { ... } do the job ?
What do I need to do in order to get the very same Firebase-Data, no matter the login method ???
let appleCredentials = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
do {
// if user did log in with Email/Password anytime before
if let email = try THKeychain.getEmail(),
let password = try THKeychain.getPassword() {
let firebaseEmailCredentials = EmailAuthProvider.credential(withEmail: email, password: password)
Auth.auth().signIn(with: firebaseEmailCredentials) { (authResult, error) in
if let user = authResult?.user {
user.link(with: appleCredentials) { (result, linkError) in
if let linkError = linkError, (linkError as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue {
print("The user you're signing in with has been linked.")
print("Signing in to Apple and migrating the email/pw-firebase-users [\(user.uid)]` data.")
if let updatedCredential = (linkError as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as? OAuthCredential {
print("Signing in using the updated credentials")
Auth.auth().signIn(with: updatedCredential) { (result, error) in
if let _ = result?.user {
print("signed in with Apple...")
// TODO: handle data migration
print("Data-migration takes place now...")
loginStatus = true
}
}
}
}
else if let error = error {
print("Error trying to link user: \(error.localizedDescription)")
}
else {
if let _ = result?.user {
loginStatus = true
}
}
}
}
}
} else {
// case where user never logged in with firebase-Email/Password before
Auth.auth().signIn(with: appleCredentials) { (result, error) in
if let _ = result?.user {
print("signed in with Apple...")
loginStatus = true
}
}
}
} catch {
print(error.localizedDescription)
}

flutter refresh the user token of firebase in order to access firebase real-time database. (In a period of time)

I was using swift and Kotlin before.
We need to refresh the user token when we are using auth user of firebase to access firebase real-time database. (In a period of time)
How to do it in flutter?
Here with the code of Kotlin:
var mAuth: FirebaseAuth? = null
mAuth = FirebaseAuth.getInstance()
val cUser = mAuth?.currentUser
if (cUser!=null) {
cUser.getIdToken(true).addOnCompleteListener(object : OnCompleteListener<GetTokenResult> {
override fun onComplete(task: Task<GetTokenResult>) {
if (task.isSuccessful()) {
val idToken = task.getResult().getToken()
println("token has been refresh")
} else {
// Handle error -> task.getException();
println("token refresh Error Error Error Error Error Error")
val textError = context.getString(R.string.errorForInsertDataToFirebase)
alertPopUp(context, "", textError)
}
}
})
}

How to force logout firebase auth user from app remotely

I have a project which uses firebase auth with firebaseUI to authenticate users. I have enabled Google, Facebook and email providers. What I need is to remotely logout or disable some of the users.
I want the users to logout from the app on doing so. I tried disabling the user in the firebase console and also used the firebase admin SDK (https://firebase.google.com/docs/auth/admin/manage-sessions) to revoke the refresh tokens.
I waited for more than 2 days and still noticed that the user was logged in and could access the firestore data.
I have also gone through and tried
Firebase still retrieving authData after deletion
Can anyone point to what I am doing wrong ?
You also cannot remotely force a user to be signed out. Any sign out will have to happen from the device that the user is signed in on.
There is no way to revoke an access token once that is minted. This means that even if you disable the user's account, they may continue to have access for up to an hour.
If that is too long, the trick (as also mentioned in my answer to the question you linked) is to maintain a list of blocked users in your database (or elsewhere) and then check against that in your security rules (or other authorization layer).
For example in the realtime database, you could create a list of blocked user's UIDs:
banned_uids: {
"uid1": true
"uid2": true
}
And then check against that in your security rules with:
".read": "auth.uid !== null && !root.child('banned_uids').child(auth.uid).exists()"
You can send a message data with FCM to force to log out.
For example, if the users use android application.
Save the FCM token in a collection in firebase Realtime.
configure the Android client app, in the service. LINK You have to make when receive a message with especial string, force to log out.
make the trigger you need in cloud functions, to send the data LINK when you need the user log out.
SUCCESS!
As per your scenarios, i assume that you need to make user logout when user is disabled.
Use One global variable to store TokenNo (might be in shared preference or sqlite):
Add following code to your manifest:
<service android:name=".YourFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Add following code in your
public class LogoutOntokenchange extends FirebaseMessagingService{
#Override
public void onNewToken (String token){
if(TokenNo=>1){ //if tokenNo >=1 means he already logged in
TokenNo=0;
FirebaseAuth.getInstance().signOut(); //Then call signout method
}
else{
TokenNo=1; //store token no in db
}
}
}
What Happens here:
When user logged in first time onNewToken is called then It goes into else then TokenNo is updated to 1 from 0.
When You disable any user then automatically token is refreshed.Then OnNewToken is called then TokenNo>=1 so user will be logged out.
NOTE: When user log in for first time i.e if TokenNo variable is not stored then store it as 0.
For reference: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService
The only way I can think about is adding a if-else block in your starting activity.
Store the that status of user (verified/banned/deleted) in Firebase Real-time database. Then retrieve the status of user at start of application and add the code:
if (currentUserStatus.equals("banned"))
{
currentUser.logout();
}
What I've done is I created for each user upon registration a Firestore document with the UID as document ID. In this document I store an array which stores all fcm tokens the individual user receives when logging into a new device. That way I always keep track where the user is logged in. When the user logs out manually the fcm token will be deleted from the document in Firestore as well as on the device.
In order to be able to log out the user everywhere they are signed in I did the following. When starting the app and once the user is logged in I start a snapshot listener that listens to all changes in the users document. As soon as there is a change I retrieve the new array of fcm tokens, search inside the array for the local current device fcm token. If found, I do nothing. If the fcm token is no longer in the array I will call the local logout method and go back to the login screen.
Here are the methods I used in swift on iOS. The closures (passOnMethod) will just trigger an unwind segue to the login view controller.
import Foundation
import Firebase
class FB_Auth_Methods {
let db = Firestore.firestore()
var listener: ListenerRegistration?
func trackLoginStatus(passOnMethod: #escaping () -> () ) {
listener?.remove()
if let loggedInUserA_UID = Auth.auth().currentUser?.uid {
listener = db.collection(K.FStore.collectionOf_RegisteredUsers_Name)
.document(loggedInUserA_UID)
.addSnapshotListener { (snapshotDocument, error) in
if let error = error {
print(error)
} else {
if let document = snapshotDocument {
if let data = document.data() {
if let fcmTokens = data[K.FStore.Users.fcmTokens] as? [String] {
print("Found the following tokens: \(fcmTokens)")
self.compareTokensAgainstCurrentDeviceToken(fcmTokens: fcmTokens, passOnMethod: { () in
passOnMethod()
})
}
}
}
}
}
}
}
func compareTokensAgainstCurrentDeviceToken(fcmTokens: [String], passOnMethod: #escaping () -> () ) {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print(error)
} else if let result = result {
if fcmTokens.contains(result.token) {
print("Token found, doing nothing")
} else {
print("Token no longer found, logout user")
do {
try Auth.auth().signOut()
InstanceID.instanceID().deleteID { error in
if let error = error {
print(error)
} else {
passOnMethod()
}
}
} catch let signOutError as NSError {
print (signOutError)
}
}
}
}
}
}
And here is the method I use when logging out the user everywhere but at the current device.
func deleteAllFcmTokensExceptCurrent(loggedInUserA: User, passOnMethod: #escaping () -> () ) {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print(error)
} else if let result = result {
let batch = self.db.batch()
let deleteAllFcmRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
batch.updateData([K.FStore.Users.fcmTokens: FieldValue.delete()], forDocument: deleteAllFcmRef)
let updateFcmTokenRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
batch.updateData([K.FStore.Users.fcmTokens: FieldValue.arrayUnion([result.token])], forDocument: updateFcmTokenRef)
batch.commit { (error) in
if let error = error {
print(error)
} else {
passOnMethod()
}
}
}
}
}
Not tested yet, as our backend programmer, who is in charge of setting up Firestore rules was gone for the day, but in theory this should work: (and it's something I'll test tomorrow)
Having a FirebaseAuth.AuthStateListener in charge of serving UI based on the status of the user
This combined with rules in firestore
match /collection
allow read: if isAuth();
Where isAuth is:
function isAuth() {
return request.auth.uid != null;
}
If the user is then disabled, while being logged in, whenever the user tries to read data from the collection, he should be denied, and a signOut() call should be made.
The AuthStateListener will then detect it, and sign the user out.

Resources