Search code examples
fluttergoogle-cloud-firestorefirebase-authenticationfirebase-security

How to authenticate Firestore using signInWithCustomToken() (Nodejs firebase token)


I have these Firestore rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isValidUser(userId) {
      return request.auth.uid == userId && request.auth.role == 'user';
    }
    function isLoggedIn() {
      return request.auth.uid != null;
    }
    match /users/{id}/{document=**}{
      allow create, read, update, delete: if isLoggedIn() && isValidUser(id)
    }
  }
}

And I create a valid Firebase token via a Google cloud function and send it to whoever logs in successfully, like this

const firebaseToken = await admin.auth().createCustomToken(uuid);
return res.status(200).json({status: true, token: firebaseToken, id: uuid});

Now we go to Flutter, in main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  if (Firebase.apps.isEmpty) {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );
  }
  Instance.registerTypes(onRegister: (getIt) {
    getIt.registerLazySingleton(() => BusinessDevice());
  });

  runApp(
    Phoenix(
      child: ProviderScope(
        child: MyApp(),
      ),
    ),
  );
}

And in login.dart

final headers = {'Content-Type': 'application/json'};
final request = http.Request(
'POST', Uri.parse(ENDPOINTS.FIREBASE_AUTH));
request.body = json.encode({
"email": userCtrl.text.trim(),
"password": passCtrl.text.trim(),
"role": "user"
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
final body = await response.stream.bytesToString();
final jsonResponse = json.decode(body);

if (response.statusCode == 200) {
// Authentication succeeded, store the token and navigate to the dashboard
final token = jsonResponse['token'];
final userid = jsonResponse['id'];
await FirebaseAuth.instance.signInWithCustomToken(token);
 DocumentSnapshot<Map<String, dynamic>> snap = await FirebaseFirestore.instance
  .collection('users').doc(userid).get();
}

I'm getting this error when the application tries to read from Firestore

FirebaseException ([cloud_firestore/permission-denied] The caller does not have permission to execute the specified operation.)

We're using signInWithCustomToken(), because we have a username and password authentication system, which isn't supported out of the box by Firebase so I had to write a Google cloud function to handle this.

I'm looking for a solution that will require the fewest changes in the codebase. Because we are facing the same issue in 3 different big apps in production and Firestore is used on all the pages. I want to work on securing the apps and not refactoring them.


Solution

  • It looks like your role is a custom claim, in which case it exists in the token property in your rules. So:

    return request.auth.uid == userId && request.auth.token.role == 'user'
                                                  //    👆
    

    Also see: