Firebase Anonymous User once upgraded - javascript

I'm implementing a workflow where every user using my app is an anonymous user until they sign-in/up (either email or Google).
To sign up, it's straightforward: I use linkWithPopup.
However, I had some issues with user signing in: I try to link them and, if I get the auth/credential-already-in-use error (happens if the user upgrades once, signs out and then try to sign in again), I sign them in.
firebase.auth().currentUser.linkWithPopup(provider).then(function (result) {
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/credential-already-in-use') {
return firebase.auth().signInWithCredential(error.credential);
}
}).then((result) => {
return dispatch(loadContent(result.user.uid))
}).then(() => {
history.push('/');
});
The code above is working great and that's less hassle than doing it all by myself.
However, how do I remove the anonymous users which are created and orphan in case the user signs in?
I tried to make a reference to the old useless anonymous user and to delete it once the user is signed in (and so, changed its account) but it is obviously not working because the account changed and that would be a big security flaw if a user could delete another one...
I'm not very familiar with Firebase ecosystem, how should I handle that? Do I need to use a combination of Firebase Cloud Function and Firebase Admin SDK or is there a standard way of solving this problem?

It is quite simple. APIs on the user continue to work even if the user is not current.
const anonymousUser = firebase.auth().currentUser;
firebase.auth().currentUser.linkWithPopup(provider)
.then(function (result) {
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/credential-already-in-use') {
return firebase.auth().signInWithCredential(error.credential);
}
}).then((result) => {
if (anonymousUser.uid !== result.user.uid) {
return anonymousUser.delete()
.then(() => {
return result;
});
}
return result;
}).then((result) => {
return dispatch(loadContent(result.user.uid))
}).then(() => {
history.push('/');
});

Related

AWS cognito signout from google account not working properly

I'm facing a problem with aws cognito login. When I'm logging in with google, it is fine. but when I'm logging out and again clicking on "sign in with google" button it is taking me to the previous account that I logged in before logging out without asking me to select an account. But when I clear the cookies clicking on the "View site information" button on the address bar(Cannot find anything in Application=>cookies storage, that's why I had to use this method) and log out, and again try to login with google then it is asking me to select a google account to log in. But without clearing cookies from view site information it is taking me to previous account without giving me list of account to log in.
See this image that may help you to understand better:
Here is my logOut function:
const logout = () => {
window.location.reload();
const user = Pool.getCurrentUser();
if (user) {
user.signOut();
}
const accessToken = JSON.parse(localStorage.getItem("accessToken"));
const refreshToken = JSON.parse(localStorage.getItem("refreshToken"));
if (accessToken) {
localStorage.removeItem("accessToken");
}
if (refreshToken) {
localStorage.removeItem("refreshToken");
}
const authUser = JSON.parse(localStorage.getItem("authUser"));
if (authUser) {
localStorage.removeItem("authUser");
}
navigate(RoutingPaths.Login);
};
authenticate function:
const authenticate = (Username, Password) => {
return new Promise((resolve, reject) => {
const user = new CognitoUser({
Username,
Pool: Pool,
});
const authDetails = new AuthenticationDetails({
Username,
Password,
});
user.authenticateUser(authDetails, {
onSuccess: (data) => {
resolve(data);
},
onFailure: (err) => {
reject(err);
},
newPasswordRequired: (data) => {
resolve(data);
},
});
});
};
I don't think it's anything wrong that you do with the logout on your part. Most probably it's Amazon Cognito remembering the preferred user and trying to log in with that user. If your Google session for that user was expired, I'm pretty sure that you would have seen that "choose account" screen again.
When you clear cookies through that "i" icon in the browser, you also clear Cognito's cookies. That's why it forgets the preferred Google user and asks to choose the account again. You don't see those cookies in the Application -> cookie storage, because the browser only shows localhost cookies there.

Firebase .getIdToken() returns invalid token

