Firestore Security Rules Are Your Only Real Boundary
A route guard that redirects unauthenticated users away from /dashboard feels like access control. It is not. It is a suggestion.
Why the guard is theatre
Your dashboard is JavaScript shipped to the browser. Anyone can read it, call your Firestore client directly, and skip the redirect entirely. The guard improves the experience for honest users and stops nobody else.
Rules are the real gate
function isAdmin() {
return request.auth != null
&& exists(/databases/$(database)/documents/admins/$(request.auth.uid));
}
This runs on Google's servers. There is no way around it from a client.
Three mistakes worth avoiding
Trusting request data over stored data. request.resource.data.role == 'admin' reads what the caller sent. They control it. Read the existing document instead.
Forgetting the catch-all. Any path you did not explicitly match falls through. End your rules with a deny.
Letting a write smuggle extra fields. If anonymous users may bump a counter, pin the write to exactly that key:
request.resource.data.diff(resource.data).affectedKeys().hasOnly(['views'])
Without hasOnly, a "view counter" endpoint quietly becomes an arbitrary write.
Test them
The emulator runs your rules locally against fake auth states. A rules file you never tested is a rules file you are guessing about.