result.user.link is not a function in angular firebase - javascript

Not able to link facebook account with an existing firebase account. I currently have a firebase account in that I created using google credentials. Now I want to link a facebook account with this exiting firebase account (both having same creadentials) and I followed the steps mentioned here :
https://firebase.google.com/docs/auth/web/facebook-login
But at last, when I call method "result.user.link(pendingCred).then( (user) => { .. }" to link account I get following error in console :
Uncaught TypeError: result.user.link is not a function
at auth.service.ts:188
at e.g (auth.js:23)
at Yb (auth.js:26)
at Ub (auth.js:26)
at z.webpackJsonp.../../../../#firebase/auth/dist/auth.js.h.Mb (auth.js:25)
at Cb (auth.js:19)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:392)
at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:142)
at zone.js:873
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:425)
Here is my code
loginFacebook(): Promise<any> {
return this.afAuth.auth.signInWithPopup(new firebase.auth.FacebookAuthProvider())
.then( (result) => {
this.registerUserName = result.user.displayName;
this.authDbUsername = result.user.email.replace('.', '');
// this.setLoggedinUser(user)
})
.catch ( (error) => {
if (error.code === 'auth/account-exists-with-different-credential') {
alert('You have already signed up with a different auth provider for that email.');
const pendingCred = error.credential;
// The provider account's email address.
const email = error.email;
// Get registered providers for this email.
firebase.auth().fetchProvidersForEmail(email).then( (providers) => {
// Step 3.
// If the user has several providers,
// the first provider in the list will be the "recommended" provider to use.
if (providers[0] === 'password') {
// Asks the user his password.
// In real scenario, you should handle this asynchronously.
const password = this.promptUserForPassword(providers[0]); // TODO: implement promptUserForPassword.
firebase.auth().signInWithEmailAndPassword(email, password).then( (user) => {
// Step 4a.
return user.link(pendingCred);
}).then(function( user) {
// Google account successfully linked to the existing Firebase user.
this.authState = user;
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
const provider = new firebase.auth.GoogleAuthProvider(); // this.getProviderForProviderId(providers[0]);
// At this point, you should let the user know that he already has an account
// but with a different provider, and let him validate the fact he wants 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.
firebase.auth().signInWithPopup(provider).then( (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 he owns.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
const resultingUser = result.user;
result.user.link(pendingCred).then( (user) => {
// Google account successfully linked to the existing Firebase user.
this.authState = user; // goToApp();
}).catch( (errorInLinking) => {
console.log(errorInLinking);
});
});
});
}
});
}
Please let me know if I am missing anything.
Thanks!

Change to result.user.linkWithCredential(pendingCred).then( (user) ...
link has been deprecated in favor of linkWithCredential starting from version 4.0.0.

Related

how to get the user email from the applyActionCode in Firebase?

I have my own custom server that also records users. So what I want to achieve is, after a user registers, Then I will generate a custom token by signInWithCustomToken, then send a sendEmailVerification to them for verifying.
Here is how I generate the custom token from my backend
FirebaseAuth.getInstance().createCustomToken(id, additionalClaims)
Then in my front end, it will signInWithCustomToken right after they registered. (this is to get the user for sendEmailVerification)
await axios
.post(`/authorization/sign-up`, {
username: inputData.username,
password: inputData.password,
})
.then(async (response) => {
const auth = getAuth();
signInWithCustomToken(auth, response.data.content.token)
.then(async(userCredential) => {
await updateEmail(userCredential.user, inputData.username)
const actionCodeSettings = {
url: `https://domain host/test?email=${inputData.username}`
};
await sendEmailVerification(userCredential.user, actionCodeSettings)
navigate("/"); //login page
})
})
.catch((error) => toast(error.message));
} else {
toast("Please input valid data");
}
In my Login Backend I will check my database's user, whether their email has been verified or not. So if its not verified, the login will be rejected
select * from user where user = foo and password = foo and isEmailVerified = true
In my front-end verify-email page :
console.log("verifying email")
const mode = searchParams.get('mode') // mode = verifyEmail
const oobCode = searchParams.get('oobCode')
const auth = getAuth()
applyActionCode(auth, oobCode).then(() => {
//how to update my backend based on the user's email
})
Then in my Backend I will check the decodedToken.isEmailVerified, if its false, I will force them to activate their email first.
So, after the user click the activation link, it will be redirected to my verify-email page, which then will also update the status of the user in my database. But, I notice that the applyActionCode function returns Promise<void>.
For example if I register my email through my PC/Laptop, then I confirm the verify email through my phone / table / other device, then I won't be able to get the currentUser to retrieve some data, ex. their email.
I also cannot put the email into the continueUrl parameter right? since the user might change the parameter themself.
So How can I get the user's email safely after applyActionCode?
Or do I just redirect them back to the login page, then each time after they click login, I will compare the decodedToken.isEmailVerified vs my database's isEmailVerified ?

Password is gone when logging in a firebase account that was created using Email and Password

I created an app that supports both Email/Password and Google authentication. I found that if I created an account in a first way, but logged out and in again with Google, the origin password was gone, and no way to sign in with email anymore. Is there any way to avoid so?
// Google authentication
const signInWithGoogle = useCallback(
async event => {
event.preventDefault();
const provider = new firebase.auth.GoogleAuthProvider();
try {
await firebaseApp
.auth()
.signInWithRedirect(provider)
.then(function(result) {
var user = result.user.providerId;
alert(user);
});
history.push("/transfer");
} catch(error) {
alert(error.message);
}
},
[history]
);
//Email/Password sign-in
const handleLogin = useCallback(
async event => {
event.preventDefault();
const { email, password } = event.target.elements;
try {
await firebaseApp
.auth()
.signInWithEmailAndPassword(email.value, password.value)
.then(function(result) {
var user = result.user.providerId;
alert(user);
});
history.push("/transfer");
} catch (error) {
alert(error);
}
},
[history]
);
// Email/Password sign-up
const handleSignUp = useCallback(async event => {
event.preventDefault();
const { email, password } = event.target.elements;
try {
await firebaseApp
.auth()
.createUserWithEmailAndPassword(email.value, password.value);
history.push("/usersignupcred");
} catch (error) {
alert(error);
}
}, [history]);
Here in the documentation you can see this explanation:
Note that some providers, such as Google and Microsoft, serve as both email and social identity providers. Email providers are considered authoritative for all addresses related to their hosted email domain. This means a user logging in with an email address hosted by the same provider will never raise this error (for example, signing in with Google using an #gmail.com email, or Microsoft using an #live.com or #outlook.com email).
I would recommend to use as similar approach like here from the docu:
// User tries to sign in with Facebook.
auth.signInWithPopup(new firebase.auth.FacebookAuthProvider()).catch(err => {
// User's email already exists.
if (err.code === 'auth/account-exists-with-different-credential') {
// The pending Facebook credential.
var pendingCred = err.credential;
// The provider account's email address.
var email = err.email;
// Get the sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(methods => {
// 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') {
// TODO: Ask the user for their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword();
auth.signInWithEmailAndPassword(email, password).then(result => {
return result.user.linkWithCredential(pendingCred);
}).then(() => {
// Facebook account successfully linked to the existing user.
goToApp();
});
return;
}
// All 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 with a different provider, and validate they want to sign in
// with the new provider.
// Note: Browsers usually block popups triggered asynchronously, so in
// real app, you should ask the user to click on a "Continue" button
// that will trigger signInWithPopup().
auth.signInWithPopup(provider).then(result => {
// Note: Identity Platform doesn't control the provider's sign-in
// flow, so it's possible for the user to sign in with an account
// with a different email from the first one.
// Link the Facebook credential. We have access to the pending
// credential, so we can directly call the link method.
result.user.linkWithCredential(pendingCred).then(usercred => {
// Success.
goToApp();
});
});
});
}
});
But instead of waiting for the error to be raised (none will be raised if using Google login as you also explained in your case) try always to call first fetchSignInMethodsForEmail and if the user has the email provider and tries now to use the Google one first log him in with the email provider and link him later with the Google provider.

Send verification email before logging in

This is the code that i'm practicing in to create a new user. I can receive the email verification and confirm it however, the site will still logged me in even if I have not yet confirmed my email yet.
try{
const { user } = await auth.createUserWithEmailAndPassword(email,password);
await user.sendEmailVerification();
await handleUserProfile(user, { displayName});
this.setState({
...initialSate
});
}catch(err){
console.log(err);
}
}
This is the handleUserProfile in another js file.
export const handleUserProfile = async (userAuth, additionalData) => {
if (!userAuth) return;
const {uid} = userAuth;
const userRef = firestore.doc(`users/${uid}`);
//create new user
const snapshot = await userRef.get();
if (!snapshot.exists){
const { displayName, email} = userAuth;
const timestamp = new Date();
//if the user exist does not exist
try{
await userRef.set({
displayName,
email,
createdDate: timestamp,
...additionalData
});
}catch(err){
console.log(err);
}
}
return userRef;
};
Everything is explained in the firebase documentation.
There you have the corresponding code snippets to try.
You would need to narrow down your question with some of this trials.
Even you have the chance to check if user opens the link from a differenc device from which waas signed up.
I think this is the snippet you might need:
// Confirm the link is a sign-in with email link.
if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
var email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = window.prompt('Please provide your email for confirmation');
}
// The client SDK will parse the code from the link for you.
firebase.auth().signInWithEmailLink(email, window.location.href)
.then((result) => {
// Clear email from storage.
window.localStorage.removeItem('emailForSignIn');
// You can access the new user via result.user
// Additional user info profile not available via:
// result.additionalUserInfo.profile == null
// You can check if the user is new or existing:
// result.additionalUserInfo.isNewUser
})
.catch((error) => {
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
The site will still logged me in even if I have not yet confirmed my
email yet.
Yes this is how it is implemented in Firebase: there is nothing, out of the box, that prevents a user with a non-verified email to authenticate to your app.
You should manage that yourself, by:
Checking the email is verified in the back-end security rules (Firestore, Cloud Storage, etc..). For example with a function like:
function isVerifiedEmailUser() {
return request.auth.token.email_verified == true;
}
Possibly redirect and logout the user from your app if his/her email is not verified. For example, right after signing-up, as follows:
try {
const { user } = await auth.createUserWithEmailAndPassword(email,password);
await user.sendEmailVerification();
if (user.emailVerified) {
// display the content, redirect to another page, etc...
} else {
auth.signOut(); // Maybe call that after showing an error message
}
} catch(err){
console.log(err);
}
}
plus, potentially, something similar with signInWithEmailAndPassword() and onAuthStateChanged().

Getting "Error: Invalid IdP response/credential" trying to add FB user on Firebase Auth list

I've been working on implementing Facebook & Google sign-in method in an app based on Expo.
Having Firebase connected with my app, I want the user to be listed in Firebase Users. While Google sign-in seems to work fine, Facebook gives an error as stated below from firebase.auth().signInWithCredential(credential):
[Error: Invalid IdP response/credential: http://localhost?id_token=EAAGUBzIZAgb0BABAkdrj5NcUcvljcsLeNBQlV5VUZAZC7M8e7sSRg2MkqlFCuD7tKjin4uZBep5gSs20oAo8fXKiUqq2deEbUl6HoaAUskTda7x49VCqqcbYh1W3566fMZBtRFB5S3fRV7D41AGVGPMAck91l1KiFCzQzCGtSf5g6ZBKoyHw03LOVONcOiwVZB4vXVcGPYmIzL3RuzzztdBNLRql5ndSk0ZD&providerId=facebook.com]
Here's the relevant part of the code:
Firebase.js
// imports, configs and exports omitted
export const facebookProvider = new firebase.auth.FacebookAuthProvider;
LoadingScreen.js (the sign-in part)
import firebase, { googleProvider, facebookProvider } from "../firebase";
import * as Facebook from "expo-facebook";
const LoadingScreen = ({ navigation }) => {
const signInWithFacebook = async () => {
try {
await Facebook.initializeAsync("444233602924989");
const { type, token } = await Facebook.logInWithReadPermissionsAsync(
"444233602924989",
{
permissions: ["public_profile", "email"]
}
);
if (type === "success") {
const credential = facebookProvider.credential(token);
onSignInFB(credential);
} else {
Alert.alert('FB sign-in failed')
}
} catch ({ message }) {
console.log(`Facebook 1 Error: ${message}`);
Alert.alert(`first ${message}`);
}
};
const onSignInFB = credential => {
firebase
.auth()
.signInWithCredential(credential)
.then(function() {
navigation.navigate("Home");
})
.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;
});
};
};
I've logged some of the elements as follows:
While none of the error properties were available, the error code was auth/internal-error. Console.logging the error itself produced the error Invalid IdP response/credential.
The token from logInWithReadPermissionsAsync was a valid access token (string)
The credential from facebookProvider.credential(token) was:
Object {"oauthIdToken": "EAAGUBzIZAgb0BAKgeMa5E7qHm8WNJYv5SeuQ8DHyhkUlAnkMhE7niu6tx3e2amSMHSEqG9B0MV4a9dygwgjs337PR7AA3M4PZB2F6x6n1FwAEyZBKhZBpOSE2OWQ9dJipirpafg61TKX36hnKIzaIcwRkjs8YYRBbDuLnZAhJzWst3ZBM5tafwxYKumv2F4kYdexxZAXqb1nosnwYodNvB9bstkcaBrfB8ZD",
"providerId": "facebook.com",
"signInMethod": "facebook.com",
}
firebase.auth().currentUser was null.
I've also gone through my own sanity checklist of possible mistakes below, but couldn't find any:
FB is enabled in Firebase Auth Sign-in
AppId & App Secret is correctly copied from FB app to Firebase(and also the redirect URI)
Pass in the access token from Facebook.logInWithReadPermissionsAsync to Facebook's access token debugger and see if anything is wrong (e.g. expired or invalid)
Check if an actual credential is returned from facebookProvider.credential(token) and passed to signInWithCredential
It would be great to find out the source of the problem! (one possibility might be the access token returned from logInWithReadPermissionsAsync is identical with the oauthIdToken property in the credential object, but I haven't manually put or changed any one of those.)
Thanks for reading :)
I banged my head against this today and the solution was to pass an object in to the credential call, even though the docs say to pass in a string. In addition, the object property needs to be keyed as accessToken.
So instead of:
const credential = facebookProvider.credential(token);
try:
const credential = facebookProvider.credential({ accessToken: token });
Based on the docs, I have no idea why that key is needed or why it has to be an object, but it worked in my case. Hope it helps!

How to linkWithCredential after you delete current user?

In my app, I'm trying following the documentation for how to link multiple auths in Firebase, but when I follow the example for linking to a pre-existing account linkWithCredential fails with an error of No user currently signed in.
// Get reference to the currently signed-in user
let prevUser = firebase.auth().currentUser;
// Sign in user with another account
firebase.auth().signInWithCredential(_credential).then((user) => {
let currentUser = user.user;
// Merge prevUser and currentUser data stored in Firebase.
// Note: How you handle this is specific to your application
// After data is migrated delete the duplicate user
return currentUser.delete().then(() => {
// Link the OAuth Credential to original account
console.log("!!DELETE FACEBOOK")
return prevUser.linkWithCredential(_credential).then(() => {
console.log("!!LINKING-FACEBOOK")
}).catch((error) => {
console.log("!!LINKING ERROR", error)
});
}).then(() => {
console.log("!!SIGNIN-LINKED")
// Sign in with the newly linked credential
return firebase.auth().signInWithCredential(_credential)
});
}).catch((error) => {
console.log("Sign In Error", error);
});
What I'm confused about is how can I can re-sign in to my previous account in order to be able to link the newly deleted account credentials to it?

Categories

Resources