How to make Flutter authentification just over Firestore (without Firebase Auth) - firebase

I have to create a manual Registration and Sign in over Firestore without using Firebase Auth.
The reason for that requrirement is once when user wants to reset his password, first of all I have to sent him a confirmation code (MD5) on his email address and when he receives the code, above new password that he should enter, he must paste that code.
I'll sent him a code on email and in the same time, that code will be also in Firestore from which later on I will compare (code is correct or not).
By the end, (if entered code is correct, his new password will overwrite previous stored password in his document). Documents are named by email address which means that email should be unique.
Here is the code that I'm using for registration:
void onPressedRegister(BuildContext context, String fullName, String email,
String password, String phoneNumber, dynamic formKey) {
if (formKey.currentState.validate()) {
db.collection("firestoreUsers").document(email).setData({
'fullName': fullName,
'email': email,
'password': password,
'phoneNumber': phoneNumber
});
Now I have a problem with Sign in because I can't check is user stored in database or not. Here is what I wrote:
Future<bool> signInOverFirestore(String email, String password) async {
db
.collection('firestoreUsers')
.where('email', isEqualTo: email)
.getDocuments();
return true;
}
So is it possible to make sign in like this and also update that user later on when he enter correct code from his email, or not?

If you're not using Firebase Auth with Firestore and security rules, it's basically impossible to have secure per-user data. What you have now is storing plaintext email addresses and passwords for everyone in the world to see. All someone has to do is use the Firestore REST API to query the entire firestoreUsers collection to see everything.

According to my understanding, you want to make Firestore data public (access/read without authentication) check if some document with a value and if that value doesn't exist, you want to create a new document. In order to do that you can try to fetch the document which is equal to your value(email) and catch the error or listen if it is successful. You may accomplish it with the following example.
_db
.collection('path')
.where('field', isEqualTo: '')
.getDocuments()
.then((value) {
print('User on Firestore : $value');
}).catchError((e) {
print('Error no document found');
});

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.

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.

How to set Firestore security rules for create operation

I am working on developing a mobile app in Flutter and using Firebase as backend.
Users will need to sign up to be able to use my app and I will save some user data in a user_profile collection in Firestore.
Since a new document will be added to the user_profile collection, I wonder how security rules should be set for creating a new document for the first time when the user registers.
Here is what happens during the sign up in my case:
// Step 1: Register user with email and password
FirebaseAuth _auth = FirebaseAuth.instance;
await _auth.createUserWithEmailAndPassword(email: email, password: password);
// Step 2: save user data in user_profile collection
FirebaseFirestore.instance
.collection('user_profile')
.doc(user.uid)
.set({
'uid': user.uid,
'email': email,
'name': name,
'date': DateTime.now,
})
Since I save the user data in the user_profile collection after the call for createUserWithEmailAndPassword(..) function is completed (meaning that the user is now authenticated), is it safe to define the security rule for create operation in Firebase as following? Or should I set it differently to make it secure in the right way?
// Rules for User Profile data
match /user_profile/{any} {
// Applies to writes to non-existent documents
allow create: if request.auth != null;
}
From my experience, this should be fine but you could additionally check that the uid contained in the document corresponds to the authenticated user.
allow create: if request.auth != null &&
request.auth.uid == request.resource.data.uid;

How to get Facebook Email from flutter_facebook_login in Firebase?

I am using flutter_facebook_login plugin(3.0.0). But my Facebook email doesn't appear inside my Firebase user identifier column; instead, I see this "--". Please help!
Future loginWithFacebook() async {
FacebookLogin facebookLogin = FacebookLogin();
final result = await facebookLogin.logIn(['email', 'public_profile']);
//result.accessToken.
final token = result.accessToken.token;
print('Facebook token userID : ${result.accessToken.permissions}');
final graphResponse = await http.get(
'https://graph.facebook.com/v2.12/me?fields=name,first_name,last_name,email&access_token=${token}');
final profile = jsonDecode(graphResponse.body);
print(profile);
if (result.status == FacebookLoginStatus.loggedIn) {
final credential = FacebookAuthProvider.getCredential(accessToken: token);
FirebaseUser fbUser = (await _auth.signInWithCredential(credential)).user;
//print('Our credential is : $credential');
print('Facebook firebase user ${fbUser.}');
}
return _userFromFacebookLogin(profile);
}
}
Possible Points.
Some Facebook accounts are created using Mobile numbers, so whenever we request for email address we get an empty string.
Email was set to "--" on firebase auth due to missing permission to read email, which fixed by:
final FacebookLoginResult facebookLoginResult = await facebook_Login.logIn(['email', 'public_profile']);
After reading post in firebase-talk google group here https://groups.google.com/forum/#!topic/firebase-talk/gPGNq-IkTLo, I found out the answer. The issue was happened because I'm using "Allow creation of multiple accounts with the same email address" in Firebase Auth sign-in method.
So I change the option into: "Prevent creation of multiple accounts with the same email address" can it's working properly now. It's simple as that. It's true I need more logic to merge accounts having the same email address, but it's okay.
Maybe everyone else having the same issue, can also try this, and hopefully it's solved as well.

Not Able To get the token Id in flutter

I am using below code to register new user in Flutter Application with Firebase as backend, but I am not able to retrieve the token id in my application, when I tried to save it the firestore database it is storing it as null.
FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: emailInputController.text,
password: pwdInputController.text)
.then((currentUser) =>
Firestore.instance
.collection("users")
.document(currentUser.user.uid)
.setData({
"id": currentUser.user.uid,
"tokenId": currentUser.user.getIdToken(),
"fullname": fullNameInputController.text,
})
I receive this in my logs about the tokenId
Invalid argument: Instance of 'Future<'IdTokenResult'>'
As indicated in the official documentation getIdToken(), it actually returns a promise, so you won't be able to directly access the value. You will need to await the value to be loaded and them manage it. You will need to handle as in the below code sample:
FirebaseAuth.instance.currentUser().then(user => {
if (user != null) {
user.getIdToken().then(token => {
//handle token
}
}
});
In case you want a more direct way, the below line should work for you too.
var token = await FirebaseAuth.instance.currentUser().getIdToken();
While this codes are untested,, I believe they should help you have handle the promise, you should have the value and saving it in your database should not face any more issues.
Let me know if the information helped you!

Resources