Handling linking accounts in Firebase - javascript

I am following Firebase's instruction on social login. Below is an example of what I am using and it is all working fine from a login authentication perspective.
I have, however, both Google and Facebook login working independently.
What I would like now to be able to do is link the accounts. You can see below in fact where this might go (see the comment):
If you are using multiple auth providers on your app you should handle linking the user's accounts here.
I have tried many variations of what I think should go here, but to no avail. Can anyone guide me in relation to what they think should go here? Thanks!
function initFBApp() {
// Result from Redirect auth flow.
// [START getidptoken]
firebase.auth().getRedirectResult().then(function (result) {
if (result.credential) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// [START_EXCLUDE]
document.getElementById('FBquickstart-oauthtoken').textContent = token;
}
else {
document.getElementById('FBquickstart-oauthtoken').textContent = 'null';
// [END_EXCLUDE]
}
// The signed-in user info.
var user = result.user;
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// [START_EXCLUDE]
if (errorCode === 'auth/account-exists-with-different-credential') {
alert('You have already signed up with a different auth provider for that emails.');
// If you are using multiple auth providers on your app you should handle linking
// the user's accounts here.
}
else {
console.error(error);
}
// [END_EXCLUDE]
});
// [END getidptoken]
// Listening for auth state changes.
// [START authstatelistener]
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
var email = user.email;
var emailVerified = user.emailVerified;
var photoURL = user.photoURL;
var isAnonymous = user.isAnonymous;
var uid = user.uid;
var providerData = user.providerData;
// [START_EXCLUDE]
document.getElementById('FBquickstart-sign-in-status').textContent = 'Signed in';
document.getElementById('FBquickstart-sign-in').textContent = 'Log out';
document.getElementById('FBquickstart-account-details').textContent = JSON.stringify(user, null, ' ');
// [END_EXCLUDE]
}
else {
// User is signed out.
// [START_EXCLUDE]
document.getElementById('FBquickstart-sign-in-status').textContent = 'Signed out';
document.getElementById('FBquickstart-sign-in').textContent = 'Log in with Facebook';
document.getElementById('FBquickstart-account-details').textContent = 'null';
document.getElementById('FBquickstart-oauthtoken').textContent = 'null';
// [END_EXCLUDE]
}
// [START_EXCLUDE]
document.getElementById('FBquickstart-sign-in').disabled = false;
// [END_EXCLUDE]
});
// [END authstatelistener]
document.getElementById('FBquickstart-sign-in').addEventListener('click', toggleFBSignIn, false);
}

These are roughly the steps on how to handle auth/account-exists-with-different-credential:
You will get that error if you are signing in to a new Facebook account that uses the email of another account that already exists. Let's say the existing account is a google account.
You will get that error in getRedirectResult().catch(function(error) {})
The error will also contain an email and credential field.
You will need to save the credential (using the recommended sessionStorage). Check this post for more on that:
Firebase Authentication Javascript: setCookie for pending Credential for redirect
You then call firebase.auth().fetchProvidersForEmail(error.email) to determine the providers that already exist for that email.
You will then sign in to one of those existing providers and assert that the email is the same as error.email. On success, you will load the pending credential from sessionStorage, re-initialize as described in the other post and link it to the currentUser:
firebase.auth().currentUser.linkWithCredential(savedCred);
You will now have both accounts linked. Keep in mind the existing provider could be a password type. In that case you don't need to save the credential, you just ask the user for the password and sign them in using the same email error.email. You can then call link directly with the error.credential.
BTW, I recommend firebaseui-web which takes care of all this for you:
https://github.com/firebase/firebaseui-web

I think the Firebase API changed a bit and firebase.auth().currentUser.link(savedCred);
is now firebase.auth().currentUser.linkWithRedirect(provider). In my implementation I'm saving the initially selected provider to sessionStorage and use that with the above method in case account linking is required.
You can also do linkWithPopUp if that suits your needs better.

read example carefully https://firebase.google.com/docs/auth/web/google-signin
section "Handling account-exists-with-different-credential Errors"
Redirect mode This error is handled in a similar way in the redirect
mode, with the difference that the pending credential has to be cached
between page redirects (for example, using session storage).

