Flutter: have different authentication providers in Firebase - firebase

I have different methods in my app to log in:
Facebook
Google
Apple
Email
For the question I'll focus on the first 2 ones. When the user logs in with Facebook the providers look like this:
That's fine but if I log out and log in again, this time with a new Google account but using same email, the providers look like this:
Now, if I log out and log in again with Facebook I face the account-exists-with-different-credential error. Something for which I have the logic prepared and show its provider login method, but this user should have both provider available and he should be able to log in with both methods.
This is my code:
Future facebookSignIn(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
final AccessToken accessToken = result.accessToken!;
AuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
await _firebaseCredential(context, credential);
}
}
Future googleSignIn(BuildContext context,
[String? email, facebookCredential]) async {
try {
GoogleSignInAccount googleUser;
dynamic popup = await _googleSignIn.signIn();
// cancelled login
if (popup == null) {
return null;
}
googleUser = popup;
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _firebaseCredential(context, credential);
} on FirebaseAuthException catch (e) {
// await FirebaseCrashlytics.instance.recordError(
// e,
// StackTrace.fromString("/googleSignIn"),
// reason: e.message,
// );
// return null;
}
}
_firebaseCredential(BuildContext context, credential) async {
try {
User user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user!;
// Provider.of<MyRents>(context, listen: false).updateUI();
await firebaseProfile.updateUserData(context, user);
} on FirebaseAuthException catch (error) {
// final error = e as FirebaseAuthException;
if (error.code == 'account-exists-with-different-credential') {
String email = error.email!;
// AuthCredential pendingCredential = e.credential;
List<String> signInMethods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (signInMethods.first == 'google.com' ||
signInMethods.first == 'facebook.com') {
// TODO: fix facebook
return await googleSignIn(context, email, credential);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
}
}
Am I missing something?
flutter_facebook_auth: ^4.3.3
google_sign_in: ^5.2.1

Future googleSignIn(BuildContext context,
[String? email, facebookCredential]) async {
try {
GoogleSignInAccount googleUser;
dynamic popup = await _googleSignIn.signIn();
// cancelled login
if (popup == null) {
return null;
}
googleUser = popup;
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _firebaseCredential(context, credential);
} on FirebaseAuthException catch (e) {
// await FirebaseCrashlytics.instance.recordError(
// e,
// StackTrace.fromString("/googleSignIn"),
// reason: e.message,
// );
// return null;
}
}
Future facebookSignIn(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
final AccessToken accessToken = result.accessToken!;
AuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
await _firebaseCredential(context, credential);
}
}
// other methods...
_firebaseCredential(BuildContext context, credential) async {
try {
User user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user!;
await firebaseProfile.updateUserData(context, user);
} on FirebaseAuthException catch (error) {
if (error.code == 'account-exists-with-different-credential') {
String email = error.email!;
List<String> signInMethods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
// bool newUser = (signInMethods.length > 0) ? false : true;
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
var user;
switch (signInMethods.first) {
case 'google.com':
user = await googleSignIn(context, email, credential);
break;
case 'facebook.com':
user = await facebookSignIn(context);
break;
case 'apple.com':
user = await appleSignIn(context);
break;
case 'password':
// since password is managed by user we force have email provider only
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.signInMethods_password'))));
break;
// TODO: apple
}
await linkProvider(context, credential);
return user;
}
return ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
}
// just some extra error covering
Future linkProvider(BuildContext context, credential) async {
try {
await FirebaseAuth.instance.currentUser?.linkWithCredential(credential);
} on FirebaseAuthException catch (e) {
switch (e.code) {
case "provider-already-linked":
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.provider_already_linked'))));
break;
case "invalid-credential":
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(translate('auth.invalid_credential'))));
break;
case "credential-already-in-use":
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.credential_already_in_use'))));
break;
default:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(translate('auth.something_happened'))));
}
}
}
If you do Google -> Facebook it will look like this:
Other way around only Google will be present if your Google email is a trusted email (gmail). More info about that:
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ

Related

FlutterFire authentication using role authentication

