How to get the verified phone number in firebase cloud functions? - firebase

I have this function to add a custom claim when a user is verified his phone number
exports.phoneCustomClaim = functions.auth.user().onCreate((user) => {
const sellerNumber = user.phone; // here i am not getting the phone number
const uid = user.uid;
const customClaims = {
number: sellerNumber,
};
console.log(`Uid: ${uid}, number: ${sellerNumber}`);
console.log(`before Seller number: ${sellerNumber}`);
return admin.auth().setCustomUserClaims(uid, customClaims).then(() => {
console.log(`claim added!`);
})
.catch(error => {
console.log(error);
})
});
Log
Uid: 1mlxF1T8xcWbByAzQ7rfKjdshj, number: undefined
please how to get that phone number?

I suspect that the user account is created (but not authenticated) before the phone number is actually verified. This is similar to how the flow for email verification works.
If that is the case, you'll have to work around the problem. As there currently is no trigger for Cloud Functions when a user profile is updated, the best you can do is call a callable Cloud Function from your code when the user has verified their phone number.

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.

How do I query the Firebase authentication by phone number?

In our React 16.13.0 application, we are using Firebase. We link a user to a phone number like so
return firebase
.auth()
.currentUser.linkWithPhoneNumber(phoneNumber, recaptchaVerfier)
.then(function (confirmationResult: any) {
var code = window.prompt("Provide your SMS code");
recaptchaVerfier.clear();
return confirmationResult.confirm(code).then(() => {
callback();
});
})
I was curious how would we then go back and query the Firebase authentication table for users that have a particular phone number, assuming that phone number is used as the identifier for the user, as seen in the portal Authentication view below
. The purpose of querying is not for logging in, but rather for looking up various users.
You cannot query the Authentication database with the Client SDKs but you can with the Admin SDKs.
This means that you will need to implement this querying in your own server or in a Cloud Function.
You could for example write a Callable Cloud Function that would return the user details for a specific user.
The code would look like:
exports.getUserByPhone = functions.https.onCall(async (data, context) => {
try {
const phoneNbr = data.phoneNbr;
const userRecord = await admin.auth().getUserByPhoneNumber(phoneNbr);
return userRecord;
} catch (error) {
// See https://firebase.google.com/docs/functions/callable#handle_errors
// Also see here the error codes: https://firebase.google.com/docs/auth/admin/errors
// In particular, the auth/user-not-found code is returned if there is no existing user record corresponding to the provided identifier.
}
});
You would then call this Cloud Function from your front-end as explained here in the doc, by passing the value of the desired phoneNbr.

How to use Firebase's 'verifyPhoneNumber()' to confirm phone # ownership without using # to sign-in?

