Firebase user.sendEmailVerification() to my own address - firebase

Whenever a firebase user creates an account I would like to send the verification mail to my own mail instead of the created user their email.
This is my code:
export function onAuthStateChanged(callback) {
auth.onAuthStateChanged((user) => {
let userinfo = { uid: "", token: "", name: "", email: "", verified: "" }
if (firebaseUser) {
userinfo = {
uid: user.uid,
token: user.ma,
name: user.displayName,
email: user.email,
verified: user.emailVerified
}
if (!user.emailVerified) {
user.sendEmailVerification();
}
}
})
}
And I tried to edit this line:
user.email = "mymail#gmail.com"
user.sendEmailVerification();
But 'user' is read-only
Thanks

Firebase Authentication will only send an email to the account that is specified in the user profile. You can't configure it to send the email elsewhere.
Options I can quickly see:
Either send an additional email to your own a address when you call user.sendEmailVerification().
Or use your own verification flow entirely, not depending on Firebase Authentication's user.sendEmailVerification() method.
Firebase has no specific support for either of these actions, and you'll have to code them yourself as you'd do with any requirement to send email. If you're new to this, you might want to look at using Cloud Functions to send email.

Related

How to export Firebase Auth users, including providerUserInfo for Sign In with Apple users

I want to export all the users inside of Firebase Auth because I want to migrate away from Firebase. I can get the export of the users just fine with the command:
firebase auth:export users.json --format=json --project [my-project]
However, for all of the users that use Sign In with Apple the providerUserInfo is an empty array, so there is currently no way at all to import them into my own database and keep them as functional accounts that can actually login via SIWA again.
When I look at the auth user by adding an onAuthStateChanged listener and logging the auth user to the console, then providerData.uid is Apple's user id, the exact ID that I need to copy to my new database:
onAuthStateChanged(auth, authUser => {
if (authUser) {
const uid = authUser.providerData[0].uid;
if (authUser.providerData[0].providerId === "apple.com") {
console.log(`Apple ID: ${uid}`);
} else {
console.log(`Email address: ${uid}`);
}
}
});
So the value is definitely stored in Firebase Auth, and it's this value that I need to be able to export for all users.
So my question is: how can I fetch the providerUserInfo for such users? Would the accounts:lookup REST endpoint help? Sadly I can't really figure out how that endpoint is supposed to work, what the idToken you need to send is supposed to be.
I found a way to export all the users, including Apple's internal user id, by using the admin SDK:
const fs = require("fs");
const admin = require("firebase-admin");
admin.initializeApp();
const records = {};
function handleUser(userRecord) {
records[userRecord.uid] = userRecord;
}
const listAllUsers = nextPageToken => {
// List batch of users, 1000 at a time.
return admin
.auth()
.listUsers(1000, nextPageToken)
.then(listUsersResult => {
listUsersResult.users.forEach(userRecord => {
handleUser(userRecord);
});
if (listUsersResult.pageToken) {
// List next batch of users.
return listAllUsers(listUsersResult.pageToken);
}
})
.catch(error => {
console.log("Error listing users:", error);
});
};
// Start listing users from the beginning, 1000 at a time.
listAllUsers().then(() => {
const data = JSON.stringify(records);
fs.writeFile("users.json", data, err => {
console.log("JSON data is saved.");
});
});
ProviderUserInfo includes the following data:
{
displayName?: string,
email: string,
phoneNumber?: string,
photoURL?: string,
providerId: string,
uid: string
}
For SIWA (Sign In with Apple) users, their information is anonymized and you must gain explicit consent from the user to be able to collect their personal information. If you had such information you would use updateProfile() to attach it to their user ID at the top of their UserRecord. If there were a ProviderUserInfo entry for Apple, it would consist of:
{
displayName: null, // SIWA does not provide a display name
email: string, // an anonymized email, same as User.email
phoneNumber: null, // SIWA does not provide a phone number
photoURL: null, // SIWA does not provide profile images ​
​ providerId: string, // "apple"
​ uid: string // same as User.localId
}
As the available data is found elsewhere, it is pointless to include in the response and is omitted.
Transferring SIWA users is not a straightforward process. You must follow the steps outlined in the Transferring apps and users to new teams documentation. In short, you use a "Transfer ID Token" with a freshly signed in user's account details, to ask Apple's auth service "Did this user ever sign in for this old client ID?". The returned response will then either say "Yes, their ID with the old client ID was X" or "No, this is a new user". Based on that you migrate their old data across to your new database and authentication ID.
Importantly, after 60 days, you can no longer transfer users from the old service to the new one.

linking anonymous Firebase user with custom token user

I want users to be automatically logged in anonymously when they first visit the site, and then at any point they should be able to log in with an external provider such as Google or LinkedIn, and continue to use the data that had been stored under the anonymous account.
let currentUser = auth.currentUser;
if (!currentUser) {
currentUser = (await auth.signInAnonymously()).user;
}
I have implemented OAuth 2.0 flows to enable users to log into my system using various providers including LinkedIn. My back-end generates a JWT access token which is provided to the web client.
const accessToken = {
sub: my-service-account#my-firebase-project.iam.gserviceaccount.com,
iss: my-service-account#my-firebase-project.iam.gserviceaccount.com,
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
uid: 1234
// other claims...
}
const newUser = await auth.signInWithCustomToken(accessToken);
All good so far - but how do I link the two? According to the account linking documentation I should be able to do something like this:
if (currentUser && currentUser.isAnonymous) {
const provider = new firebase.auth.OAuthProvider('myprovider');
const credential = provider.credential(token, token);
const linkedUser = await currentUser.linkWithCredential(credential);
}
However, firebase sends a request to https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion
{
idToken: {
"provider_id": "anonymous",
"iss": "https://securetoken.google.com/my-firebase-project",
"aud": "my-firebase-project",
"user_id": anonymousUserId,
"sub": anonymousUserId,
"firebase": {
"identities": {},
"sign_in_provider": "anonymous"
}
}
postBody: {
id_token: token,
access_token: token
providerId: "myprovider"
}
requestUri: "http://localhost"
returnIdpCredential: true
returnSecureToken: true
}
and I get a 400 error response:
INVALID_CREDENTIAL_OR_PROVIDER_ID : Invalid IdP response/credential: http://localhost?id_token=${token}&access_token=${token}&provider=myprovider
My front-end is https running on port 3000, and my backend is at /api/oauth/, also https
how do you configure a different requestUri?
what is the endpoint supposed to do?

How to use admin.auth().createUser() with hashed password on Cloud Functions?