Below is the relevant snippet of working code (this sits inside an async function). Note that "apples" is just a simplified test record in Firestore representing a shopping cart.
if(error.code === "auth/email-already-in-use"){
// REMEMBER AUTH CURRENT USER OBJECT
previousUser = firebase.auth().currentUser;
// WE MUST HANDLE DB READ AND DELETE WHILE SIGNED IN AS PREVIOUS USER PER FIRESTORE SECURITY RULES
if(localUserDoc){ //this was saved from .snapshot firing
if(localUserDoc.data().apples){
apples = localUserDoc.data().apples;
}
}
//DELETE CURRENT USER RECORD WHILE STILL SIGNED IN
await firebase.firestore().collection("users").doc(previousUser.uid).delete();
// CLEAN UP DONE. NOW SIGN IN USING EMAIL LINK CREDENTIAL
try {
var firebaseUserObj = await firebase.auth().signInAndRetrieveDataWithCredential(credential);
// FIRESTORE USER RECORD FOR EMAIL LINK USER WAS CREATED WHEN THEY ADDED APPLE TO CART
try {
var doc = await firebase.firestore().collection("users").doc(firebaseUserObj.user.uid).get();
if (doc.exists) {
if(doc.data().apples){
apples = apples + doc.data().apples;
}
}
await firebase.firestore().collection("users").doc(firebaseUserObj.user.uid).update({
apples: apples
});
} catch(error) {
console.log("Error getting document:", error);
}
previousUser.delete();
} catch (error) {
console.log(".signInWithCredential err ", error);
}
}

Related

I am not able to access UID in firebase on other HTML pages than login page

firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
function signUp(){
var email = document.getElementById("emailsu");
var password = document.getElementById("passwordsu");
const promise = auth.createUserWithEmailAndPassword(email.value, password.value);
promise.catch(e => alert(e.message));
alert("Signed Up");
}
function signIn(){
var email = document.getElementById("email");
var password = document.getElementById("password");
const promise = auth.signInWithEmailAndPassword(email.value, password.value);
promise.catch(e => alert(e.message));
}
auth.onAuthStateChanged(function(user){
if(user){
window.location.assign("Landing Page");
}else{
//no user is signed in
}
});
function signOut(){
auth.signOut();
alert("Signed Out");
window.location.replace("index.html");
}
This is my authentication JavaScript And when I tried to link this file using script tag to access UID pages other than login page... It starts to reload recursively... I want to Access UIDs and save them in database as DOCUMENT name so I can fetch the data using UID of the user
the folder contains 6 Different HTML pages but it only authenticates the login page
You just need to do it after the promise of auth.createUserWithEmailAndPassword() has been fulfilled, which will return you a UserCredentials object which contains the uid you want, and then you can create a document in Firestore with that, like this:
const promise = auth.createUserWithEmailAndPassword(email.value, password.value)
.then(function(userCredential) {
firestore.collection('users').doc(userCredential.user.uid).set({
email: email.value
})
})
With that approach the uid of that document will be the same as the uid of the auth credentials, however, because you will be using the same value you will have to set the document with some other value since no document can be created empty. Since email was available and it is a user information that can be added to the document I used that variable and you can add more information later in your app's execution.
Also make sure to initialize the Firestore as well with:
const firestore = firebase.firestore();

Firebase anonymous auth is triggering both sides of a JS if statement

I have followed the Google reference documents but find that the firebase auth triggers both sides of the if statement.
calcbtn.addEventListener('click', e => {
//Auth
firebase.auth().signInAnonymously().catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(`${errorCode}: ${errorMessage}`);
// ...
});
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var isAnonymous = user.isAnonymous;
uid = user.uid;
console.log(`UserID: ${uid}`);
// ...
} else {
// User is signed out.
console.log('Error: User is not authenticated');
// ...
}
// ...
});
Returns both the userID and the Error: User is not authenticated?
Initially when you attach the onAuthStateChanged, no user will be signed in yet. So at that point your callback will be called with null.
Then the user sign in triggered by signInAnonymously() completes and another call is made to your callback with that user object.
This is normal operation for an auth state listener in Firebase: it will usually initially be called with null, and then with the actual user object.

Firebase logout user all sessions

