Flutter
Firestore Security Rules Dasar
Panduan dasar Cloud Firestore Security Rules untuk Flutter: auth.uid, read, create, update, delete, validasi field, owner data, dan testing rules.
Firestore Security Rules Dasar
Materi ini adalah lanjutan dari Task Manager Online per User.
Di materi sebelumnya kita sudah membuat task yang disimpan berdasarkan userId. Sekarang kita akan belajar cara melindungi data tersebut dengan Cloud Firestore Security Rules.
Security Rules penting karena aplikasi client seperti Flutter tidak boleh dipercaya sepenuhnya. Walaupun UI kita hanya menampilkan task milik user login, user yang paham teknis tetap bisa mencoba mengakses data lain langsung ke Firestore. Di sinilah rules bekerja.
Target materi:
- memahami fungsi Security Rules
- memahami
request.auth - memahami
request.auth.uid - membatasi akses data per user
- membedakan
resource.datadanrequest.resource.data - membuat rules untuk create, read, update, delete
- validasi field sederhana
- memahami error
permission-denied - testing rules secara manual
1. Apa itu Firestore Security Rules?
Firestore Security Rules adalah aturan keamanan yang menentukan siapa boleh membaca atau menulis data di Cloud Firestore.
Contoh pertanyaan yang dijawab oleh rules:
- apakah user sudah login?
- apakah data ini milik user yang sedang login?
- apakah user boleh membuat data baru?
- apakah user boleh mengubah field tertentu?
- apakah user boleh menghapus document?
- apakah bentuk data yang dikirim valid?
Rules berjalan di sisi Firebase, bukan di aplikasi Flutter. Jadi walaupun seseorang memodifikasi aplikasi client, rules tetap menjadi penjaga terakhir di server.
2. Kenapa Rules Wajib?
Misalnya aplikasi Flutter punya query:
FirebaseFirestore.instance
.collection('tasks')
.where('userId', isEqualTo: uid)
.snapshots();
Query ini sudah benar di sisi aplikasi karena hanya mengambil task milik user login.
Tetapi query di aplikasi saja belum cukup. Tanpa rules yang benar, orang lain bisa mencoba membaca collection tasks secara langsung.
Jadi kita butuh dua lapis:
- Flutter query membatasi data yang diminta
- Firestore Security Rules membatasi data yang diizinkan
Keduanya harus sejalan.
3. Rules Bukan Filter
Ini konsep yang sangat penting.
Security Rules bukan filter data otomatis.
Artinya, kalau rules hanya mengizinkan user membaca task miliknya, query Flutter juga harus meminta task miliknya.
Contoh rules:
allow read: if request.auth != null
&& resource.data.userId == request.auth.uid;
Query yang benar:
FirebaseFirestore.instance
.collection('tasks')
.where('userId', isEqualTo: uid)
.get();
Query yang salah:
FirebaseFirestore.instance
.collection('tasks')
.get();
Walaupun nanti aplikasi hanya ingin menampilkan data user tertentu, Firestore akan menolak query kedua karena query itu berpotensi membaca data semua user.
Jadi ingat:
Rules mengecek apakah sebuah request boleh dilakukan, bukan menyaring hasil query untuk kita.
4. Struktur Rules Dasar
Contoh struktur dasar:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{taskId} {
allow read, write: if false;
}
}
}
Penjelasan:
rules_version = '2'memakai versi rules terbaruservice cloud.firestoreberarti aturan untuk Cloud Firestorematch /databases/{database}/documentsadalah root document databasematch /tasks/{taskId}berarti aturan berlaku untuk collectiontasksallow read, write: if falseberarti semua akses ditolak
Rules paling aman adalah menolak semua dulu, lalu buka akses sedikit demi sedikit sesuai kebutuhan.
5. Mengenal request.auth
request.auth berisi informasi user yang sedang login.
Kalau user belum login:
request.auth == null
Kalau user sudah login:
request.auth != null
UID user login bisa dibaca lewat:
request.auth.uid
Contoh rules hanya user login yang boleh membaca:
allow read: if request.auth != null;
Rules ini sudah lebih aman daripada public access, tetapi belum cukup untuk data pribadi. Semua user login masih bisa membaca data semua user.
6. Data Task yang Akan Diamankan
Kita gunakan struktur document seperti ini:
{
"title": "Belajar Firestore Rules",
"isDone": false,
"userId": "uid_user_login",
"createdAt": "server_timestamp",
"updatedAt": "server_timestamp"
}
Field paling penting adalah:
userId
Field ini menentukan siapa pemilik task.
7. Rules Read per User
Rules untuk membaca task milik sendiri:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{taskId} {
allow read: if request.auth != null
&& resource.data.userId == request.auth.uid;
}
}
}
Penjelasan:
request.auth != null: user wajib loginresource.data.userId:userIddari data yang sudah ada di Firestorerequest.auth.uid: uid user yang sedang login- akses hanya diberikan kalau keduanya sama
Dengan rules ini, user A tidak bisa membaca task milik user B.
8. Rules Create per User
Saat membuat document baru, data lama belum ada. Karena itu kita tidak bisa memakai resource.data.
Untuk data baru, gunakan:
request.resource.data
Rules create:
allow create: if request.auth != null
&& request.resource.data.userId == request.auth.uid;
Penjelasan:
- user wajib login
- data baru harus punya
userId userIddi data baru harus sama dengan uid user login
Ini mencegah user membuat task untuk user lain.
Contoh yang diizinkan:
{
"title": "Belajar Flutter",
"isDone": false,
"userId": "uid_user_login"
}
Contoh yang ditolak:
{
"title": "Task palsu",
"isDone": false,
"userId": "uid_user_lain"
}
9. Rules Update per User
Rules update perlu memastikan:
- user sudah login
- document lama memang milik user
- data baru tetap memakai
userIdyang sama
Contoh:
allow update: if request.auth != null
&& resource.data.userId == request.auth.uid
&& request.resource.data.userId == resource.data.userId;
Penjelasan:
resource.data.userId == request.auth.uidmemastikan document lama milik user loginrequest.resource.data.userId == resource.data.userIdmencegah user mengganti pemilik task
Tanpa pengecekan kedua, user bisa mencoba mengubah userId document.
10. Rules Delete per User
Untuk delete, cukup pastikan document yang akan dihapus adalah milik user login:
allow delete: if request.auth != null
&& resource.data.userId == request.auth.uid;
Delete tidak memakai request.resource.data karena setelah delete tidak ada data baru.
11. Rules CRUD Lengkap
Gabungan rules dasar:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{taskId} {
allow read: if request.auth != null
&& resource.data.userId == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.userId == request.auth.uid;
allow update: if request.auth != null
&& resource.data.userId == request.auth.uid
&& request.resource.data.userId == resource.data.userId;
allow delete: if request.auth != null
&& resource.data.userId == request.auth.uid;
}
}
}
Rules ini sudah cukup untuk tahap awal belajar aplikasi task per user.
12. Membuat Helper Function
Rules bisa dibuat lebih rapi dengan function.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{taskId} {
function isSignedIn() {
return request.auth != null;
}
function isOwner() {
return resource.data.userId == request.auth.uid;
}
function isCreatingOwnTask() {
return request.resource.data.userId == request.auth.uid;
}
function keepsSameOwner() {
return request.resource.data.userId == resource.data.userId;
}
allow read: if isSignedIn() && isOwner();
allow create: if isSignedIn() && isCreatingOwnTask();
allow update: if isSignedIn() && isOwner() && keepsSameOwner();
allow delete: if isSignedIn() && isOwner();
}
}
}
Kelebihan function:
- rules lebih mudah dibaca
- kondisi tidak terlalu panjang
- logic bisa dipakai ulang
- lebih mudah dikembangkan
13. Validasi Field
Rules juga bisa memvalidasi bentuk data.
Misalnya task hanya boleh punya field:
titleisDoneuserIdcreatedAtupdatedAt
Tambahkan function:
function hasOnlyAllowedFields() {
return request.resource.data.keys().hasOnly([
'title',
'isDone',
'userId',
'createdAt',
'updatedAt'
]);
}
Rules ini mencegah user mengirim field aneh seperti:
{
"role": "admin"
}
14. Validasi Tipe Data
Kita juga bisa memastikan tipe data benar.
function hasValidTypes() {
return request.resource.data.title is string
&& request.resource.data.isDone is bool
&& request.resource.data.userId is string;
}
Dengan ini:
titleharus stringisDoneharus booleanuserIdharus string
Contoh data yang ditolak:
{
"title": 123,
"isDone": "belum",
"userId": true
}
15. Validasi Panjang Title
Title sebaiknya tidak kosong dan tidak terlalu panjang.
function hasValidTitle() {
return request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 100;
}
Dengan rules ini:
- title kosong ditolak
- title lebih dari 100 karakter ditolak
Validasi ini tetap perlu dilakukan juga di Flutter. Rules adalah pengaman server, bukan pengganti validasi UI.
16. Rules Lengkap dengan Validasi
Contoh rules yang lebih lengkap:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{taskId} {
function isSignedIn() {
return request.auth != null;
}
function isOwner() {
return resource.data.userId == request.auth.uid;
}
function isCreatingOwnTask() {
return request.resource.data.userId == request.auth.uid;
}
function keepsSameOwner() {
return request.resource.data.userId == resource.data.userId;
}
function hasOnlyAllowedFields() {
return request.resource.data.keys().hasOnly([
'title',
'isDone',
'userId',
'createdAt',
'updatedAt'
]);
}
function hasValidTypes() {
return request.resource.data.title is string
&& request.resource.data.isDone is bool
&& request.resource.data.userId is string;
}
function hasValidTitle() {
return request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 100;
}
function hasValidTaskData() {
return hasOnlyAllowedFields()
&& hasValidTypes()
&& hasValidTitle();
}
allow read: if isSignedIn() && isOwner();
allow create: if isSignedIn()
&& isCreatingOwnTask()
&& hasValidTaskData();
allow update: if isSignedIn()
&& isOwner()
&& keepsSameOwner()
&& hasValidTaskData();
allow delete: if isSignedIn() && isOwner();
}
}
}
Ini adalah rules yang lebih cocok untuk aplikasi belajar yang mulai mendekati aplikasi nyata.
17. Query Flutter Harus Sesuai Rules
Kalau rules membaca data berdasarkan userId, query Flutter juga harus menyertakan userId.
Benar:
final uid = FirebaseAuth.instance.currentUser!.uid;
FirebaseFirestore.instance
.collection('tasks')
.where('userId', isEqualTo: uid)
.orderBy('createdAt', descending: true)
.snapshots();
Salah:
FirebaseFirestore.instance
.collection('tasks')
.orderBy('createdAt', descending: true)
.snapshots();
Query kedua akan ditolak karena Firestore melihat query itu berpotensi membaca task semua user.
18. Mengatasi Permission Denied
Error yang sering muncul:
cloud_firestore/permission-denied
Penyebab umum:
- user belum login
- query tidak memakai
where('userId', isEqualTo: uid) - data baru tidak mengirim
userId userIdyang dikirim tidak sama dengan uid user login- rules terlalu ketat
- field yang dikirim tidak sesuai validasi
Cara debug:
- cek apakah
FirebaseAuth.instance.currentUsertidak null - print uid user login
- cek data yang dikirim ke Firestore
- cek field
userId - cek query sudah sesuai rules
- coba rules sederhana dulu
- tambah validasi sedikit demi sedikit
Contoh debug di Flutter:
final user = FirebaseAuth.instance.currentUser;
debugPrint('UID: ${user?.uid}');
19. Testing Manual dengan Dua Akun
Testing rules jangan hanya memakai satu akun.
Gunakan skenario:
- login dengan akun A
- buat task dari akun A
- pastikan field
userIdsama dengan uid akun A - logout
- login dengan akun B
- pastikan task akun A tidak muncul
- buat task akun B
- pastikan field
userIdsama dengan uid akun B - logout
- login lagi dengan akun A
- pastikan task akun A masih muncul
- pastikan task akun B tidak muncul
Kalau task akun lain muncul, berarti query atau rules masih salah.
20. Testing dari Firebase Console
Firebase Console menyediakan Rules Playground untuk simulasi rules.
Yang bisa dites:
- read document dengan user login
- read document tanpa login
- create document dengan
userIdbenar - create document dengan
userIduser lain - update title
- update
userId - delete document milik sendiri
- delete document milik orang lain
Rules yang baik harus:
- mengizinkan aksi yang benar
- menolak aksi yang salah
Jangan hanya memastikan fitur berhasil. Pastikan juga aksi berbahaya gagal.
21. Kesalahan Umum Pemula
Membuka semua akses
Contoh rules berbahaya:
allow read, write: if true;
Rules ini berarti semua orang boleh membaca dan menulis semua data.
Hanya cek login
allow read, write: if request.auth != null;
Ini lebih baik daripada public, tetapi semua user login masih bisa mengakses data semua user.
Tidak mengunci userId saat update
allow update: if resource.data.userId == request.auth.uid;
Rules ini belum cukup karena user bisa mencoba mengganti userId di data baru.
Tambahkan:
request.resource.data.userId == resource.data.userId
Query tidak sesuai rules
Rules sudah benar, tetapi query Flutter tidak memakai where userId. Hasilnya tetap permission-denied.
22. Checklist
Checklist setelah mempelajari materi ini:
- paham fungsi Firestore Security Rules
- paham
request.auth - paham
request.auth.uid - paham bedanya
resource.datadanrequest.resource.data - bisa membuat rules read per user
- bisa membuat rules create per user
- bisa membuat rules update per user
- bisa membuat rules delete per user
- bisa validasi field sederhana
- bisa debug
permission-denied - bisa testing dengan dua akun
23. Kesimpulan
Firestore Security Rules adalah bagian wajib saat membuat aplikasi Firebase.
Untuk aplikasi Task Manager per user, prinsip utamanya:
- user harus login
- document harus punya
userId userIdharus sama denganrequest.auth.uid- query Flutter harus sesuai rules
- update tidak boleh mengganti pemilik data
- validasi data tetap perlu di server
Setelah rules berjalan dengan benar, aplikasi menjadi jauh lebih aman untuk dikembangkan ke tahap berikutnya.
24. Selanjutnya
Materi berikutnya yang cocok adalah Repository Pattern dengan Firebase.
Di materi itu kita akan merapikan kode agar aplikasi tidak terlalu bergantung langsung pada Firestore di halaman UI.
Referensi resmi: