Custom username and password login using Flutter firebase - 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({});
}
});
}

Related

Firebase Auth authStateChanges trigger

I'm using firebase to authenticate a user and create a user in firestore database :
final auth.FirebaseAuth _firebaseAuth;
Future<void> signUp(
{#required String email, #required String password}) async {
assert(email != null && password != null);
try {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
await createUserInDatabaseIfNew();
} on Exception {
throw SignUpFailure();
}
}
With firebase, once the method .createUserWithEmailAndPassword() is executed, it triggers right after the authStateChanges which I am using in my code to send the new user in the user stream, and eventually retrieve its data from the database
Stream<User> get user {
return _firebaseAuth.authStateChanges().map((firebaseUser) {
return firebaseUser == null ? User.empty : firebaseUser.toUser;
});
}
StreamSubscription<User> _userSubscription = _authenticationRepository.user.listen((user) {
return add(AuthenticationUserChanged(user));}
if(event is AuthenticationUserChanged){
if(event.user != User.empty){
yield AuthenticationState.fetchingUser();
User userFromDatabase;
try{
var documentSnapshot = await _firebaseUserRepository.getUser(event.user.id);
userFromDatabase = User.fromEntity(UserEntity.fromSnapshot(documentSnapshot));
yield AuthenticationState.authenticated(userFromDatabase);
}
The problem that I am facing, is that because of _firebaseAuth.createUserWithEmailAndPassword, _firebaseAuth.authStateChanges is triggerd before the user is created in the database, and eventually when I try to retrieve that user, it still does not exist in the database.
I would like _firebaseAuth.authStateChanges() to be triggered after my method createUserInDatabaseIfNew runs.
How could I achieve that ?
I would like _firebaseAuth.authStateChanges() to be triggered after my method createUserInDatabaseIfNew runs.
The auth state change listener fires when the user's authentication state change, which is when their sign in completes. There's no way to change this behavior, nor should there be.
If you want to trigger when the user's registration in your app has completed, you should respond to events that signal that. So if registration means that the user is written to the database, you can use a onSnapshot listener on the database to detect user registration.
You could even combine the two:
Use an auth state change listener to detect when the sign in completes.
Inside that auth state listener, then attach a snapshot listener for the user's registration document.

Get Identifier field from Firebase Auth Console

I'm trying to get the email from the user that's currently authenticated using the Facebook Firebase provider. The email is listed under the Identifier field inside the Project's Firebase Authentication Console:
However when I invoke firebase.auth().currentUser the user information loads, however the email field is null. Any ideas on how to get the Identifier (which is where I see the email address) from Firebase? Is this even possible?
Below is the code I'm using:
componentDidMount() {
let user = firebase.auth().currentUser;
let name, email, photoUrl, uid, emailVerified;
if (user !== null) {
name = user.displayName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid;
console.log(name, email, photoUrl, emailVerified, uid);
}
}
Note: Prevent creation of multiple accounts with the same email address is enabled in Firebase. Also, Facebook API permissions are set to ['public_profile', 'email']
After some testing and debugging I found that the email field will be populated if you're using a regular Firebase Email/Password Sign In method. However, if you're using another Sign In provider method such as Facebook, the email field will appear null (not sure why).
Further inspection of the user object revealed a providerData property.
It's an array that contains all the provider information (including the email address):
So, I updated my code to accommodate this:
componentDidMount() {
let user = firebase.auth().currentUser;
let name, email, photoUrl, uid, emailVerified;
if (user) {
name = user.displayName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid;
if (!email) {
email = user.providerData[0].email;
}
console.log(name, email, photoUrl, emailVerified, uid);
}
}
In my case, the getEmail() method always returns data for three sign-in possibilities (if user gave authorization to my app to show/use email): Sign in with Email, Sign in with Google, Sign in with Facebook.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
Log.v(TAG,"user.getEmail():"+user.getEmail());
if (user.getEmail() == null){
// User did not authorize the app to show/user email
}
else {
Log.v(TAG,"user.getEmail():"+user.getEmail());
}

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);
});

Firebase Convert Anonymous User Account to Permanent Account Error

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
// ...
})

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