I am using Firebase authentication in my iOS app. Is there any way in Firebase when user login my app with Firebase then logout that user all other devices(sessions)? Can I do that with Firebase admin SDK?
When i had this issue i resolved it with cloud functions
Please visit this link for more details https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens
Do the following;
Set up web server with firebase cloud functions (if none exists)
use the admin sdk(thats the only way this method would work) - [Visit this link] (
(https://firebase.google.com/docs/admin/setup#initialize_the_sdk).
Create an api that receives the uid and revokes current sessions as specified in the first link above
admin.auth().revokeRefreshTokens(uid)
.then(() => {
return admin.auth().getUser(uid);
})
.then((userRecord) => {
return new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
})
.then((timestamp) => {
//return valid response to ios app to continue the user's login process
});
Voila users logged out. I hope this gives insight into resolving the issue
Firebase doesn't provide such feature. You need to manage it yourself.
Here is the Firebase Doc and they haven't mentioned anything related to single user sign in.
Here is what you can do for this-
Take one token in User node (Where you save user's other data) in Firebase database and regenerate it every time you logged in into application, Match this token with already logged in user's token (Which is saved locally) in appDidBecomeActive and appDidFinishLaunching or possibly each time you perform any operation with Firebase or may be in some fixed time interval. If tokens are different logged out the user manually and take user to authenticate screen.
What i have done is:
Created collection in firestore called "activeSessions".User email as an id for object and "activeID" field for holding most recent session id.
in sign in page code:
Generating id for a user session every time user is logging in.
Add this id to localstorage(should be cleaned everytime before adding).
Replace "activeID" by generated id in collection "activeSessions" with current user email.
function addToActiveSession() {
var sesID = gen();
var db = firebase.firestore();
localStorage.setItem('userID', sesID);
db.collection("activeSessions").doc(firebase.auth().currentUser.email).set({
activeID: sesID
}).catch(function (error) {
console.error("Error writing document: ", error);
});
}
function gen() {
var buf = new Uint8Array(1);
window.crypto.getRandomValues(buf);
return buf[0];
}
function signin(){
firebase.auth().signInWithEmailAndPassword(email, password).then(function (user) {
localStorage.clear();
addToActiveSession();
}
}), function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
alert('wrong pass');
} else {
alert(errorMessage);
}
console.log(error);
};
}
Then i am checking on each page if the id session in local storage is the same as "activeID" in firestore,if not then log out.
function checkSession(){
var db = firebase.firestore();
var docRef = db.collection("activeSessions").doc(firebase.auth().currentUser.email);
docRef.get().then(function (doc) {
alert(doc.data().activeID);
alert(localStorage.getItem('userID'));
if (doc.data().activeID != localStorage.getItem('userID')) {
alert("bie bie");
firebase.auth().signOut().then(() => {
window.location.href = "signin.html";
}).catch((error) => {
// An error happened.
});
window.location.href = "accountone.html";
} else{alert("vse ok");}
}).catch(function (error) {
console.log("Error getting document:", error);
});
}
PS: window has to be refreshed to log inactive session out.

Authenticate same user in multiple firebase projects in web

