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.
Related
We are having a flutter app (ios, android, web), where users are signed in via username & password.
We are also using google firebase since its powerful and easy to integrate.
The username and password mainly belongs to the website where we are gathering data at. (As example - If they use the website without the app, and they change the password, after that he wont be able to login to the app)
Now the mentionned websites host is giving us API access, login via OpenId to get the access token for the API. Because we are a safety risk since we store the passwort of the users too!
For the API access we dont really need to store Username and password of the user, since they are redundant anyway. But if we want to add a feature (for example message sending or further data storage) we need to have the user signed in into firebase.
Upt to now we are using for (first) signin the following snippet:
firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
and for already signed in users :
firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
Notice that similar credentials are also using to login on the API. (Since the user is there already registered)
How can we login on firebase with said information without asking twice for a password ond username (once for us, and once for the API) ?
We already tried :
await firebaseAuth.signInWithCustomToken(token)
with the jwl token from the OpenId, of course it did not work because the token did not contain the uid reference.
SOLUTION
Create a Firebase Cloud Function just like described in Firebase Cloud Functions.
Be aware that if you want to create a customtoken, the cloud functions need rights. On initializeApp(..)
admin.initializeApp({
serviceAccountId: '{App_Name}#appspot.gserviceaccount.com',
});
So the correct service account has to be selected, you also have to give him the rights to generate tokens. (See => Stackoverflow Question
The Cloud Function does then look the following way :
export const functionName= functions.https.onRequest(async (request, response) => {
const id = request.query.id;
const passcode = request.query.passcode; // not really needed
// add other passcodes for different authentications
if (passcode == "{COMPARE SOMETHING}") {
await admin.auth().createCustomToken(id).then((customToken) => {
response.status(200).send({
'id': id,
'customToken': customToken
});
}).catch((error) => {
response.status(500).send({
'ErrorMessage': "No token could be generated",
"Error": error
});
});
}
else {
response.status(500).send({
'ErrorMessage': "Passcode wrong"
});
}
});
On the other hand we have the code on the mobile app :
// Get JWT Token
Map<String, dynamic> jwtpayload =
Jwt.parseJwt(response_decoded['id_token']); // use import 'package:jwt_decode/jwt_decode.dart';
final queryParameters = {
'id': jwtpayload ['sub'],
'passcode': 'APassCode',
};
final uri = Uri.https('us-central1-{yourApp}.cloudfunctions.net',
'/{functionName}', queryParameters);
final cloud_function_api_call = await client.post(uri);
var decoded_cloud_function_api_call =
jsonDecode(cloud_function_api_call.body);
And at the end :
await firebaseAuth.signInWithCustomToken(
decoded_cloud_function_api_call['customToken']);
I hope it helps others facing a similar issue.
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.
My mobile app built in Flutter uses google login to register users. From within this app I am calling a Firebase cloud function (called questionAnswer) using the Cloud Functions Plugin for Flutter.
If I understand correctly from this documentation, the https request should automatically include the Firebase authentication of the user.
How can I retrieve the authentication information of the user from within the Cloud Function? I need it in order to access data associated with that specific user in a Firebase Cloud Database. Should I include the google auth token as a parameter in the https request?
Here is my code in the Flutter app:
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'questionAnswer',
);
fetchHttps() async {
dynamic resp = await callable.call();
print(resp);
}
This is the code in the Cloud Function
exports.questionAnswer = functions.https.onCall(() => {
console.log('addNumbersLog', "holaLog");
return answer;
});
As you can see from the documentation, auth information is in the second parameter passed to the function. You will need to declare and use it:
exports.questionAnswer = functions.https.onCall((data, context) => {
console.log('addNumbersLog', "holaLog");
console.log(context.auth);
return answer;
});
context here is a CallableContext object.
You can obtain the uid of the user from the CallableContext parameter, which is passed to your onCall handler as the second argument.
From there on you can retrieve the Firebase UserRecord using the .getUser() method and passing in the uid.
exports.questionAnswer = functions.https.onCall((data, { auth }) => {
admin.auth().getUser(auth.uid)
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
});
});
As of firebase-admin#5.11.0 and firebase-functions#1.0.0 firebase-admin no longer takes in an application config when the app initializes.
I had a firestore function that would generate a custom token using firebase-admin’s createCustomToken. Calling that function would generate a credential that I would pass into initializeApp in the credential attribute. How would I go about doing that now?
Do I need to edit process.env.FIREBASE_CONFIG somehow and put the serialized credential there before calling initializeApp?
Based on this issue in Github, it still works.
https://github.com/firebase/firebase-admin-node/issues/224
The following example worked for me:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://yourapplication.firebaseio.com/'
});
exports.createToken = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
return admin.auth()
.createCustomToken(uid)
.then(customToken => {
console.log(`The customToken is: ${customToken}`);
return {status: 'success', customToken: customToken};
})
.catch(error => {
console.error(`Something happened buddy: ${error}`)
return {status: 'error'};
});
});
Michael Chen's cloud function appears to trigger from a HTTP request from somewhere (an external server?). My employee wrote a cloud function that triggers when the user logs in:
// this watches for any updates to the user document in the User's collection (not subcollections)
exports.userLogin = functions.firestore.document('Users/{userID}').onUpdate((change, context) => {
// save the userID ubtained from the wildcard match, which gets put into context.params
let uid = context.params.userID;
// initialize basic values for custom claims
let trusted = false;
let teaches = [];
// check the Trusted_Users doc
admin.firestore().collection('Users').doc('Trusted_Users').get()
.then(function(doc) {
if (doc.data().UIDs.includes(uid)) {
// if the userID is in the UIDs array of the document, set trusted to true.
trusted = true;
}
// Get docs for each language in our dictionary
admin.firestore().collection('Dictionaries').get()
.then(function(docs) {
// for each of those language docs
docs.forEach(function(doc) {
// check if the userID is included in the trustedUIDs array in the doc
if (doc.data().trustedUIDs.includes(uid)) {
// if it is, we push the 2-letter language abbreviation onto the array of what languages this user teaches
teaches.push(doc.data().shortLanguage);
}
});
// finally, set custom claims as we've parsed
admin.auth().setCustomUserClaims(uid, {'trusted': trusted, 'teaches': teaches}).then(() => {
console.log("custom claims set.");
});
});
});
});
First, we put in a lastLogin property on the user object, which runs Date.now when a user logs in and writes the time to the database location, triggering the cloud function.
Next, we get the userID from the cloud function response context.params.userID.
Two variables are then initialized. We assume that the user is not trusted until proven otherwise. The other variable is an array of subjects the user teaches. In a roles-based data security system, these are the collections that the user is allowed to access.
Next, we access a document listing the userIDs of trusted users. We then check if the recently logged in userID is in this array. If so, we set trusted to true.
Next, we go to the database and traverse a collection Dictionaries whose documents include arrays of trusted userIDs (i.e., users allowed to read and write those documents). If the user is in one or more of these arrays, he or she gets that document added to the teaches property on his or her user data, giving the user access to that document.
Finally, we're ready to run setCustomUserClaims to customize the token claims.
Here's a variation for a Callable Cloud Function, thanks to Thomas's answer
Once the custom claim is set, you can access the field in/from .. say, a firebase storage rule.
For example:
allow write: if request.auth.token.isAppAdmin == true;
With a Callable Cloud Function, as long as the admin.auth().setCustomUserClaims(..) function is returned somewhere along the promise chain, the claim field will be added to the request.auth.token object:
const functions = require('firebase-functions');
exports.setIsAdminClaim = functions.https.onCall((data, context) => {
var uid = context.auth.uid;
return admin.auth().setCustomUserClaims(
uid, {
isAppAdmin: true
}
)
.then(() => {
var msg = 'isAppAdmin custom claim set';
console.log(msg);
return new Promise(function (resolve, reject) {
var resolveObject = {
message : msg
};
resolve(resolveObject);
});
});
});
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"