Uploading Image to FirebaseStorage - Permission Denied

337

The match /user-images in your rules matches a file in the root names user-images. It does not recursively apply the permissions to files in a filter.

To allow anyone to write files under /user-images, you can apply a wildcard match:

match /user-images/{file} {
  allow read, create: if request.auth != null;
}

Or if you want to allow writing in any folder under /user-images:

match /user-images/{file=**} {
  allow read, create: if request.auth != null;
}

To then allow everyone to read /user-images/avatar.png:

match /user-images/avatar.png {
    allow read: if true;
}
Share:
337
Hey Soo Kim
Author by

Hey Soo Kim

Updated on January 01, 2023

Comments

  • Hey Soo Kim
    Hey Soo Kim over 1 year

    I'm working on authentication/signing up with FirebaseStorage where the user can upload an image during sign-up but I keep on getting errors about the user not having any permission. When the user clicks sign-up, the sign-up process using Firebase works because the user is created in Authentication but the information does not get saved on the Firestore Database. Does anyone know what I'm doing wrong?

    This is my Firebase Storage rule:

    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /user-images {
          allow read, create: if request.auth != null;
        }
        match /avatar.png {
            allow read: if true;
        }
      }
    }
    

    This is the sign-up screen.

    import 'dart:io';
    import 'dart:ui';
    
    import 'package:firebase_storage/firebase_storage.dart';
    import 'package:flutter/material.dart';
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:flutter/services.dart';
    import 'package:cloud_firestore/cloud_firestore.dart';
    
    import '/screens/auth_screen.dart';
    import '/widgets/build_row.dart';
    import '/screens/chat_screen.dart';
    import '/widgets/user_image_picker.dart';
    
    class SignUpScreen extends StatefulWidget {
      static const routeName = '/sign-up-screen';
    
      @override
      _SignUpScreenState createState() => _SignUpScreenState();
    }
    
    class _SignUpScreenState extends State<SignUpScreen> {
      final _auth = FirebaseAuth.instance;
    
      final GlobalKey<FormState> _key = GlobalKey();
      String _username = '';
      String _userPW = '';
      String _userEmail = '';
    
      var _isLoading = false;
    
      File? _userImageFile;
      void _pickedImage(File image) {
        _userImageFile = image;
      }
    
      void _submit() {
        final isValid = _key.currentState!.validate();
        if (isValid) {
          _key.currentState!.save();
        }
        this._submitAuthForm(_userEmail.trim(), _userPW.trim(), _username.trim());
      }
    
      void _submitAuthForm(
        String userEmail,
        String password,
        String username,
      ) async {
        try {
          setState(() {
            _isLoading = true;
          });
    
          UserCredential userCredential = await _auth
              .createUserWithEmailAndPassword(email: userEmail, password: password);
    
          final ref = FirebaseStorage.instance
              .ref()
              .child('user-images/${userCredential.user!.uid}.jpg');
    
          final avatarRef = FirebaseStorage.instance.ref().child('avatar.png');
    
          var url;
          if (_userImageFile == null) {
            url = await avatarRef.getDownloadURL();
          }
          if (_userImageFile != null) {
            await ref.putFile(_userImageFile!);
            url = await ref.getDownloadURL();
          }
    
          await FirebaseFirestore.instance
              .collection('users')
              .doc(userCredential.user!.uid)
              .set(
            {
              'username': username,
              'email': userEmail,
              'image_url': url,
            },
          );
          Navigator.of(context).pushReplacement(
            MaterialPageRoute(builder: (ctx) => ChatScreen()),
          );
        } on PlatformException catch (error) {
          var message = 'An Error has occured!';
          if (error.message != null) {
            message = error.message!;
          }
          setState(() {
            _isLoading = false;
          });
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(message),
              backgroundColor: Colors.deepPurple.shade600,
            ),
          );
        } catch (error) {
          if (error.toString().contains(
              'The email address is already in use by another account.')) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content:
                    Text('The email address is already in use by another account.'),
                backgroundColor: Colors.deepPurple.shade600,
              ),
            );
          }
          print(error);
    
          setState(() {
            _isLoading = false;
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        final deviceSize = MediaQuery.of(context).size;
    
        return GestureDetector(
          onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
          child: Scaffold(
            backgroundColor: Theme.of(context).primaryColor,
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    width: deviceSize.width * 0.8,
                    constraints: BoxConstraints(
                        maxHeight: (deviceSize.height -
                                MediaQuery.of(context).viewInsets.bottom) *
                            0.7),
                    decoration: BoxDecoration(
                      border: Border.all(
                        color: Colors.deepPurple.shade600,
                        width: 2.0,
                      ),
                      color: Colors.white,
                    ),
                    child: SingleChildScrollView(
                      padding: EdgeInsets.only(
                        left: 10,
                        bottom: 20,
                      ),
                      child: Form(
                        key: _key,
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
    
                          children: [
                            UserImagePicker(_pickedImage),
                            BuildRowWithIcon(Icons.person, 'Username:', false,
                                (value) {
                              if (value.isEmpty) {
                                return 'Please enter a username';
                              }
                            
                              return null;
                            }, (value) {
                              _username = value;
                            }, 'Username must be at least 6 characters long. Special characters: \$, ^, &, !, ?'),
                            BuildRow(
                              Icons.email_outlined,
                              'E-mail:',
                              false,
                              (value) {
                                if (value.isEmpty) {
                                  return 'Please provide an e-mail';
                                }
                                return null;
                              },
                              (value) {
                                _userEmail = value;
                              },
                            ),
                            BuildRowWithIcon(
                                Icons.password_outlined, 'Password:', true,
                                (value) {
                              if (value.isEmpty) {
                                return 'Please enter a password';
                              }
                              if (value.length < 7) {
                                return 'Password must be at least 7 characters long';
                              }
                              return null;
                            }, (value) {
                              _userPW = value;
                            }, 'Password must be at least 7 characters long. You must use a combination of two of the following: letters, numbers, & special characters. Special characters: \$, ^, &, !, ?'),
                            BuildRowWithIcon(
                              Icons.password_outlined,
                              'Confirm PW:',
                              true,
                              (value) {
                                if (value.isEmpty) {
                                  return 'Please enter the same password';
                                }
                                return null;
                              },
                              null,
                              'Confirm password',
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(height: 30),
                  if (_isLoading) CircularProgressIndicator(),
                  if (!_isLoading)
                    ElevatedButton(
                      onPressed: _submit,
                      child: const Text(
                        'Sign up!',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      style: ButtonStyle(
                        elevation: MaterialStateProperty.all<double>(20),
                        backgroundColor: MaterialStateProperty.all<Color>(
                            Colors.deepPurple.shade600),
                        foregroundColor: MaterialStateProperty.all<Color>(
                            Colors.tealAccent.shade200),
                        padding: MaterialStateProperty.all<EdgeInsets>(
                            EdgeInsets.all(15)),
                        shadowColor: MaterialStateProperty.all<Color>(
                            Colors.deepPurple.shade800),
                        shape: MaterialStateProperty.all<OutlinedBorder>(
                            RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(15),
                        )),
                      ),
                    ),
                  SizedBox(height: 25),
                  TextButton(
                    onPressed: () => Navigator.of(context)
                        .pushReplacementNamed(AuthScreen.routeName),
                    child: Text(
                      'I already have an account.',
                      style: TextStyle(
                        color: Colors.deepPurple.shade400,
                        fontSize: 15,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    And this is for the ImagePicker() widget

    import 'package:flutter/material.dart';
    import 'package:image_picker/image_picker.dart';
    import 'dart:io';
    import 'package:path/path.dart' as path;
    
    import 'package:path_provider/path_provider.dart' as syspath;
    
    class UserImagePicker extends StatefulWidget {
      final void Function(File pickedImage) imagePickFn;
    
      UserImagePicker(this.imagePickFn);
    
      @override
      _UserImagePickerState createState() => _UserImagePickerState();
    }
    
    class _UserImagePickerState extends State<UserImagePicker> {
      File? _pickedImage;
    
      Future<void> _pickImage() async {
        final _pickedImageXFile =
            await ImagePicker().pickImage(
                source: ImageSource.gallery,
                imageQuality: 50,
                maxHeight: 150,
                maxWidth: 150);
        if (_pickedImageXFile == null) {
          return;
        }
        final _pickedImageFile = File(_pickedImageXFile.path);
        setState(() {
          _pickedImage = _pickedImageFile;
        });
        final appDir = await syspath.getApplicationDocumentsDirectory();
        final fileName = path.basename(_pickedImageFile.path);
        final userImageFile =
            await _pickedImageFile.copy('${appDir.path}/$fileName');
        widget.imagePickFn(userImageFile);
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            Container(
              margin: EdgeInsets.only(top: 10),
              child: CircleAvatar(
                radius: 35,
                backgroundColor: Colors.tealAccent.shade200,
                backgroundImage:
                    _pickedImage == null ? null : FileImage(_pickedImage!),
              ),
            ),
            TextButton.icon(
              onPressed: _pickImage,
              icon: Icon(
                Icons.image_outlined,
                color: Colors.deepPurple.shade600,
              ),
              label: Text(
                'Add User Image',
                style: TextStyle(color: Colors.deepPurple.shade600),
              ),
            ),
          ],
        );
      }
    }
    

    Side question: Is there a way to use Future with validation? I want to be able to show the user while he/she is typing in password/confirm password that it doesn't match the requirements or that the confirm password value doesn't the password value. Do I need to use separate TextControllers for this?

  • Hey Soo Kim
    Hey Soo Kim over 2 years
    This solved it so quickly! And to think I spent loads of time tweaking the rules after "allow" and not the "match" portion...thank you so much!!