I have a Flutter project that's using the cloud_firestore plugin for data access. Once a user authenticates to the application, what do I need to do to set that as the authentication used by the Firestore client? For example, I just have these basic rules enabled:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
match /users/{userId}/{document=**} {
allow read, update, delete, create: if request.auth.uid == userId;
}
match /ingredients/* {
allow read, create: if request.auth.uid != null;
}
match /units/* {
allow read, create: if request.auth.uid != null;
}
match /recipes/* {
allow read, create, update: if request.auth.uid != null;
}
}
}
As soon as I enabled those rules, every request from my Flutter app started failing. If I test the Firestore rules with the little "simulator" they have, they work as expected, so the authentication does not appear to be getting set correctly from the Flutter app side.
EDIT: Adding some code samples.
I have authentication code that uses Google Auth, so when the user logs in it looks like this:
class Auth implements AuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
Future<String> signInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final FirebaseUser user = await _firebaseAuth.signInWithCredential(credential);
return user.uid;
}
I've verified that the user is being authenticated properly.
Then, when accessing Firestore, something like:
DocumentSnapshot userSnapshot = await Firestore.instance
.collection('users')
.document(userId)
.collection('shoppingLists')
.document(listName)
.get();
I've followed all of the guides to add Firebase and Firestore to my app, and I didn't see anything specific about setting the currently authenticated user as the user that's making the Firestore requests, so I feel like I'm missing something there. Is there something I'm supposed to be doing when making the Firestore queries to pass in the current user?
The answer is to not user Firestore.instance as this gives you an instance disconnected from your Firebase app;
instead use create you Firebase app providing all the keys, then authenticate against that app, and then create a Firestore instance against that app. There's no way to explicitly pass the user, but the code internally uses some logic to figure out user that's been authenticated against your created app. Here's the code I used:
Future<void> _authenticate() async {
FirebaseApp _app = await FirebaseApp.configure(
name: 'some-name',
options: FirebaseOptions(
googleAppID: 'some:id',
gcmSenderID: 'sender',
apiKey: 'api-key-goes-here',
projectID: 'some-id',
),
);
final _auth = FirebaseAuth.fromApp(_app);
final _result = await _auth.signInAnonymously();
setState(() {
app = _app;
auth = _auth;
user = _result.user;
store = Firestore(app: app); //this is the code that does the magic!
});
}
as for the serverside rule configuration - refer to #cloudwalker's answer
For me, it wound up being an issue with how I had my firestore rules set up. This is what I have now, and it works well:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow create: if isAuthenticated();
}
match /users/{userId} {
allow read, update: if isOwner(userId);
}
match /users/{userId}/{document=**} {
allow read, write: if isOwner(userId);
}
match /categories/{document=**} {
allow read: if isAuthenticated();
}
match /config/{document=**} {
allow read: if isAuthenticated();
}
}
function isAuthenticated() {
return request.auth.uid != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
}
The solution is to initialize FirebaseStorage after getting authenticated user
Future<Null> main() async {
WidgetsFlutterBinding.ensureInitialized();
AppInfo.app = await Firebase.initializeApp(options: AppInfo.firebaseOptions);
}
It's easier if you have a listener
FirebaseAuth.instance.authStateChanges().listen((authUser) async {
if (authUser != null && AppInfo.authUser == null) {
AppInfo.authUser = authUser;
print("Got Firebase user: " + authUser.uid);
setState(() {
if (AppInfo.instance == null) {
AppInfo.instance =
FirebaseStorage(app: AppInfo.app, storageBucket: BUCKETGS);
AppInfo.instanceTemp =
FirebaseStorage(app: AppInfo.app, storageBucket: BUCKETTEMP);
}
AppInfo.userAuthId = authUser.uid;
streamsStartVerify();
});
} else if (authUser == null && AppInfo.authUser != null) {
print("sign out!!!");
AppInfo.authUser = null;
AppInfo.userAuthId = "";
}
});
}
Related
I have this following database in Firestore
The security rule of the database is the following:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{users} {
allow read: if request.auth != null && request.auth.uid == userId;
}
}
}
I can not register into the database, because of the security rules.
The flutter code is the following:
try {
final newUser =
await _auth.createUserWithEmailAndPassword(
DataBaseService(uid: newUser.user?.uid)
.addUserData(
userName: userName,
);
widget.registered();
nav.pop();
} on FirebaseAuthException catch (e) {
setState(() {
_hideSpinner = true;
});
What should be the security rule to let the user to register?
Just add allow write: if request.auth != null under allow read line.
I am trying to login with google and have the data mapped to a firebase user. I'm using getX. So far this works HOWEVER it automatically logs me back in as the same user if I logout and then try to log back in again. I'll send the code for my login page and the page where the logout button is if needed, but I suspect this may have to do with my AuthController which I've included here
class AuthController extends GetxController {
static AuthController instance = Get.find();
GoogleSignIn googleSignIn = GoogleSignIn();
Rxn<User> firebaseUser = Rxn<User>();
Rxn<UserModel> firestoreUser = Rxn<UserModel>();
final RxBool admin = false.obs;
String usersCollection = "users";
#override
void onReady() async {
//run every time auth state changes
ever(firebaseUser, handleAuthChanged);
firebaseUser.bindStream(user);
super.onReady();
}
handleAuthChanged(firebaseUser) async {
//get user data from firestore
if (firebaseUser?.uid != null) {
firestoreUser.bindStream(streamFirestoreUser());
print("You are logged in as ${firebaseUser.email}");
await isAdmin();
}
//this is for new users
if (firebaseUser == null) {
print('Send to signin');
Get.offAll(LoginPage());
} else {
Get.offAll(AppSetup());
}
}
// Firebase user one-time fetch
Future<User> get getUser async => auth.currentUser!;
// Firebase user a realtime stream
Stream<User?> get user => auth.authStateChanges();
//Streams the firestore user from the firestore collection
Stream<UserModel> streamFirestoreUser() {
print('streamFirestoreUser()');
return firebaseFirestore
.doc('/users/${firebaseUser.value!.uid}')
.snapshots()
.map((snapshot) => UserModel.fromSnapshot(snapshot));
}
//get the firestore user from the firestore collection
Future<UserModel> getFirestoreUser() {
return firebaseFirestore
.doc('/users/${firebaseUser.value!.uid}')
.get()
.then((documentSnapshot) => UserModel.fromSnapshot(documentSnapshot));
}
//Method to handle user sign in using email and password
// User registration using email and password
googleLogin(BuildContext context) async {
final GoogleSignInAccount? googleUser = await googleSignIn.signIn();
if (googleUser != null) {
final googleAuth = await googleUser.authentication;
if (googleAuth.accessToken != null && googleAuth.idToken != null) {
try {
await auth
.signInWithCredential(
GoogleAuthProvider.credential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken),
)
.then((firebaseUser) async {
print('uid: ' + firebaseUser.user!.uid.toString());
print('email: ' + firebaseUser.user!.email.toString());
//create the new user object from the login modelled data
UserModel _newUser = UserModel(
id: firebaseUser.user!.uid,
email: firebaseUser.user!.email!,
name: firebaseUser.user!.email!,
photoURL: firebaseUser.user!.photoURL,
cart: [],
);
//create the user in firestore here with the _addUserToFirestore function
_updateUserFirestore(_newUser, firebaseUser.user!);
});
} on FirebaseAuthException catch (error) {
Get.snackbar('auth.signUpErrorTitle'.tr, error.message!,
snackPosition: SnackPosition.BOTTOM,
duration: Duration(seconds: 10),
backgroundColor: Get.theme.snackBarTheme.backgroundColor,
colorText: Get.theme.snackBarTheme.actionTextColor);
}
}
}
}
void _updateUserFirestore(UserModel user, User _firebaseUser) {
firebaseFirestore.doc('/users/${_firebaseUser.uid}').update(user.toJson());
update();
}
updateUserData(Map<String, dynamic> data) {
logger.i("UPDATED");
firebaseFirestore
.collection(usersCollection)
.doc(firebaseUser.value!.uid)
.update(data);
}
//check if user is an admin user
isAdmin() async {
await getUser.then((user) async {
DocumentSnapshot adminRef =
await firebaseFirestore.collection('admin').doc(user.uid).get();
if (adminRef.exists) {
admin.value = true;
} else {
admin.value = false;
}
update();
});
}
// This is the proper sign out method!
Future<void> signOut() {
return auth.signOut();
}
}
Simply add this line of code into your logout function
> await googleSignIn.signOut()
I am stuck at the adding an authenticated user to a firestore 'users' collection.
Unhandled Exception: [cloud_firestore/not-found] Some requested document
was not found.
User signs in via Google:
class GoogleSignInProvider extends ChangeNotifier {
final GoogleSignIn _googleSignIn = GoogleSignIn();
GoogleSignInAccount _user;
GoogleSignInAccount get user => _user;
AuthService auth = AuthService();
Future googleSignIn(BuildContext context) async {
try {
final googleUser = await _googleSignIn.signIn();
if (googleUser == null) return;
_user = googleUser;
final googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
await FirebaseAuth.instance.signInWithCredential(credential);
} catch (e) {
print(e.toString());
}
final User currentUser = FirebaseAuth.instance.currentUser;
if (currentUser != null)
usersRef.add(currentUser.uid);
Navigator.of(context).pushReplacement(
CupertinoPageRoute(builder: (_) => TabScreen()));
notifyListeners();
}
However, no matter what and how I tried the authentication firebase id is not added to the usersRef (the firestore collection). How do I fix it?
My firestore rules are:
match /users/{userId}/{documents=**} {
allow read: if request.auth.uid != null;
allow write, update, create, delete: if isOwner(userId);
}
function isOwner(userId) {
return request.auth.uid == userId;
}
Help appreciated very much!
so this solved my issue:
final User currentUser = FirebaseAuth.instance.currentUser;
if (currentUser != null)
await usersRef.doc(currentUser.uid).set({'email':
user.email, 'username': user.displayName, 'photoUrl':
user.photoURL});
I think, I was overthinking it..
Thanks, ppl for the directions and help!
Do this.
Map data = {};
FirebaseFirestore.instance
.collection('usersCollection')
.doc(currentUser.uid)
.set(data);
in place of
usersRef.add(currentUser.uid);
Use set when you have doc id and use add when you want an auto generated id.
The following documentation on deleting a user does not work:
try {
await FirebaseAuth.instance.currentUser.delete();
} catch on FirebaseAuthException (e) {
if (e.code == 'requires-recent-login') {
print('The user must reauthenticate before this operation can be executed.');
}
}
"delete()" is not a function recognized by Flutter. "FirebaseAuthException" is also not recognized by Flutter.
How do I delete a user? Where do I find this information?
Using flutter, if you want to delete firebase accounts together with the associated firestore user collection document, the following method works fine. (documents in user collection named by the firebase uid).
Database Class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection =
Firestore.instance.collection('users');
Future deleteuser() {
return userCollection.document(uid).delete();
}
}
Use Firebase version 0.15.0 or above otherwise, Firebase reauthenticateWithCredential() method throw an error like { noSuchMethod: was called on null }.
Authentication Class
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future deleteUser(String email, String password) async {
try {
FirebaseUser user = await _auth.currentUser();
AuthCredential credentials =
EmailAuthProvider.getCredential(email: email, password: password);
print(user);
AuthResult result = await user.reauthenticateWithCredential(credentials);
await DatabaseService(uid: result.user.uid)
.deleteuser(); // called from database class
await result.user.delete();
return true;
} catch (e) {
print(e.toString());
return null;
}
}
}
Then use the following code inside the clickable event of a flutter widget tree to achieve the goal:
onTap: () async {
await AuthService().deleteUser(email, password);
}
I'm learning Flutter and as an exercise I'm interfacing with Firebase (I'm also new to this - my first encounter).
In the exercise when we register a new user we create a document for the newly registered user with default values.
A new user is getting registered but neither collection nor document is getting created.
I had created the Cloud Firestore is Test Mode. I've attached the rules bellow.
Rules :
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if
request.time < timestamp.date(2021, 3, 4);
}
}
}
auth.dart
//Resgister using email and password
Future registerWithEmailPassword(
{#required String email, #required String password}) async {
try {
Firebase.UserCredential resut = await _auth
.createUserWithEmailAndPassword(email: email, password: password);
Firebase.User user = resut.user;
//create a new document for user with uid
await DatabaseService(uid: user.uid).updateUserData(
sugars: '0',
name: 'new crew member',
strength: 100,
);
return _userFromFirebase(user);
} catch (e) {
//print(e.toString());
return e.toString();
}
}
database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseService { final String uid; DatabaseService({this.uid});
//colection reference
final CollectionReference brewCollection =
FirebaseFirestore.instance.collection('brews');
Future updateUserData({String sugars, String name, int strength}) async {
return await brewCollection
.doc(uid)
.set({'sugars': sugars, 'name': name, 'strength': strength}); } }
}
I added a break-point and checked the data in updateUserData, the data is proper.
Their was an issue with the Firebase plugin.
MissingPluginException(No implementation found for method DocumentReference#set on channel plugins.flutter.io/firebase_firestore)
I updated by pubspec.yaml to have the latest version file then run the following commands
flutter clean
flutter packages get