How to create "credential" object needed by Firebase web user.reauthenticateWithCredential() method? - javascript

The (unclear) example in the new docs:
var user = firebase.auth().currentUser;
var credential;
// Prompt the user to re-provide their sign-in credentials
user.reauthenticateWithCredential(credential).then(function() {
How should I create this credential object?
I tried:
reauthenticateWithCredential(email, password) (like the login method)
reauthenticateWithCredential({ email, password }) (the docs mention one argument only)
No luck :(
PS: I don't count the hours wasted searching for relevant info in the new docs... I miss so much the fabulous firebase.com docs, but wanted to switch to v3 or superior for firebase.storage...

I managed to make it work, docs should be updated to include this for who does not want to spend too much time in the exhaustive-but-hard-to-read API reference.
Firebase 8.x
The credential object is created like so:
const user = firebase.auth().currentUser;
const credential = firebase.auth.EmailAuthProvider.credential(
user.email,
userProvidedPassword
);
// Now you can use that to reauthenticate
user.reauthenticateWithCredential(credential);
Firebase 9.x
(Thanks #Dako Junior for his answer that I'm adding here for exhaustivity)
import {
EmailAuthProvider,
getAuth,
reauthenticateWithCredential,
} from 'firebase/auth'
const auth = getAuth()
const credential = EmailAuthProvider.credential(
auth.currentUser.email,
userProvidedPassword
)
const result = await reauthenticateWithCredential(
auth.currentUser,
credential
)
// User successfully reauthenticated. New ID tokens should be valid.
Note
Some people asked about userProvidedPassword, if it was some sort of stored variable from the first login. It is not, you should open a new dialog/page with a password input, and the user will enter their password again.
I insist that you must not try to workaround it by storing user password in cleartext. This is a normal feature for an app. In GMail for example, sometimes your session expires, or there is a suspicion of hack, you change location, etc. GMail asks for your password again. This is reauthentication.
It won't happen often but an app using Firebase should support it or the user will be stuck at some point.

Complete answer - you can use the following:
var user = firebase.auth().currentUser;
var credentials = firebase.auth.EmailAuthProvider.credential(
user.email,
'yourpassword'
);
user.reauthenticateWithCredential(credentials);
Please note that reauthenticateWithCredential is the updated version of reauthenticate()

There are multiple methods to re-authenticat. See the refs: https://firebase.google.com/docs/reference/js/firebase.User
firebase
.auth()
.currentUser.reauthenticateWithPopup(new firebase.auth.GoogleAuthProvider())
.then((UserCredential) => {
console.log("re-outh", UserCredential);
});
In case your app allows multiple authentication methods you might want to first find out what privider was used. You can do this by looking at the firebase.auth().currentUser.providerData array.

With the new firebase version 9.*
import {
EmailAuthProvider,
getAuth,
reauthenticateWithCredential,
} from "firebase/auth";
const auth = getAuth();
let credential = EmailAuthProvider.credential(
auth.currentUser.email,
password
);
reauthenticateWithCredential(auth.currentUser, credential)
.then(result => {
// User successfully reauthenticated. New ID tokens should be valid.
})

I agree that the documentation is not pretty clear on this. But looking a little deeper on the API reference I found firebase.auth.AuthCredential and this and I guess you should be looking to pass it to reauthenticate().
I'm guessing here but I would start trying to log the firebase.auth() to see if there is any credential object there.
I suppose it will look something like the following:
user.reauthenticate(firebase.auth().credential).then(function() {

Now there's a small change in the method since both posted answers are deprecated,
val user = auth.currentUser
user?.let { _user ->
val credentials = EmailAuthProvider.getCredential(
_user.email!!,
"userPassword"
)
_user.reauthenticate(credentials).addOnCompleteListener { _reauthenticateTask ->
}

final FirebaseUser fireBaseUser = FirebaseAuth.getInstance().getCurrentUser();
AuthCredential credential = EmailAuthProvider.getCredential(fireBaseUser.getEmail(), storedPassword);
fireBaseUser.reauthenticate(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> reAuthenticateTask) {
if (!reAuthenticateTask.isSuccessful())
...
}
});

Related

How do I convert a user authenticated with an EmailLink to a user authenticated with Email and Password?

I am using the Capacitor-Firebase/authentication plugin in my application.
My goal
I am trying to turn an Anonymous user into a user with either credentials (email/password).
Also, I want to have his e-mail verified.
My approach
My approach was to use the EmailLink functionality and then trying to convert it to a user with email and password like the following:
I have successfully signed in a user using the following code from the docs:
const signInWithEmailLink = async () => {
const credential = EmailAuthProvider.credentialWithLink(email, emailLink);
const auth = getAuth();
const result = await signInWithCredential(auth, credential);
return result.user;
};
Later, I want to convert this to a user with email and password.
I am trying to do this with FirebaseAuthentication.updatePassword({ newPassword: password.value }).
My Problem
This does not work because IMO after using signInWithCredential the state of the user is different in the native layer than in the web layer. In the native layer the user is still an Anonymous user so FirebaseAuthentication.updatePassword() won't work.
I also tried the following to try to associate a username/password:
const credential = EmailAuthProvider.credential(email.value, password.value)
const auth = getAuth()
// https://firebase.google.com/docs/auth/web/account-linking
linkWithCredential(auth.currentUser, credential)
This fails with the following error: auth/provider-already-linked.
I finally found the solution myself:
An EmailLink account is just a flavor account of an Email/Password-Account which does not have a password.
So, if I am in Capacitor, I have to do the following to add the password:
const auth = getAuth()
updatePassword(auth.currentUser!, password)
On the web, the following command works:
FirebaseAuthentication.updatePassword({ newPassword: password })

A user doesn't have any persmission when its account has just been created in Firebase (web version), why?

I'm trying to use Firebase in the web project (with NextJS), but here is my problem: the user doesn't have an account, so naturally the account is created with createUserWithEmailAndPassword(). However, I want to store data in Firestore right after the creation of the account... and there is the problem. Indeed Firebase throws the following error:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Here is an extract of my code:
import { useState } from "react";
import {
createUserWithEmailAndPassword,
getAuth,
} from "firebase/auth";
import {
collection,
getFirestore,
addDoc,
} from "firebase/firestore";
export const ConnectionPage = () => {
// the values of the inputs in the HTML content
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// the function executed when the user clicks the register button
const submit = async (e) => {
e.preventDefault();
// create a new user with email and password
// and add the document in the "users" collection
try {
const userCredentials = await createUserWithEmailAndPassword(
getAuth(),
email,
password
);
const user = userCredentials.user;
const userInfo = {
uid: user.uid,
// ...and all kind of data
};
const collectionRef = collection(getFirestore(), "users");
// my problem starts here
// the following line will throw an error
// "[code=permission-denied]: Missing or insufficient permissions."
// whereas, according to the rules in Firestore, it should work
// because I just ask the user to be authenticated (`allow write, read: if request.auth != null`)
// and according to the documentation, `createUserWithEmailAndPassword` logs in the user.
const docRef = await addDoc(collectionRef, userInfo);
console.log(`New document with id '${docRef.id}' created successfully.`);
} catch (e) {
console.error("An error has occured during register, look:");
console.error(e.toString());
}
};
// returns a form
// with an email input
// and a password input
// and a button to register
};
Here are my rules in Firestore:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write, read: if request.auth != null;
}
}
}
Please help me, the only solution I can find on a similar problem is to sign out the user after the creation of the account, and sign in right after that.
I tried:
in-promise syntax (.then()) but the problem is the same than with async/await
onAuthStateChanged(), but the same problem occurs.
Changing the rules to something more complex (like testing the existence of the user according to the request.auth.uid) doesn't work either.
When you just have:
allow read;
This is an entire statement, and means that nobody can read the data.
If you want to apply the same condition for both read and write, use:
allow read, write: if request.auth != null;
Try using an auth state observer instead of just await on the creation of the account. In my experience, the current user object is not immediately set after account creation, and you have to wait until the auth state observer triggers with a non-null user object in order to be able to perform authenticated queries. Without a non-null currentUser, Firestore queries that require auth will fail.
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
If this feels like a bug to you, then I suggest filing it on GitHub.
the problem is fixed thanks to the contributors I talked to in GitHub. The problem was coming from the App Check feature which does not work with Firestore until the version 9.6.0.
The solution is:
npm install firebase#9.6.0
By default, npm install firebase is installing version 9.5.0.
Note that this version is recent and if you see that message in 2031 it's completely useless.
Thank to the people who answered me :)

Can I send DescribeUserPoolClient commands for a user pool in another AWS account

looking for some AWS Javascript SDK help.
I am in the situation that my User Pool is defined in a separate account to my Lambda which needs to send a DescribeUserPoolClient command.
Code snippet below:
import {
CognitoIdentityProviderClient,
DescribeUserPoolClientCommand,
} from '#aws-sdk/client-cognito-identity-provider';
export async function describeUserPoolClient(userPoolClientId: string, userPoolId: string) {
const cognitoClient = new CognitoIdentityProviderClient({});
const describeUserPoolClientCommand = new DescribeUserPoolClientCommand({
ClientId: userPoolClientId,
UserPoolId: userPoolId,
});
const userPoolClient = await cognitoClient.send(describeUserPoolClientCommand);
return userPoolClient;
}
Since I can only provide a userPoolId and not a full ARN, I can't see a way to send this request cross-account without assuming a role in the other account which makes my local testing a nightmare with getting roles and policies set up.
Can anyone see another way of doing this? Thanks for your help.

Check if User is Anonymous in Firestore Cloud Function

When a user is authenticated I trigger a cloud Firestore Cloud Function below;
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase)
exports.createUserAccount = functions.auth.user().onCreate((user) => {
const uid = user.uid
const firstName = user.firstName
const familyName = user.familyName
const displayName = user.displayName
const email = user.email
const photoURL = user.photoURL
const newUser = admin.firestore().doc(`/users/${uid}`)
return newUser.set({
firstName: firstName,
familyName: familyName,
displayName: displayName,
email: email,
photoURL: photoURL,
})
})
I run into an issue when a user is logged in anonymously. I have tried adding a const isAnonymous = user.isAnonymous and then returning it under;
return newUser.set({
isAnonymous: isAnonymous,
ERROR LOG IN CONSOLE
Error: Value for argument "data" is not a valid Firestore document. Cannot use "undefined" as a Firestore value (found in field isAnonymous).
My question is how do I check if the user is anonymously logged in or not through a Firebase Cloud Function?
The UserRecord object delivered to your auth function will never have an isAnonymous property on it. That's why it's always taking the value undefined. Check the link to the API documentation to see what's there. I'm not sure how you came to the conclusion that it should be there.
Each authentication provider that has verified an account is present in the providerData array property of the UserRecord object. You should look through that array to check if it's an anonymous account. Specifically, you should be checking the providerId field of each UserInfo object in the providerData array. I'm not 100% certain if this is the correct approach, but from what I'm observing, an anonymous account will have an empty providerData array, since it's not been verified by any auth providers.
As long as you are using email-based authentication (email+pass & OAuth) then you can simply check
if (user.email !== null){
// Run this code if the user is not anonymous
}
Docs
As far as I know there is no direct API for checking if a user is anonymous using the firebase admin sdk. By checking that a user has been active and don't have any connected login providers I believe it should work however.
const user = await admin.auth().getUser('Bp7DmEl5HKcDKrQr7QFVnQXS8FH1')
const hasBeenActive = user.metadata.lastSignInTime != null
const hasProviders = user.providerData.length > 0
const isAnonymous = hasBeenActive && !hasProviders
Note that the reason for the active check is to filter out users who are created manually using either the firebase console or with the admin sdk. Depending on your use case you might want to consider them anonymous as well.

Interacting with the current user object in firebase requires me the type user.user, why must I type user twice?

I am trying to add a registration page for users to register on a firebase database in a web page(it's wordpress).
For example when I create the user and then try a simple console log to check the created user's uid like so:
auth.createUserWithEmailAndPassword(email, password)
.then(function(user){
console.log(user.uid);
});
I get an "undefined". But when I attempt to do the same thing by typing
auth.createUserWithEmailAndPassword(email, password)
.then(function(user){
console.log(user.user.uid);
});
It works correctly.
Why is this?
I followed the documentation for initialising firebase and then set the reference to the auth as
var auth = firebase.auth();
Thanks for any help. I am new to both Javascript and Firebase.
From the documentation
createUserWithEmailAndPassword(email, password) returns firebase.Promise containing non-null firebase.auth.UserCredential
Clicking firebase.auth.UserCredential shows you it has the structure
{
user: nullable firebase.User,
credential: nullable firebase.auth.AuthCredential,
operationType: (nullable string or undefined),
additionalUserInfo: (nullable firebase.auth.AdditionalUserInfo or undefined)
}
So you are accessing the user property of the returned UserCredential object which you have named user.
If you wanted to, you could change user to userCredential to help clarify what it is and avoid future confusion.
auth.createUserWithEmailAndPassword(email, password)
.then(function(userCredential){
console.log(userCredential.user.uid);
});

Categories