Firebase Convert Anonymous User Account to Permanent Account Error - firebase

Using Firebase for web I can successfully create an anonymous user. I can also create a new email/password user. But when trying to convert an anonymous user to a email/password user I get error:
auth/provider-already-linked
User can only be linked to one identity for the given provider.
Firebase documents the procedure here under section "Convert an anonymous account to a permanent account" here:
https://firebase.google.com/docs/auth/web/anonymous-auth
Here's the account link code. Anonymous user is signed in.
return firebase.auth().createUserWithEmailAndPassword(email, password).then(newUser => {
// Credential is being successfully retrieved. Note "any" workaround until typescript updated.
let credential = (<any>firebase.auth.EmailAuthProvider).credential(email, password);
firebase.auth().currentUser.link(credential)
.then(user => { return user; })
.catch(err => console.log(err)); // Returns auth/provider-already-linked error.
});

You should not call createUserWithEmailAndPassword to upgrade the anonymous user. This will sign up a new user, signing out the currently signed in anonymous user.
All you need is the email and password of the user. IDP providers (e.g. Google, Facebook), on the contrary, will require to complete their full sign in flow to get their tokens to identify the user. We do recommend to use linkWithPopup or linkWithRedirect for these, though.
Example:
// (Anonymous user is signed in at that point.)
// 1. Create the email and password credential, to upgrade the
// anonymous user.
var credential = firebase.auth.EmailAuthProvider.credential(email, password);
// 2. Links the credential to the currently signed in user
// (the anonymous user).
firebase.auth().currentUser.linkWithCredential(credential).then(function(user) {
console.log("Anonymous account successfully upgraded", user);
}, function(error) {
console.log("Error upgrading anonymous account", error);
});
Let me know if that works!

After you log in as an Anonymous user, run this code to raise Popup and connect your anon user wit some OAUTH provider
const provider = new firebase.auth.FacebookAuthProvider()
firebase.auth().currentUser.linkWithPopup(provider)
console.log(provider)

For iOS, Swift 5 to create a credential use
EmailAuthProvider.credential(withEmail: , password: )
example:
let credential = EmailAuthProvider.credential(withEmail: emailTextField.text!, password: passwordTextField.text!)
Auth.auth().currentUser?.link(with: credential, completion: { (authDataResult: AuthDataResult?, error) in
// ...
})

Related

What Sign in method to use best?

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.

Custom username and password login using Flutter firebase

Anyone knows how to have a custom username and password login for flutter using firebase or any other service?
I am not talking about email verification. The user just have to make a username with a password and login.
You can set up a authentication to Firebase with the firebase_auth plugin for Flutter https://pub.dev/packages/firebase_auth
After that you can utilize Firestore to save the user to a collection called users for example with new fields (username in this case) which is assigned to the user.
Which means that on login you need to also map the custom fields from Firestore to the user object in Flutter. The process of getting Firestore data at the same time you being logged in would look something like this (user is a Firebase User object).
AuthService() {
user = Observable(_auth.onAuthStateChanged);
profile = user.switchMap((FirebaseUser u) {
if (u != null) {
return _db
.collection('users')
.document(u.uid)
.snapshots()
.map((snap) => snap.data);
} else {
return Observable.just({});
}
});
}

create user with self-specified uid

I am using flutter with firebase to manage my users, and in this link, it says you can specify the uid during user creation: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
My question: What's the equivalent in dart/ flutter? I understand firebase auto-generates one for you, but in my use case I need to be able to specify mine.
For flutter, I am only aware of createUserWithEmailAndPassword method but it does not have a 'uid' argument.
FirebaseAuth.instance.createUserWithEmailAndPassword(email: null, password: null)
In the link above, however, they provided an example (node.js) with such methods.
admin.auth().createUser({
uid: 'some-uid',
email: 'user#example.com',
phoneNumber: '+11234567890'
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch(function(error) {
console.log('Error creating new user:', error);
});
You can fully control the creation of Firebase Authentication users by implementing a custom provider. But as you can probably imagine, this is a sensitive operation, so the code requires that you have full administrative access to the Firebase project. For that reason, you can't run this type of operation in your Flutter app, but must run it on a trusted environment, such as your development machine, a server you control, or Cloud Functions.
Typically this means:
you'll gather the user's credentials in your Flutter app
send them (securely) to a custom endpoint on the server
and there validate the the user credentials are correct
and use the Admin SDK to create a token for the user
that you then send back securely to the Flutter app
There is no such option for any of the Firebase the client SDKs on any platform (Android, iOS, web, Flutter, etc).
Firebase Admin is a server SDK, and trusts that the code calling its methods is privileged and running in a secure and trusted environment. This is considered to be safe for inventing UIDs. The client SDKs are run on user devices, which is considered untrusted and could be compromised. In that case, the Firebase Auth product has to come up with an appropriate UID for the user.
Use the firebase cloud functions
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.createUser1 = functions.https.onCall(async (data, _context) => {
try {
const user = await admin.auth().createUser({
uid: data.uid,
phoneNumber: data.phoneNumber,
disabled: false,
}); return {response: user};
} catch (error) {
throw new functions.https.HttpsError("failed to create a user");
}
});
then request from flutter app
{
"data":
{
"uid": "12345678",
"phoneNumber": "+905378227777",
"disabled": false
}
}

Cloud Functions for Firebase auth.user API has empty displayName property

I have an Android app using Firebase with database, authentication and cloud functions modules. When a user creates a profile through the app and sets a user full name, the value of
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
// user signed in
String displayName = user.getDisplayName();
// do whatever with displayName...
} else {
// ....
}
is pretty much as expected - whatever the user put in.
I also have a cloud function, which creates a database record for the new user:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.createUserRecord = functions.auth.user().onCreate(function(event) {
const user = event.data;
const userName = user.displayName || 'Anonymous';
const userSignedUp = (new Date()).toJSON();
console.log('A new user signed up: ', user.uid, userName, userSignedUp);
return admin.database().ref('users').push({
name: `${userName}`,
email: `${user.email}`,
ref: `${user.uid}`,
signup: `${userSignedUp}`
});
});
This works mostly OK, except that both the log entry, and the resulting record in the database list Anonymous as the name value.
Can anyone please tell me where I am going wrong with this? Thanks.
Best regards,
~Todor
UPDATE:
Forgot to mention, the user is created with the email/password provider.
In FirebaseUI, creation of a user via email/password is done in two steps: the user is created using the address and password, then the user profile is update with the entered username. This can be seen in the FirebaseUI RegisterEmailFragment.
At this time, Cloud Functions offers only a trigger for user creation, the one you are using, functions.auth.user().onCreate(). There is no trigger for user profile update. Your trigger fires when the user is created. The event data contains the user profile as it exists at that moment, before it has been updated with the user name.
You will need to use some other means to obtain the user name. One option is to use a database event to trigger a function that would retrieve the user data after the profile is updated:
admin.auth().getUser(uid)
.then(function(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);
});

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