I'm building a web app using Angular for the frontend and Cloud Functions for backend stuff. Also using Firebase Authentication to register and login users. To persist data I'm using Cloud Firestore.
The register part is used on Cloud Functions. There I call admin.auth().createUser() to set the user credentials for the Auth user as well as for the user document that I add to Cloud Firestore.
My problem is that I hash the password value on the frontend before the HTTP request to Functions is sent. I do this because of safety reasons. But how can I revert the hash, so I can write the raw password into createUser()?
Actually I save the hashed password and after that I can't login with the created user, because it's the hashed value.
My function in Cloud Functions:
exports.createUser = async (req, res) => {
try {
const user = req.body;
console.log(user.password);
const userRecord = await admin.auth().createUser({
email: user.mail,
emailVerified: false,
password: user.password,
displayName: user.firstname + ' ' + user.lastname,
photoURL: "http://www.example.com/12345678/photo.png",
disabled: false
}).then(async (userRecord) => {
console.log(userRecord.password);
console.log(userRecord);
return await userCollection.doc(userRecord.uid).set({
uid: userRecord.uid,
firstname: user.firstname,
lastname: user.lastname,
photoURL: userRecord.photoURL,
dateOfBirth: user.dateOfBirth,
sex: user.sex,
city: user.city,
activities: user.activities,
offers: user.offers
})
})
console.log("Successfully created new user:");
return res.status(200).send(userRecord);
}
catch (error) {
console.log("Error creating new user:", error);
return res.send(false);
}
The hashing on the frontend looks like this (using ts-md5):
password: Md5.hashStr(this.registerForm.value.registerFormPassword)
Is there a method to reverse the hash or a method from Firebase Auth to hash the password on the client side and a counterpart to revert it on the server side?
I searched the internet for answers but can't find anything to this topic, so could it be that this is absolutely the wrong way with hashing the password? And if so, what's good practise in such cases?

Passing email address in custom token

I have simple backend that can register users and generate jwt's using the firebase-admin package.
When I login with this custom token in firebase console -> authentication the email address is not set. Is there a way to be able to see the email address from there?
You can use the updateUser() method to update the user's email address (detailed docs here):
admin.auth().updateUser(uid, {
email: "modifiedUser#example.com"
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully updated user", userRecord.toJSON());
})
.catch(function(error) {
console.log("Error updating user:", error);
});

Firebase confirmation email not being sent

I've set up Firebase email/password authentication successfully, but for security reasons I want the user to confirm her/his email.
It says on Firebases website:
When a user signs up using an email address and password, a confirmation email is sent to verify their email address.
But when I sign up, I doesn't receive a confirmation email.
I've looked and can only find a code for sending the password reset email, but not a code for sending the email confirmation.
I've looked here:
https://firebase.google.com/docs/auth/ios/manage-users#send_a_password_reset_email
anyone got a clue about how I can do it?
I noticed that the new Firebase email authentication docs is not properly documented.
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
});
Do note that:
You can only send email verification to users object whom you created using Email&Password method createUserWithEmailAndPassword
Only after you signed users into authenticated state, Firebase will return a promise of the auth object.
The old onAuth method has been changed to onAuthStateChanged.
To check if email is verified:
firebase.auth().onAuthStateChanged(function(user) {
if (user.emailVerified) {
console.log('Email is verified');
}
else {
console.log('Email is not verified');
}
});
After creating a user a User object is returned, where you can check if the user's email has been verified or not.
When a user has not been verified you can trigger the sendEmailVerification method on the user object itself.
firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then(function(user){
if(user && user.emailVerified === false){
user.sendEmailVerification().then(function(){
console.log("email verification sent to user");
});
}
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode, errorMessage);
});
You can also check by listening to the AuthState, the problem with the following method is, that with each new session (by refreshing the page),
a new email is sent.
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
});
The confirmation email could be in your spam folder.
Check your spam folder.
You can send verification email and check if was verified as follow into the AuthListener:
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
//---- HERE YOU CHECK IF EMAIL IS VERIFIED
if (user.isEmailVerified()) {
Toast.makeText(LoginActivity.this,"You are in =)",Toast.LENGTH_LONG).show();
}
else {
//---- HERE YOU SEND THE EMAIL
user.sendEmailVerification();
Toast.makeText(LoginActivity.this,"Check your email first...",Toast.LENGTH_LONG).show();
}
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
// [START_EXCLUDE]
updateUI(user);
// [END_EXCLUDE]
}
};
if you're using compile "com.google.firebase:firebase-auth:9.2.0" and
compile 'com.google.firebase:firebase-core:9.2.0' the method sendEmailVerification() will not be resolved until you update to 9.8.0 or higher. It wasted most of time before I figured it out.
I have been looking at this too. It seems like firebase have changed the way you send the verification. for me
user.sendEmailVerification()
did not work.
If you get an error such as user.sendEmailVerification() doesn't exist.
use the following.
firebase.auth().currentUser.sendEmailVerification()
It's not the answer to the question but might help someone.
Don't forget to add your site domain to the Authorised domains list under Sign-in-method
You could send a verification email to any user whose email is linked to the Firebase Auth account. For example, in Flutter you could do. something like :
Future<void> signInWithCredentialAndLinkDetails(AuthCredential authCredential,
String email, String password) async {
// Here authCredential is from Phone Auth
_auth.signInWithCredential(authCredential).then((authResult) async {
if (authResult.user != null) {
var emailAuthCredential = EmailAuthProvider.getCredential(
email: email,
password: password,
);
authResult.user
.linkWithCredential(emailAuthCredential)
.then((authResult,onError:(){/* Error Logic */}) async {
if (authResult.user != null) {
await authResult.user.sendEmailVerification().then((_) {
debugPrint('verification email send');
}, onError: () {
debugPrint('email verification failed.');
});
}
});
}
});
}

Resources