Im using react-native-firebase v5.6 in a project.
Goal: In the registration flow, I have the user input their phone number, I then send a OTP to said phone number. I want to be able to compare the code entered by the user with the code sent from Firebase, to be able to grant entry to the next steps in registration.
Problem: the user gets the SMS OTP and everything , but the phoneAuthSnapshot object returned by firebase.auth().verifyPhoneNumber(number).on('state_changed', (phoneAuthSnapshot => {}), it doesn't give a value for the code that firebase sent, so there's nothing to compare the users entered code with. However, there's a value for the verificationId property. Here's the object return from the above method:
'Verification code sent', {
verificationId: 'AM5PThBmFvPRB6x_tySDSCBG-6tezCCm0Niwm2ohmtmYktNJALCkj11vpwyou3QGTg_lT4lkKme8UvMGhtDO5rfMM7U9SNq7duQ41T8TeJupuEkxWOelgUiKf_iGSjnodFv9Jee8gvHc50XeAJ3z7wj0_BRSg_gwlN6sumL1rXJQ6AdZwzvGetebXhZMb2gGVQ9J7_JZykCwREEPB-vC0lQcUVdSMBjtig',
code: null,
error: null,
state: 'sent'
}
Here is my on-screen implementation:
firebase
.firestore()
.collection('users')
.where('phoneNumber', '==', this.state.phoneNumber)
.get()
.then((querySnapshot) => {
if (querySnapshot.empty === true) {
// change status
this.setState({ status: 'Sending confirmation code...' });
// send confirmation OTP
firebase.auth().verifyPhoneNumber(this.state.phoneNumber).on(
'state_changed',
(phoneAuthSnapshot) => {
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.CODE_SENT:
console.log('Verification code sent', phoneAuthSnapshot);
this.setState({ status: 'Confirmation code sent.', confirmationCode: phoneAuthSnapshot.code });
break;
case firebase.auth.PhoneAuthState.ERROR:
console.log('Verification error: ' + JSON.stringify(phoneAuthSnapshot));
this.setState({ status: 'Error sending code.', processing: false });
break;
}
},
(error) => {
console.log('Error verifying phone number: ' + error);
}
);
}
})
.catch((error) => {
// there was an error
console.log('Error during firebase operation: ' + JSON.stringify(error));
});
How do I get the code sent from Firebase to be able to compare?
As #christos-lytras had in their answer, the verification code is not exposed to your application.
This is done for security reasons as providing the code used for the out of band authentication to the device itself would allow a knowledgeable user to just take the code out of memory and authenticate as if they had access to that phone number.
The general flow of operations is:
Get the phone number to be verified
Use that number with verifyPhoneNumber() and cache the verification ID it returns
Prompt the user to input the code (or automatically retrieve it)
Bundle the ID and the user's input together as a credential using firebase.auth.PhoneAuthProvider.credential(id, code)
Attempt to sign in with that credential using
firebase.auth().signInWithCredential(credential)
In your source code, you also use the on(event, observer, errorCb, successCb) listener of the verifyPhoneNumber(phoneNumber) method. However this method also supports listening to results using Promises, which allows you to chain to your Firebase query. This is shown below.
Sending the verification code:
firebase
.firestore()
.collection('users')
.where('phoneNumber', '==', this.state.phoneNumber)
.get()
.then((querySnapshot) => {
if (!querySnapshot.empty) {
// User found with this phone number.
throw new Error('already-exists');
}
// change status
this.setState({ status: 'Sending confirmation code...' });
// send confirmation OTP
return firebase.auth().verifyPhoneNumber(this.state.phoneNumber)
})
.then((phoneAuthSnapshot) => {
// verification sent
this.setState({
status: 'Confirmation code sent.',
verificationId: phoneAuthSnapshot.verificationId,
showCodeInput: true // shows input field such as react-native-confirmation-code-field
});
})
.catch((error) => {
// there was an error
let newStatus;
if (error.message === 'already-exists') {
newStatus = 'Sorry, this phone number is already in use.';
} else {
// Other internal error
// see https://firebase.google.com/docs/reference/js/firebase.firestore.html#firestore-error-code
// see https://firebase.google.com/docs/reference/js/firebase.auth.PhoneAuthProvider#verify-phone-number
// probably 'unavailable' or 'deadline-exceeded' for loss of connection while querying users
newStatus = 'Failed to send verification code.';
console.log('Unexpected error during firebase operation: ' + JSON.stringify(error));
}
this.setState({
status: newStatus,
processing: false
});
});
Handling a user-sourced verification code:
codeInputSubmitted(code) {
const { verificationId } = this.state;
const credential = firebase.auth.PhoneAuthProvider.credential(
verificationId,
code
);
// To verify phone number without interfering with the existing user
// who is signed in, we offload the verification to a worker app.
let fbWorkerApp = firebase.apps.find(app => app.name === 'auth-worker')
|| firebase.initializeApp(firebase.app().options, 'auth-worker');
fbWorkerAuth = fbWorkerApp.auth();
fbWorkerAuth.setPersistence(firebase.auth.Auth.Persistence.NONE); // disables caching of account credentials
fbWorkerAuth.signInWithCredential(credential)
.then((userCredential) => {
// userCredential.additionalUserInfo.isNewUser may be present
// userCredential.credential can be used to link to an existing user account
// successful
this.setState({
status: 'Phone number verified!',
verificationId: null,
showCodeInput: false,
user: userCredential.user;
});
return fbWorkerAuth.signOut().catch(err => console.error('Ignored sign out error: ', err);
})
.catch((err) => {
// failed
let userErrorMessage;
if (error.code === 'auth/invalid-verification-code') {
userErrorMessage = 'Sorry, that code was incorrect.'
} else if (error.code === 'auth/user-disabled') {
userErrorMessage = 'Sorry, this phone number has been blocked.';
} else {
// other internal error
// see https://firebase.google.com/docs/reference/js/firebase.auth.Auth.html#sign-inwith-credential
userErrorMessage = 'Sorry, we couldn\'t verify that phone number at the moment. '
+ 'Please try again later. '
+ '\n\nIf the issue persists, please contact support.'
}
this.setState({
codeInputErrorMessage: userErrorMessage
});
})
}
API References:
verifyPhoneNumber() - React Native or Firebase
PhoneAuthProvider.credential(id, code) - Firebase
signInWithCredential() - React Native or Firebase
Suggested code input component:
react-native-confirmation-code-field
Firebase firebase.auth.PhoneAuthProvider won't give you the code for to compare, you'll have to use verificationId to verify the verificationCode that the user enters. There is a basic example in firebase documentation than uses firebase.auth.PhoneAuthProvider.credential and then tries to sign in using these credentials with firebase.auth().signInWithCredential(phoneCredential):
firebase
.firestore()
.collection('users')
.where('phoneNumber', '==', this.state.phoneNumber)
.get()
.then((querySnapshot) => {
if (querySnapshot.empty === true) {
// change status
this.setState({ status: 'Sending confirmation code...' });
// send confirmation OTP
firebase.auth().verifyPhoneNumber(this.state.phoneNumber).on(
'state_changed',
(phoneAuthSnapshot) => {
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.CODE_SENT:
console.log('Verification code sent', phoneAuthSnapshot);
// this.setState({ status: 'Confirmation code sent.', confirmationCode: phoneAuthSnapshot.code });
// Prompt the user the enter the verification code they get and save it to state
const userVerificationCodeInput = this.state.userVerificationCode;
const phoneCredentials = firebase.auth.PhoneAuthProvider.credential(
phoneAuthSnapshot.verificationId,
userVerificationCodeInput
);
// Try to sign in with the phone credentials
firebase.auth().signInWithCredential(phoneCredentials)
.then(userCredentials => {
// Sign in successfull
// Use userCredentials.user and userCredentials.additionalUserInfo
})
.catch(error => {
// Check error code to see the reason
// Expect something like:
// auth/invalid-verification-code
// auth/invalid-verification-id
});
break;
case firebase.auth.PhoneAuthState.ERROR:
console.log('Verification error: ' + JSON.stringify(phoneAuthSnapshot));
this.setState({ status: 'Error sending code.', processing: false });
break;
}
},
(error) => {
console.log('Error verifying phone number: ' + error);
}
);
}
})
.catch((error) => {
// there was an error
console.log('Error during firebase operation: ' + JSON.stringify(error));
});
In order to use Multi-factor Authentication, you must create Phone Sign-in provider in the background alongside primary (in your case) email sign-in provider either initially or later while user choose to update settings and enables MFA. And then link it while user is logged using email sign-in provider as follows;
const credential = auth.PhoneAuthProvider.credential(verificationId, code);
let userData = await auth().currentUser.linkWithCredential(credential);
This is not supported by firebase unfortunately. Logging in and out after signInWithCredential can work, but is very confusing
I was facing the same difficulty. My aim was only to verify users' phone numbersenter image description here and then register them using email and password. After a long intense trial and error methodology, I have arrived at the solution. But the point is I am using firebase in my android application. what it did was
I first tried matching the OTP with the user entered OTP, but the OTP that firebase provides us in the backend is encrypted with some logic and the logic is nowhere in the documentation so I could not decrypt it.
The second approach worked for me. What I did was, I signed in the user using the phone authorization and when the task was successful I deleted the newly created user there itself and then signed in the user using email id and password.

