Revert auth state to last anonymous - firebase

I have a website where authentication isn't mandatory to be able to use the website.
To be able to persist user data even when not signed in, I'm using signInAnonymously() in my AppComponent.
When the user wants to register to the website, I'm following these steps:
Ask for oauth.
Check if this oauth is already in my database.
If the oauth is not already in the db, revert to the last anonymous state.
If it's already in the db, get user data.
But here is the issue: once I called signInWithPopup(), the last auth state is lost and because it's an anonymous one, I can't sign in to it again.
Because the anonymous state contains data I don't want the user to loose, I have to be able to revert to the last anonymous state if the registration process fails.

I still don't understand your use case but anyway here goes.
You sign the user anonymously on the default App instance.
var app = firebase.initializeApp(config);
app.auth().signInAnonymously()....
You then create another Auth instance with same config:
var appTemp = firebase.initializeApp(config, 'temp');
// Set persistence to none in case you forget to clear this.
appTemp.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
// Sign in the user with popup and save the credential.
var cred = null;
appTemp.auth().signInWithPopup(provider).then(function(result) {
cred = result.credential;
// Check the user email is registered in your DB.
return isRegistered(result.user.email);
}).then(function(registered) {
if (registered) {
// User is registered. Delete and relink to original anonymous.
return appTemp.auth().currentUser.delete().then(function() {
return app.auth().currentUser.linkWithCredential(cred);
});
} else {
// User not registered, just delete. The anonymous user remains.
return appTemp.auth().currentUser.delete();
}
})...
Not sure if this is exactly what you want but it will point you in the right direction using the temp auth instance copy technique.

Related

Firebase auth overriding user providers (email + password, phone) when signing-in with Google

As we have seen in another posts related to this situation and github issues, the expected behavior is that Google IDP overrides other non trusted providers related to the same email as an example, another account with the same email + password (non-verified).
Trying to understand Firebase Authentication one account per email address and trusted providers
Firebase Overwrites Signin with Google Account
https://github.com/firebase/firebase-ios-sdk/issues/5344
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ
So, ok, according to google that's the expected behavior.
Our questions comes when we go to the documentation and there's an example of a user login in with google and getting this error auth/account-exists-with-different-credential just because there's another account created with email+password with the same email. Then, they recommend to catch the error, check the user email related login methods and ask the user to login with the other provider and then link to google.
Does this make sense ? If they say the expected behavior is that google as a trusted provider will override the others (this is what happens to us) how is possible that the case of the code example would even occur ?
https://firebase.google.com/docs/auth/web/google-signin#expandable-1
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(result) {
// Step 4a.
return result.user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that they already have an account
// but with a different provider, and let them validate the fact they want to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account they own.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
There's another example with the same structure in the flutter docs:
https://firebase.google.com/docs/auth/flutter/errors#handling_account-exists-with-different-credential_errors
Is this a contradiction in the documentation ? Again, if Firebase will always give priority to the trusted IDP (Google email) in this case, how is it possible to get this error if the other provider will be deleted (at least when having account linking activated - single account per email activated)
At least this is our case. We create an account with email & password and then try to login with google with the same email and what happens is that the email&password account is overwritten by the new google provider.
Unfortunately, you can't change it. If a user with #gmail.com email and password authentication updates their profile picture and then later logins with Google then the profile picture and any other information will be overwritten with the data from Google. The only option is to create a user record in the database that gets populated with the user data (displayName, photoURL etc) when the user is created for the first time. You then always use the data from this record instead of the default user object that is returned by the authentication.
The other advantage of creating a record is that you can attach a listener to it. That way if the user changes their details then it gets reflected everywhere.

Anonymous user in Firebase

I'm running a crowdsourcing website with Amazon Mechanical Turk. Firebase is used to track intermediate information of users.
Since AMT authentication is not related to Firebase, I can't use those to authenticate user. And to be able to write data to Firebase, the authentication must be present.
My current solution is using anonymous users. While it works, I found that every time the page is reloaded, a new anonymous user is created. This might not be scalable, as during development I have created ~6000 anonymous users.
How could I fix this problem?
It sounds like you're calling signInAnonymously() on every page load, which will indeed create a new anonymous user.
You should instead monitor authentication state and only sign in the user when they're not signed in yet:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
firebase.auth().signInAnonymously().catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
}
});

How to prove to the server that I as a client am logged in with the given uid?