I am developing a website. I hosted my website to Project-A and need to get the FireStore data from another firebase project(Project-B). The sample code I wrote in my website to get the data from Project-B
<script type="text/javascript">
var primaryAppConfig = {
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
storageBucket: "<BUCKET>.appspot.com",
}; // Project-A configuration
var secondaryAppConfig = {
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
storageBucket: "<BUCKET>.appspot.com",
}; // Project-B configuration
var primary = firebase.initializeApp(primaryAppConfig);
var secondary = firebase.initializeApp(secondaryAppConfig, "secondary");
var db1 = primary.firestore();
var db2 = secondary.firestore();
// Some code to get the data from Project-A and I got the result.
db1.collection('sample').get().then(snap => {
size = snap.size;
console.log("No.ofUsers",size); //Got result
});
// Some code to get the data from Project-B. And here, I am getting error like Error: Missing or insufficient permissions. Lack of permissions from Project-B.
db2.collection('sample').get().then(snap => {
var size = snap.size;
console.log("TotalSample",size);
});
</script>
So that I need to authenticate the user in Project-B also. For that I wrote the below code to login into my webiste
function signInUser() {
var emailelement = document.getElementById("email");
var passwordelement = document.getElementById("password");
if (emailelement != null){
var email = emailelement.value;
}
if (passwordelement != null){
var password = passwordelement.value;
}
primary.auth().signInWithEmailAndPassword(email, password).then(function() {
console.log("Logged into secondary");
var user = primary.auth().currentUser;
console.log(user.uid); // It showing Project-A UID
secondary.auth().signInWithEmailAndPassword(email, password).then(function() {
console.log("Logged into primary");
var user = secondary.auth().currentUser;
console.log(user.uid); // It showing Project-B UID
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
alert('Wrong password.');
} else {
alert(errorMessage);
}
});
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
if (errorCode === 'auth/wrong-password') {
alert('Wrong password.');
} else {
alert(errorMessage);
}
});
}
By the above signInUser() function the user is successfully authenticated in both projects and getting the FireStore in home.html page itself after login. But I need to get the data in home.html page(so many pages in my web expect login). What I have to do in home.html page to get the current user.
// By this user logged into both projects at a time with same credentials but with different UIDs.
Now the problem is After the authentication I need to navigate to another page using
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var user = firebase.auth().currentUser;
if(user != null){
console.log(user.uid); // In auth state overall it showing the Project-B UID
window.location = 'home.html';
}
} else {
// User is signed out.
}
});
In my home.html page the it showing the current user is null
firebase.auth().onAuthStateChanged(function(user) {
var user = firebase.auth().currentUser; // it showing null
if(!user)
{
console.log("SignOut");
window.location='/';
}
else
{
console.log(user.uid);
}
});
I need to clarify one thing. By using above signInUser() function in login.html user authenticated in both projects and I getting the data from both projects in login.html page. Actually I need to get the data in home.html page after login. But in this it showing the current user is null. I configure both projects in home.html page also like above.
Please help me out. I stopped at this point. I am new to Firebase.
In Project-B the database rules are like
service cloud.firestore
{
match /databases/{database}/documents
{
match /sample/ {AuthId=**}
{
allow read, write: if request.auth.uid != null;
}
}
}
Thanks in advance.
I know I'm late to the game, but this is for anyone who stumbles upon this in the future.
The problem, in this case, is that you are mixing up 2 different needs: The need to authenticate a user, and the need to access a resource on a different system. The former is an interaction between a user whereas the latter is between 2 systems. Still, you are using the same set of credentials. An easy solution would be to create a set of credentials for system-to-system communication. That way you can instantiate the object to access to the resources on system B at any given time, without depending on the user credentials.

firebase authentication custom attribute failure

I have been putting together a website and i've been using the latest firebase script and everything.
When I request for a custom user attribute that has been created it says it's 'undefined'.
CustomAttributes:
points
ownedavatars
Code:
SignUp
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: //username,
photoURL: //icon,
points: 0,
ownedavatars: "default"
}).then(function() {
user.sendEmailVerification().then(function() {
//it would save email and password and then redirect here
}).catch(function(error) {
console.log(error.message);
});
}).catch(function(error) {
console.log(error.message);
});
Login
var listofavatars;
firebase.auth().signInWithEmailAndPassword(email, password).then(function() {
var user = firebase.auth().currentUser;
if (user != null) {
document.getElementById("user").innerHTML = user.displayName;
if (user.points == undefined) {
document.getElementById("points").innerHTML = "0p";
} else {
document.getElementById("points").innerHTML = user.points + "p";
}
listofavatars = user.ownedavatars;
if (user.photoURL == "default") {
document.getElementById("avatar").src = //would pull default;
} else {
document.getElementById("avatar").src = //would pull any other icon saved;
}
}
}).catch(function(error) {
alert(error.message + " Code:" + error.code);
});
You can't use updateProfile to save arbitrary custom user variables. This API only currently supports photoURL and displayName`. To save other user data, you have to use a separate database to do so. You can use Firebase realtime database or Firestore to do so. Here is an example how to save user specific data securely: https://firebase.google.com/docs/database/security/user-security
If you need to save user specific data for role based access control, you can use the Firebase Admin SDK to set custom user attributes:
https://firebase.google.com/docs/auth/admin/custom-claims
However, it is highly recommended that this custom user data is to be used for access control. For other data, use a dedicated database as described above.

Categories