I'm trying to make just a simple authentication app with electron and firebase redirect, but if the user is already logged in and I use the firebase.auth().currentUser.getIdToken() to get the IdToken of that user, but when i try that token in
firebase.auth().signInWithCredential(credential) I get the error that says ERROR: auth/invalid-credential
Here is my code front-end
firebase.auth().onAuthStateChanged( async function (user) {
if (user) {
// User is signed in.
var user = await firebase.auth().currentUser;
if (user != null) {
await firebase.auth().currentUser.getIdToken().then(function(idToken) {
window.location.href = "electron://"+idToken;
}).catch(function(error) {
console.log(error)
});
}
} else {
// No user is signed in.
document.getElementById("user_div").style.display = "none";
document.getElementById("login_div").style.display = "block";
}
});
Here is my code back-end
app.on('second-instance', (event, commandLine, workingDirectory) => {
if (commandLine[2]) {
var url = commandLine[2].split('/')
var id_token = url[2]
console.log('id: ', id_token)
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
// Sign in with credential from the Google user.
firebase.auth().signInWithCredential(credential)
.then((success)=>{
myWindow.loadFile('./scr/welcome.html')
console.log('RESULT: ',success)
})
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log('ERROR:', errorMessage)
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
console.log('ERROR:', credential)
// ...
})
}
I'm missing something or doing something wrong?
That's not how ID tokens work. The purpose of an ID token is to pass to your backend, so that it can validate the identity of the signed in user, and perform some action on their behalf. It's not valid for signing in on the client again. You might want to review the documentation on use of ID tokens to learn how this works.
signInWithCredential only works with Google Auth when you correctly construct a GoogleAuthProvider credential. There is plenty of sample code in the API documentation for that.

Firebase web sign out doesn't send any XHR

When setting up a simple form - 2 buttons with 2 functions to test firebase's auth, i have a problem with sign out.
The sign in function works perfect.
However, sign out doesn't send any XHR. Nothing, nada. No errors.
But the promise resolves.
The console logs 'Signed Out'.
What am i doing wrong?
Thanks!
function signIn() {
firebase.auth().signInWithEmailAndPassword('email#email.com', 'password')
.then(() => {
console.log('Logged in')
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorMessage)
});
}
function signOut() {
firebase.auth().signOut().then(() => {
console.log('Signed out!')
}).catch((error) => { console.log(error) });
}
Signing out just involves "forgetting" the refresh token that's used to keep the user signed in persistently over time. The server doesn't need to be notified that this happened.

Why is Firebase's "getRedirectResults()" returning "{user: null}"?

Currently, when the user goes through the Social auth (via redirects), it successfully creates a user under Firebase Authentication, but fails to retrieve the Google/Facebook API's data. Perplexed as to how it's failing to retrieve the data when the Auth is successful.
I used to only have the SignInWithPopup (which works perfectly fine), but am trying to get getRedirectResults to work too (for mobile).
Given the behavior I've been seeing, the problem is most likely with the getRedirectResults. Most likely not the social API setup, because otherwise the user would never get created in Authentication.
The following code resides in componentDidMount (this is a React.js app):
if (this.state.device === 'mobile') {
firebase.auth().getRedirectResult().then(function(result) {
console.log('login successful! Data is:');
console.log(result);
if (result.credential) {
var provider = result.credential.providerId;
self.handleSocialAuth(provider, result);
}
}).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
});
}
results is supposed to contain the user's data, keeps returning:
login successful! Data is:
{user: null}
I have not deviated from official docs examples, which is why I'm confused.
firebase.auth().getRedirectResult() - is called after the page loads.
Official doc link: https://firebase.google.com/docs/auth/web/google-signin
Use the below method to retrieve the user that is logged in.
firebase.auth().onAuthStateChanged(function(user) {
console.log(user);
});
If no user is logged in the user will be null.
Live example:
checkAuthStatus(){
firebase.auth().onAuthStateChanged(user => {
this.setState({ btnWithImg: user });
if(user !== null){
this.setState({ userIsLoggedIn: true });
this.props.toggleLogIn();
}
});
}
You should call firebase.auth().getRedirectResult() only when user has been authenticated. Example
firebase.auth().onAuthStateChanged(user => {
if (user) {
const result = await firebase.auth().getRedirectResult();
assert(result.user, 'user is empty')
}
});

