Issue
I have been able to get Firebase Authentication to work for Google sign in, Anonymous sign in and from Email and Password sign in, including sending a verification email during email and password sign in thanks to help on stackoverflow. Everything works as intended. Now for my next step I am trying to create a user collection in Firestore using the uid created by Firebase Authentication. I am confident my code is written correctly because I have tested it with (unsecure) Security Rules and the process worked exactly as desired. I have reviewed the Firebase documentation several times but I cannot figure out what is wrong with my Security Rules code. How can I fix my Security rules to allow a new user to create a Screen name that will be added to the user collection in Firestore? Thanks in advance for the help.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid}/jobs/{document=**} {
allow read, write: if request.auth.uid == uid;
}
match /users/{uid}/{document=**} {
allow read, write: if request.auth.uid == uid;
}
}
}
class HomePage extends StatefulWidget {
const HomePage({
Key? key,
}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
createUserInFirestore();
}
Future<void> createUserInFirestore() async {
final GoogleSignIn googleSignIn = GoogleSignIn();
final GoogleSignInAccount? user = googleSignIn.currentUser;
final usersRef = FirebaseFirestore.instance.collection('users');
final DocumentSnapshot doc = await usersRef.doc(user?.id).get();
if (!doc.exists) {
final userName = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CreateAccountPage(),
),
);
usersRef.doc(user?.id).set({
'id': user?.id,
'userName': userName,
'photoUrl': user?.photoUrl,
'email': user?.email,
'displayName': user?.displayName,
'bio': '',
'timestamp': documentIdFromCurrentDate(),
});
doc = await usersRef.doc(user?.id).get();
}
}
@override
Widget build(BuildContext context) {
return AdaptiveLayoutScaffold(
drawer: const SideSheet(
userImage: FakeUserAvatars.stacy,
userName: 'Stacy James',
),
landscapeBodyWidget: Container(),
portraitBodyWidget: Container(),
);
}
}
class CreateAccountPage extends StatefulWidget {
const CreateAccountPage({
Key? key,
}) : super(key: key);
@override
_CreateAccountPageState createState() => _CreateAccountPageState();
}
class _CreateAccountPageState extends State<CreateAccountPage> {
final _formKey = GlobalKey<FormState>();
late String userName;
void submit() {
_formKey.currentState?.save();
Navigator.pop(context, userName);
}
@override
Widget build(BuildContext context) {
return AdaptiveLayoutScaffold(
appBar: const Header(
automaticallyImplyLeading: false,
pageName: 'User Name',
),
landscapeBodyWidget: Container(),
portraitBodyWidget: ListView(
children: [
Column(
children: [
const Padding(
padding: EdgeInsets.only(top: 16.0),
child: Center(
child: Text('Create a User Name'),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: TextFormField(
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(
hintText: 'Must be between 3 and 20 characters',
labelText: 'User Name',
prefixIcon: Icon(
Icons.person,
color: Theme.of(context).iconTheme.color,
),
),
keyboardType: TextInputType.text,
onSaved: (val) => userName = val as String,
),
),
),
PlatformElevatedButton(
onPressed: submit,
buttonText: 'Create User Name',
),
],
),
],
),
);
}
}
Solution
After reading what has been suggested and a few other things I used the Firestore Rules Playground to fix my code and then updated my Auth class to include a new method called createUserInFirestore() to handle the creation of a new user in Firestore using the uid after the user is created by Firebase Authentication.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid}/{document=**} {
allow read, create, update, delete: if request.auth.uid == uid;
}
}
}
abstract class AuthBase {
User? get currentUser;
Stream<User?> authStateChanges();
Future<User?> signInWithGoogle();
Future<User?> createUserWithEmailAndPassword(
String email,
String password,
);
Future<void> checkEmailVerified(BuildContext context, Timer timer);
Future<User?> signInWithEmailAndPassword(String email, String password);
Future<User?> signInAnonymously();
Future<void> resetPassword(BuildContext context, String email);
Future<void> confirmSignOut(BuildContext context);
Future<void> signOut();
}
class Auth implements AuthBase {
final _firebaseAuth = FirebaseAuth.instance;
@override
User? get currentUser => _firebaseAuth.currentUser;
@override
Stream<User?> authStateChanges() => _firebaseAuth.authStateChanges();
void _createNewUserInFirestore() {
final User? user = currentUser;
final CollectionReference<Map<String, dynamic>> usersRef =
FirebaseFirestore.instance.collection('users');
usersRef.doc(user?.uid).set({
'id': user?.uid,
'screenName': '',
'displayName': user?.displayName,
'photoUrl': user?.photoURL,
'bio': '',
'darkMode': false,
'timestamp': documentIdFromCurrentDate(),
});
}
@override
Future<User?> signInWithGoogle() async {
final GoogleSignIn googleSignIn = GoogleSignIn();
final GoogleSignInAccount? googleUser = await googleSignIn.signIn();
if (googleUser != null) {
final googleAuth = await googleUser.authentication;
if (googleAuth.idToken != null) {
final UserCredential userCredential =
await _firebaseAuth.signInWithCredential(
GoogleAuthProvider.credential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken,
),
);
_createNewUserInFirestore();
return userCredential.user;
} else {
throw FirebaseAuthException(
code: FirebaseExceptionString.missingGoogleIDTokenCode,
message: FirebaseExceptionString.missingGoogleIDTokenMessage,
);
}
} else {
throw FirebaseAuthException(
code: FirebaseExceptionString.abortedByUserCode,
message: FirebaseExceptionString.canceledByUserMessage,
);
}
}
@override
Future<User?> createUserWithEmailAndPassword(
String email,
String password,
) async {
final UserCredential userCredential =
await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
_createNewUserInFirestore();
return userCredential.user;
}
@override
Future<void> checkEmailVerified(BuildContext context, Timer timer) async {
final User? user = currentUser;
await user?.reload();
final User? signedInUser = user;
if (signedInUser != null && signedInUser.emailVerified) {
timer.cancel();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
}
}
@override
Future<User?> signInWithEmailAndPassword(
String email,
String password,
) async {
final UserCredential userCredential =
await _firebaseAuth.signInWithCredential(
EmailAuthProvider.credential(
email: email,
password: password,
),
);
return userCredential.user;
}
@override
Future<void> resetPassword(
BuildContext context,
String email,
) async {
try {
await _firebaseAuth.sendPasswordResetEmail(email: email);
Navigator.of(context).pop();
} catch (e) {
print(
e.toString(),
);
}
}
@override
Future<User?> signInAnonymously() async {
final UserCredential userCredential =
await _firebaseAuth.signInAnonymously();
return userCredential.user;
}
Future<void> _signOut(BuildContext context) async {
try {
final AuthBase auth = Provider.of<AuthBase>(
context,
listen: false,
);
await auth.signOut();
Navigator.pushAndRemoveUntil<dynamic>(
context,
MaterialPageRoute<dynamic>(
builder: (BuildContext context) => const LandingPage(),
),
(route) => false,
);
} catch (e) {
print(
e.toString(),
);
}
}
@override
Future<void> confirmSignOut(BuildContext context) async {
final bool? didRequestSignOut = await showAlertDialog(
context,
cancelActionText: DialogString.cancel,
content: DialogString.signOutAccount,
defaultActionText: DialogString.signOut,
title: DialogString.signOut,
);
if (didRequestSignOut == true) {
_signOut(context);
}
}
@override
Future<void> signOut() async {
final GoogleSignIn googleSignIn = GoogleSignIn();
await googleSignIn.signOut();
await _firebaseAuth.signOut();
}
}
Answered By - Carleton Y
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.