I'm trying to sign in based on the role of the user from firebase. But mostly, the tutorials i saw are using older firebase version, therefore I'm using firebase documentation and my logic.
here is my function for auth role sign in
Future<User?> signIn(String email, String password) async {
try {
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email, password: password);
User? user = userCredential.user;
await FirebaseFirestore.instance
.collection('users')
.doc(user!.uid)
.get()
.then((DocumentSnapshot documentSnapshot) {
var docData = documentSnapshot.data();
var role = docData['role'];
if (documentSnapshot.data()['roles'] == 'dosen') {
return 'ok';
} else {
return 'err';
}
});
return user;
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
}
}
but mostly having problem with this section
the error is like this
"The method '[]' can't be unconditionally invoked because the receiver can be 'null'. Try making the call conditional (using '?.') or adding a null check to the target ('!')."
await FirebaseFirestore.instance
.collection('users')
.doc(user!.uid)
.get()
.then((DocumentSnapshot documentSnapshot) {
var docData = documentSnapshot.data();
var role = docData['role'];
if (documentSnapshot.data()['roles'] == 'dosen') {
return 'ok';
} else {
return 'err';
}
});
Thank you.
---------------EDIT---------------
i've solved it using this
Map<String, dynamic> data =
documentSnapshot.data()! as Map<String, dynamic>;
so then i can pass the data using string, in which i call it like this
role = data['role'].toString();
thank you for the help :)
REPLACE THIS:
if (documentSnapshot.data()['roles'] == 'dosen')
WITH:
if (documentSnapshot.data()!['roles'] == 'dosen')

To Register Cloud Firestore with Facebook Input

