Home > other >  request.auth.uid is not working in firestore security rule
request.auth.uid is not working in firestore security rule

Time:02-22

I have firestore DB like below, uid field contains, uid of the authenticated user who has added the document

enter image description here

security role:

rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents {
    match /crush/{doc} {
      allow read: if request.auth != null && request.auth.uid == resource.data.uid;
      allow write : if request.auth != null;
      }
    }
}

mocking read rule works enter image description here

however, in the actual flutter App, only DB write is working, read fails with below error

/Firestore( 6538): (24.0.1) [Firestore]: Listen for Query(target=Query(crush order by name);limitType=LIMIT_TO_FIRST) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}

which makes me believe request.auth.uid is blank for incoming read

the program looks something like below

# landing.dart
# Authentication class is helper for FirebaseAuth.instance 
import 'home.dart';

class Landing extends StatefulWidget {
  const Landing({Key? key}) : super(key: key);

  @override
  State<Landing> createState() => _LandingState();
}

class _LandingState extends State<Landing> {
  dynamic authStream = Authentication().authState;
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: authStream,
        builder: (BuildContext context, AsyncSnapshot<AppUser?> snapshot) {
          if (snapshot.data != null) {
            return Home(snapshot.data);
          }
          else {
            return SignIn(); //via google
          }
        });
  }
}

# home.dart
# Database class is helper for FirebaseFirestore.instance
import 'package:flutter/material.dart';
import 'package:password_safe/models/crush.dart';
import 'package:password_safe/models/user.dart';
import 'package:password_safe/services/authentication.dart';
import 'package:password_safe/services/database.dart';

class Home extends StatefulWidget {
  AppUser? user;

  Home(this.user, {Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  dynamic streamCrush = Database().getCrushSnapshot;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          provideInput(context, widget.user);
        },
        child: Icon(Icons.add),
      ),
      body: StreamBuilder(
        stream: streamCrush,
        builder: (BuildContext context, AsyncSnapshot<List<Crush>> snapshot) {
          if (snapshot.hasError) {
            return Center(child: Text('Error Ocurred'));
          }
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          }
          return myList(snapshot.data!);
        },
      ),
    );
  }
}

\\ database.dart
import 'dart:developer' as developer;

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:password_safe/models/crush.dart';

class Database {
  late FirebaseFirestore _db;
  late CollectionReference crushCollection;
  final String _logName = "Database";
  static final Database _instance = Database._internal();

  Database._internal() {
    try {
      _db = FirebaseFirestore.instance;
      crushCollection = _db.collection('crush');
    } catch (e) {
      // do something
    }
  }

  factory Database() {
    return _instance;
  }

  Future<void> addCrush(Crush crush) async {
    await crushCollection
        .add(crush.toMap())
        .then(
          (value) => developer.log('adding crush to backend', name: _logName),
        )
        .catchError(
          (error) => developer.log('Error while adding crush',
              name: _logName, error: error),
        );
  }

  Stream<List<Crush>> get getCrushSnapshot {
    return crushCollection
        .snapshots()
        .map((event) => _crushListFromSnapshot(event));
  }

  List<Crush> _crushListFromSnapshot(QuerySnapshot snapshot) {
    return snapshot.docs.map((e) {
      Map<String, dynamic> data = e.data() as Map<String, dynamic>;
      data.addAll({'id': e.id});
      return Crush.fromMap(data);
    }).toList();
  }

  Future<void> deleteCrush(Crush crush) {
    return crushCollection
        .doc(crush.id)
        .delete()
        .then((value) =>
            developer.log('deleting crush from backend', name: _logName))
        .onError((error, stackTrace) => developer.log(
            'error occured while deleting data from crush',
            name: _logName,
            error: error,
            stackTrace: stackTrace));
  }
}


pubspec.yaml

dependencies:
  cloud_firestore:
  google_sign_in: '^4.5.1'
  firebase_auth:
  firebase_core:
  flutter:
    sdk: flutter

CodePudding user response:

Your Query constraints do not match security rules constraints. You are trying to get /crush/* (as per in your database query) but you only have access to /crush/{doc} as per your Firestore security rule.

The query fails even if the current user actually is the author of every document. The reason for this behavior is that when Cloud Firestore applies your security rules, it evaluates the query against its potential result set, not against the actual properties of documents in your database. If a query could potentially include documents that violate your security rules, the query will fail.

So you need to filter your database query to match the uid field of the document with auth.uid. Something like this :

var user = FirebaseAuth.instance.currentUser;
db.collection("crush").where(“uid”, "==", user.uid).get()

You can read more about securing and querying documents based on auth.id here.

  • Related