There is no user record corresponding to this identifier. The user may have been deleted

There is no user record corresponding to this identifier. The user may have been deleted.
export const createEmployee = ({ email, password}) => {
return (dispatch) =>{`
firebase.auth().createUserWithEmailAndPassword ( email,password )
.then(
firebase.auth().signInWithEmailAndPassword( email,password )
.then(Actions.profile())
)
};
};
According to the official documentation, after the success of createUserWithEmailAndPassword the user is automatically signed-in.
Create a new account by passing the new user's email address and
password to createUserWithEmailAndPassword:
firebase.auth().createUserWithEmailAndPassword(email, password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
If the new account was created, the user is signed in automatically.
Have a look at the Next steps section below to get the signed in user
details.
[...]
Look also at this SO question.
So in your promise you can just get you already authenticated user like that:
var user = firebase.auth().currentUser;
No need to make signInWithEmailAndPassword call.

How to get the email of any user in Firebase based on user id?

I need to get a user object, specifically the user email, I will have the user id in this format:
simplelogin:6
So I need to write a function something like this:
getUserEmail('simplelogin:6')
Is that possible?
It is possible with Admin SDK
Admin SDK cannot be used on client, only in Firebase Cloud Functions which you can then call from client. You will be provided with these promises: (it's really easy to set a cloud function up.)
admin.auth().getUser(uid)
admin.auth().getUserByEmail(email)
admin.auth().getUserByPhoneNumber(phoneNumber)
See here https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data
In short, this is what you are looking for
admin.auth().getUser(data.uid)
.then(userRecord => resolve(userRecord.toJSON().email))
.catch(error => reject({status: 'error', code: 500, error}))
full snippet
In the code below, I first verify that the user who calls this function is authorized to display such sensitive information about anybody by checking if his uid is under the node userRights/admin.
export const getUser = functions.https.onCall((data, context) => {
if (!context.auth) return {status: 'error', code: 401, message: 'Not signed in'}
return new Promise((resolve, reject) => {
// verify user's rights
admin.database().ref('userRights/admin').child(context.auth.uid).once('value', snapshot => {
if (snapshot.val() === true) {
// query user data
admin.auth().getUser(data.uid)
.then(userRecord => {
resolve(userRecord.toJSON()) // WARNING! Filter the json first, it contains password hash!
})
.catch(error => {
console.error('Error fetching user data:', error)
reject({status: 'error', code: 500, error})
})
} else {
reject({status: 'error', code: 403, message: 'Forbidden'})
}
})
})
})
BTW, read about difference between onCall() and onRequest() here.
Current solution as per latest update of Firebase framework:
firebase.auth().currentUser && firebase.auth().currentUser.email
See: https://firebase.google.com/docs/reference/js/firebase.auth.Auth.html#currentuser
Every provider haven't a defined email address, but if user authenticate with email. then it will be a possible way to achieve above solution.
To get the email address of the currently logged in user, use the getAuth function. For email and password / simplelogin you should be able to get the email like this:
ref = new Firebase('https://YourFirebase.firebaseio.com');
email = ref.getAuth().password.email;
In my opinion, the password object is not very aptly named, since it contains the email field.
I believe it is not a Firebase feature to get the email address of just any user by uid. Certainly, this would expose the emails of all users to all users. If you do want this, you will need to save the email of each user to the database, by their uid, at the time of account creation. Other users will then be able to retrieve the email from the database by the uid .
simple get the firebaseauth instance.
i created one default email and password in firebase. this is only for the security so that no one can get used other than who knows or who purchased our product to use our app.
Next step we are providing singup screen for user account creation.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String email = user.getEmail();
every time user opens the app, user redirecting to dashboard if current user is not equal to our default email.
below is the code
mAuth = FirebaseAuth.getInstance();
if (mAuth.getCurrentUser() != null){
String EMAIL= mAuth.getCurrentUser().getEmail();
if (!EMAIL.equals("example#gmail.com")){
startActivity(new Intent(LoginActivity.this,MainActivity.class));
finish();
}
}
i Am also searching for the same solution finally i got it.
I had the same problem. Needed to replace email in Firestore by uid in order to not keep emails all around the place. It is possible to call it from a script on your computer using Service Account. You don't need Firebase Functions for this.
First Generate service account and download its json key.
Firebase Console > gear icon > Project settings > Service accounts > Generate a new private key button.
https://console.firebase.google.com/u/0/project/MYPROJECT/settings/serviceaccounts/adminsdk
Then create project, add the key and call the Admin SDK.
npm init
npm install dotenv firebase-admin
Place the json key file from above into .keys directory, keeping the project directory clean of keys files. Also .gitignore the directory.
Write the path of the json key file into .env file like this: GOOGLE_APPLICATION_CREDENTIALS=".keys/MYPROJECT-firebase-adminsdk-asdf-234lkjjfsoi.json". We will user dotenv to load it later.
Write following code into index.js:
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
(async () => {
const email = "admin#example.com";
const auth = admin.auth();
const user = await auth.getUserByEmail(email);
// Or by uid as asked
//const user = await auth.getUser(uid);
console.log(user.uid, user.email);
//const firestore = admin.firestore();
// Here be dragons...
})();
Run as follows node -r dotenv/config index.js
See the docs
Current solution (Xcode 11.0)
Auth.auth().currentUser? ?? "Mail"
Auth.auth().currentUser?.email ?? "User"

Resources