Heres my problem:
I wan't to be able to create new users for my website, from my website. This is only aloud though, if I have the "isAdmin" flag set to true in the realtime db under /users/myid.
Generally I would have done this with a security rule but the problem here is that I need to use the admin SDK, since the normal "createNewUser" method signs in to the newly created user automatically. I as an admin though, want to be able to create a new user and stay logged in as myself. So what I wan't to do is use ajax post request to my server with my uid und the new userdata which is to be created. My server then checks if the given uid has the isAdmin flag and if so creates the user with the firebase admin SDK which provides such a method.
But, anyone, if they have an admins uid, could hit up that request and create a new user. (My clients definetely get uid's from other users).
So how would I go about proving to the server that I am actually logged in with that uid.
From my understanding, tokens are used to be able to write to the database, but I don't need that permission, I just need to prove that I'm actually logged in with that uid.
Is there something I'm missing? Thanks a lot guys!
Was easier then I thought. This will generate a token on the client side:
firebase.auth().currentUser.getToken(true).then(function(token) {
// send request to server with generated token
}).catch(function(error) {
// handle error
});
Which I can then verify on the server like so:
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
// user is logged in
}).catch(function(error) {
// user is not logged in, or other error occured
});
Taken from https://firebase.google.com/docs/auth/admin/verify-id-tokens

Meteor account enrollment

I'm looking for a way to customize logins to allow for an administrator to provide a user with an account and a one-time-use temporary password (via email), that enables the user to log into a site (the user can't create an account on their own).
The first time the user logs in, they are instructed to change the temporary password given by the administrator.
I have accounts-ui and accounts-password working, but the user can create a new account at will.
If it matters, I plan to use Autoform and Iron Router for this.
I search the Meteor docs for "enroll", but the information is sparse IMO. Is there a fully working example somewhere to help me get started?
To disable the usual way of creating an account, use Accounts.config:
forbidClientAccountCreation Boolean
Calls to createUser from the client will be rejected. In addition, if you are using accounts-ui, the "Create account" link will not be
available.
Then, instead of having a temporary password, I think you should create the account without password and then use Accounts.sendEnrollmentEmail to send the user an email to choose one.
To create an account without a password on the server and still let
the user pick their own password, call createUser with the email
option and then call Accounts.sendEnrollmentEmail. This will send the
user an email with a link to set their initial password.
So, something like that:
Accounts.config({forbidClientAccountCreation: true});
Meteor.methods({
adminCreateAccount: function (accountAttributes) {
if(Meteor.user() && Meteor.user().role == "admin") {
var accountId = Accounts.createUser({
'username': accountAttributes.username,
'email': accountAttributes.emailAddress
});
Accounts.sendEnrollmentEmail(accountId);
}
}
});
What you can do is
let the admin create user (Accounts.createUser)
add a marker ( eg user.profile.changedInitialPwd) which will be set when the user
changed his pwd)
use some verification logic to make sure, the user has changed his password before he is allowed to sign in
E.g.
Accounts.validateLoginAttempt(function(attempt){
if (attempt.user && !attempt.user.profile.changedInitialPwd ) {
console.log('Initial password not changed');
return false; // the login is aborted
}
return true;
});

Creating temporary anonymous users in Firebase

I'm trying auto-generate user accounts that I can save data with, and later promote those to proper username/password accounts. Any ideas on the best way to do that?
It wasn't clear to me whether or not I could switch auth providers from anonymous to password.
You can start by creating an anonymous login with Firebase, and then store that auth "uid" key to some sort of session or cookie (depending on what framework you're working with). While the client is anonymous, you can link your saved data to this anonymous "uid".
To transfer the data over to the user after they login, you'll need to use Firebase's onAuth() listener. This will notify you when the user's auth data changes. Once you have the new authenticated uid, you can link your stored data to that new uid and delete your anonymous session.
Here's the modified sample from Firebase docs:
var firebaseRef = new Firebase('https://samplechat.firebaseio-demo.com');
firebaseRef.onAuth(function(authData) {
if (authData) {
// Client is logged in, so check if it's anonymous or a real user
if (authData.provider === 'anonymous') {
// user is anonymous
// save authData.uid to some local session info, to keep track of data
} else {
// user is logged in
// transfer any data that you had stored from the anonymous session to
// this new authUser.uid
} else {
// Client is unauthenticated
// This would be a good spot to create the anonymous login
firebaseRef.authAnonymously(function(error, authData) {
if (error) {
// handle login failure
} else {
// you are now anonymously logged in, but you really don't need to do anything
// here, because your onAuth() listener is already going to catch this auth change
}
}
});
Firebase doesn't tell you what the previous uid was when the user changes their auth info, so you really need to store that anonymous uid somewhere. You could also do the whole thing without ever creating the anonymous login, by just storing session data until the user logs in, but the anonymous login provides more consistency.

Resources