Only authenticate with Google Auth Provider if user exists

I am using Firebase to authenticate users in our app using GoogleAuthProvider. But I don't want a new user to sign in if they are not already an authenticated user.
If the user exists then sign them in and console.log('user ' + user.email + ' does exist!');.
However, if the user does not exist. Then do not allow authentication and console.log('user ' + user.email + ' does not exist!')
var googleProvider = new firebase.auth.GoogleAuthProvider();
export const doSignInWithGoogle = () => auth.signInWithPopup(googleProvider);
googleLogin = () => {
auth
.doSignInWithGoogle()
.then(result => {
var user = result.user;
const userRef = db.collection('users').doc(user.uid);
userRef.get().then(docSnapshot => {
if (docSnapshot.exists) {
userRef.onSnapshot(() => {
console.log('user ' + user.email + ' does exist!');
});
} else {
console.log('user ' + user.email + ' does not exist!');
}
});
})
.catch(error => {
this.setState(updateByPropertyName('error', error));
});
};
I thought referencing the user records in Firestore would be a simple approach to this. However, perhaps Firebase Auth already have a way to do this. I cannot find documentation or any example.
In the above code, nothing gets logged and the user is either created or logged in.
How can I stop new users from signing up, whilst still allowing current users to sign in?
If you really want to use signInWithPopup method, you have this option,
but it's not the best way. when you are signing in with google, signInWithPopup method returns a promise. you can access the isNewUser property in additionalUserInfo from resulting object. then delete the user you just created.
firebase.auth().signInWithPopup(provider).then(
function (result) {
var token = result.credential.accessToken;
var user = result.user;
//this is what you need
var isNewUser = result.additionalUserInfo.isNewUser;
if (isNewUser) {
//delete the created user
result.user.delete();
} else {
// your sign in flow
console.log('user ' + user.email + ' does exist!');
}
}).catch(function (error) {
// Handle Errors here.
});
This is the easy way but deleting after creating is not the best practice. There is another option,
you can use, signInAndRetrieveDataWithCredential method for this. according to the docs,
auth/user-not-found will be
Thrown if signing in with a credential from
firebase.auth.EmailAuthProvider.credential and there is no user
corresponding to the given email.
function googleSignInWithCredentials(id_token) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
// Sign in with credential from the Google user.
firebase.auth().signInAndRetrieveDataWithCredential(credential)
.then(function (userCredential) {
//sign in
console.log(userCredential.additionalUserInfo.username);
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/user-not-found') {
//handle this
} else {
console.error(error);
}
});
}
here is an example from firebase github repo.
with Firebase security rules, can only check if keys exist - therefore searching in the users table is not an option:
"emails": {
"example1#gmail.com": true,
"example2#gmail.com": true
}
and then one can check with security rules, if the auth.token.email exists as a key:
{
"rules": {
".read": "root.child('emails').child(auth.token.email).exists(),
".write": false,
}
}
in the client, this should throw an "The read failed: Permission denied error" error then, to be handled accordingly. hooking into the Firebase sign-up isn't possible - but while they cannot log-in, this has the same effort (except that on has to clean up the user-database from time to time); eg. with a Cloud Function, which deletes users, which do not have their email as key in the emails "table".
in Firestore security rules, one can check with:
request.auth.token.email & request.auth.token.email_verified
for example, with a collection called emails and a collection called content:
match /databases/{database}/documents {
function userMatchesId(userId) {
return request.auth != null && request.auth.uid == userId
}
function readAllowed(email) {
return if get(/databases/$(database)/documents/emails/$(request.auth.token.email)).data != null
}
match /users/{userId} {
allow get: if userMatchesId(userId)
}
match /content {
allow get: if readAllowed(request.auth.token.email)
}
}
The object you receive from firebase after login has additionalUserInfo where you have the property isNewUser.
You can find the reference here: https://firebase.google.com/docs/reference/js/firebase.auth.html#.AdditionalUserInfo

Categories