I can log in with Facebook, but I can not register Cloud Firestore. How do I get the resources to suggest? I can log in with Google, but here I have problems. I've shared the codes below. I'm not receiving an error. The information comes from Facebook but clicking on the button on the try catch process:
Future<Kullanici> facebookIleGiris() async {
print("Login wiht facebook");
FacebookLoginResult _result = await _facebookLogin.logIn(['email']);
switch(_result.status){
case FacebookLoginStatus.cancelledByUser:
print("user logout");
break;
case FacebookLoginStatus.error:
print("error");
break;
case FacebookLoginStatus.loggedIn:
await _loginWithFacebook(_result);
break;
}
}
_loginWithFacebook(FacebookLoginResult _result) async {
FacebookAccessToken accessToken = _result.accessToken;
AuthCredential _credential =
FacebookAuthProvider.getCredential(accessToken: accessToken.token);
AuthResult result = await _firebaseAuth.signInWithCredential(_credential);
print('${result.user.uid} ');
print('${result.user.displayName} now login');
print('${result.user.email} ');
print('${result.user.photoUrl} ');
print('${result.user.phoneNumber}');
return _kullaniciOlustur(result.user);
ButtonClick
void _faceIleGiris() async {
print("user clicked");
var _yetkilendirmeServisi = Provider.of<YetkilendirmeServisi>(context, listen: false);
print("user 2. step");
try {
Kullanici kullanici = await _yetkilendirmeServisi.facebookIleGiris();
print("kullanici kontrole başladi ${kullanici.email}");
if (kullanici != null) {
print(" ${kullanici.email}");
Kullanici fireStoreKullanici = await FirestoreServisi().kullaniciGetir(kullanici.id);
if (fireStoreKullanici == null) {
print("user created ${kullanici.email}");
FirestoreServisi().kullaniciOlustur(
id: kullanici.id,
email: kullanici.email,
kullaniciAdi: kullanici.userName,
fotoUrl: kullanici.fotoUrl
);
print(" ${kullanici.email}");
print("the end");
}
}
}
catch(ex){
print(ex);
}
User.dart
import 'package:flutter/cupertino.d art';
import 'package:cloud_firestore/cloud_firestore. dart';
import 'package:firebase_auth/firebase_auth. dart';
class Kullanici{
final String id;
final String userName;
final String email;
final String fotoUrl;
Kullanici({#required this.id, this.userName, this.email, this.fotoUrl});
factory Kullanici.firebasedenUret(FirebaseUser user){
return Kullanici(id:user.uid,
userName: user.displayName,
email: user.email,
fotoUrl: user.photoUrl
);
}
factory Kullanici.dokumandanuret(DocumentSnapshot doc){
return Kullanici(
id: doc.documentID,
userName: doc['kullaniciAdi'],
email: doc['email'],
fotoUrl: doc['fotoUrl']
);
}
}
createUser method:
//Kullaniciolustur
class FirestoreServisi{
final Firestore _firestore= Firestore.instance;
Future<void> kullaniciOlustur({id,email,kullaniciAdi, fotoUrl=""}) async {
await _firestore.collection("kullanicilar").document(id).setData({
"kullaniciAdi":kullaniciAdi,
"email":email,
"fotoUrl":fotoUrl,
"dTarih":""
});
}
When the FacebookIleGiris () method is logged in with Facebook, the function to be initiated to the initiated function and the error has been solved.
facebookIleGiris() async {
print("facebook ile giriş başladi");
FacebookLoginResult _result = await _facebookLogin.logIn(['email']);
switch(_result.status){
case FacebookLoginStatus.loggedIn:
var a= await _loginWithFacebook(_result);
return a;
break;
case FacebookLoginStatus.cancelledByUser:
print("kullanici çıktı");
break;
case FacebookLoginStatus.error:
print("error");
break;
}
}
_loginWithFacebook(FacebookLoginResult _result) async {
FacebookAccessToken accessToken = _result.accessToken;
AuthCredential _credential =
FacebookAuthProvider.getCredential(accessToken: accessToken.token);
AuthResult result = await _firebaseAuth.signInWithCredential(_credential);
print('${result.user.uid} ');
print('${result.user.displayName} şimdi giriş yaptı');
print('${result.user.photoUrl} ');
print('${result.user.email} ');
print(result.runtimeType);
return _kullaniciOlustur(result.user);

Flutter - Get Firebase custom claims while writing Firebase user to own user instance

I am trying to implement the example given at How do I access custom claims? to my existing code.
I have a Stream which listens to auth changes and updates my own user object with the responded Firebase user. When I store my user object, I would like to get the custom claims of that user as well.
The problem is in _userFromFirebaseUser.
It says "The await expression can only be used in an async function.
Try marking the function body with either 'async' or 'async*'."
But when I do so, the error is hops to my stream where it then says "The argument type 'Future Function(User)' can't be assigned to the parameter type 'User Function(User)'." for "_userFromFirebaseUser" in
// auth change user stream
Stream<local.User> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
Here is my complete authentication class:
import 'package:<my-pckg>/models/user.dart' as local;
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:<my-pckg>/services/database.dart';
//import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;
// create user obj based on firebase user
local.User _userFromFirebaseUser(auth.User user) {
final isAdmin = (await _currentUserClaims)['admin'] == true;
return user != null
? local.User(
uid: user.uid,
email: user.email,
displayName: user.displayName,
isAdmin: isAdmin)
: null;
}
// auth change user stream
Stream<local.User> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
// sign in anon
Future signInAnon() async {
try {
auth.UserCredential result = await _auth.signInAnonymously();
auth.User user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
print('Successfully logged in, User UID: ${user.uid}');
return user;
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
// create a new document for the user with the uid
await DatabaseService(uid: user.uid).updateUserData(null);
print('Successfully registered, User UID: ${user.uid}');
return _userFromFirebaseUser(user);
} catch (error) {
print(error.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
print('User signed out');
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
Future<Map<dynamic, dynamic>> get _currentUserClaims async {
final user = _auth.currentUser;
// If refresh is set to true, a refresh of the id token is forced.
final idTokenResult = await user.getIdTokenResult(true);
return idTokenResult.claims;
}
}
Am I heading into the wrong direction? Is there anything obvious, that I simply do not consider?
Thanks for your help!
For those, heading into the same problem, I found the solution after further research:
You will have to change the .map to .asyncMap.
Here is the code, which works for me:
import 'package:<my-pckg>/models/user.dart' as local;
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:<my-pckg>/services/database.dart';
//import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;
// create user obj based on firebase user
Future<local.User> _userFromFirebaseUser(auth.User user) async {
final isAdmin = (await _userClaims)['admin'] == true;
return user != null
? local.User(
uid: user.uid,
email: user.email,
displayName: user.displayName,
isAdmin: isAdmin)
: null;
}
// auth change user stream
Stream<local.User> get user {
return _auth.authStateChanges().asyncMap(_userFromFirebaseUser);
}
// sign in anon
Future signInAnon() async {
try {
auth.UserCredential result = await _auth.signInAnonymously();
auth.User user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
print('Successfully logged in, User UID: ${user.uid}');
return user;
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
auth.UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
auth.User user = result.user;
// create a new document for the user with the uid
await DatabaseService(uid: user.uid).updateUserData(null);
print('Successfully registered, User UID: ${user.uid}');
return _userFromFirebaseUser(user);
} catch (error) {
print(error.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
print('User signed out');
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
Future<Map<dynamic, dynamic>> get _userClaims async {
final user = _auth.currentUser;
// If refresh is set to true, a refresh of the id token is forced.
final idTokenResult = await user.getIdTokenResult(true);
return idTokenResult.claims;
}
}
Found here: In flutter, how can I "merge" Firebase onAuthStateChanged with user.getTokenId() to return a Stream?

Flutter Firestore adding data

I want to add data on firestore and it wont work. can somebody help me.
This is the newest updated version and I can't figure out how...
firebase_auth: ^0.18.0+1 cloud_firestore: ^0.14.0+2
This is the sign up screen so I want to send data after I create the email and password.
I want to add the document with user uid too.
onPressed: () async {
try {
UserCredential userCredential = await FirebaseAuth
.instance
.createUserWithEmailAndPassword(
email: _emailController.text,
password: _passwordController.text,
);
if (userCredential != null) {
firestore
.collection("user")
.doc('user.uid')
.set({
'username': username,
'email': email,
})
.then((value) => print("User Added"))
.catchError((error) =>
print("Failed to add user: $error"));
Navigator.of(context).pushNamed(AppRoutes.authLogin);
}
} catch (e) {
print(e);
_usernameController.text = "";
_passwordController.text = "";
_repasswordController.text = "";
_emailController.text = "";
//TODO: alertdialog with error
}
setState(() {
saveAttempted = true;
});
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
}
},
Can someone help me with the firestore.. Thank you..
First Create a User class.
class UserData {
final String userId;
final String fullNames;
final String email;
final String phone;
UserData(
{this.userId,
this.fullNames,
this.email,
this.phone});
Map<String, dynamic> getDataMap() {
return {
"userId": userId,
"fullNames": fullNames,
"email": email,
"phone": phone,
};
}
}
Then you can use a function like this one to save the credentials and save the data to firestore
createOrUpdateUserData(Map<String, dynamic> userDataMap) async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
DocumentReference ref =
Firestore.instance.collection('user').document(user.uid);
return ref.setData(userDataMap, merge: true);
}
==
bool validateAndSave() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
void validateAndSubmit() async {
if (validateAndSave()) {
try {
String userId = _formType == FormType.login
? await widget.auth.signIn(_email, _password)//use your signin
: await widget.auth.signUp(_email, _password);//use your signup
if (_formType == FormType.register) {
UserData userData = new UserData(
fullNames: _fullNames,
email: _email,
phone: "",
);
createOrUpdateUserData(userData.getDataMap());
}
} catch (e) {
setState(() {
_isLoading = false;
switch (e.code) {
case "ERROR_INVALID_EMAIL":
_authHint = "Your email address appears to be malformed.";
break;
case "ERROR_EMAIL_ALREADY_IN_USE":
_authHint = "Email address already used in a different account.";
break;
case "ERROR_WRONG_PASSWORD":
_authHint = "Your password is wrong.";
break;
case "ERROR_USER_NOT_FOUND":
_authHint = "User with this email doesn't exist.";
break;
case "EMAIL NOT VERIFIED":
_authHint = "Email not verified: Please go to yor email and verify";
break;
case "ERROR_USER_DISABLED":
_authHint = "User with this email has been disabled.";
break;
case "ERROR_TOO_MANY_REQUESTS":
_authHint =
"Too many Attemps. Account has temporarily disabled.\n Try again later.";
break;
case "ERROR_OPERATION_NOT_ALLOWED":
_authHint = "Signing in with Email and Password is not enabled.";
break;
case "ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL":
_authHint = "The email is in use by another account";
break;
default:
_authHint = "An undefined Error happened.";
}
});
print(e);
errorDialog(context, _authHint);
}
} else {
setState(() {
_authHint = '';
});
}
}
Then use
onpressed:(){
validateAndSubmit();
}
the formtype is an Enum
enum FormType { login, register, reset }
widget.auth.signIn and widget.auth.signUp should be replaced with your signin and signup respectively.
Added a custom error block to differentiate firebase auth errors as well.
Defining an auth page independently will help you reuse your code in future.

How to get user id from firebase auth as string?

I'm trying to insert data to firestore with the authenticated user id as the document id but i got null from the parameter.
Please take a look at my script.
void didChangeDependencies() {
uid = '';
super.didChangeDependencies();
}
Future _fetchUID() async {
var auth = AuthProvider.of(context).auth;
return await auth.getCurrentUser().then((user) => _uid = user.uid);
}
_validateAndSubmit() async {
setState(() {
_errorMessage = '';
_isLoading = true;
});
if (_validateAndSave()) {
try {
_fetchUID();
await Report.create(_uid, _suspectName, _plateNumber, _civilizationId,
_drivingLicenseId, _clause, _description);
return Navigator.of(context).pop();
} catch (e) {
setState(() {
_isLoading = false;
_errorMessage = e.message;
});
print(_errorMessage);
throw Exception(e);
}
}
}
In this method below you can see that I have already tried to set the _uid, but I still cannot get the _uid value.
Future _fetchUID() async {
var auth = AuthProvider.of(context).auth;
return await auth.getCurrentUser().then((user) => _uid = user.uid);
}
This is how the getCurrentUser() method looks like.
Future<FirebaseUser> getCurrentUser() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user;
}
Am I doing it wrong?
First of all, you mixed the Future.then syntax and the async await syntax.
You should probably write your method this way:
void _fetchUID() async {
var auth = AuthProvider.of(context).auth;
_uid = (await auth.getCurrentUser()).uid;
}
If _uid is still null for you after calling _fetchUID this way, then you are simply not signed in, meaning that there is no FirebaseUser as you first need to sign in.
In your _validateAndSubmit method, you also first need to await your _fetchUID call, otherwise it will pass _uid before it has been assigned a value.
...
try {
await _fetchUID();
await Report.create(_uid, ..);
...

Resources