Sitemap
Firebase Developers

Tutorials, deep-dives, and random musings from Firebase developers all around the world. Views expressed are those of the authors and don’t necessarily reflect those of Firebase or its parent companies.

Follow publication

How to build a team-based user management system with Firebase

9 min readOct 9, 2020

--

Teams. (Original photo by Margarida CSilva)

The challenge: Multiple teams with many users

The solution: Teams, Memberships and Users

Firestore architecture

↳ "teams" collection, one doc contains:
↳ ... team data ...
↳ "memberships" collection, one doc contains:
↳ role or permission field
↳ reference field `user` to document in "users" collection
↳ ... duplicated team data ...
↳ ... duplicated user data ...
↳ `users` collection, one doc contains:
↳ reference field `currentTeam` to the active team of the user
↳ ... user data ...

Keeping things in sync

Security first

rules_version = '2'
service cloud.firestore {
match /databases/{database}/documents {
match /teams/{teamId} {}
}
}
rules_version = '2'
service cloud.firestore {
match /databases/{database}/documents {
function hasAccessToTeam(teamId) {
return exists(/databases/$(database)/documents/teams/$(teamId)/memberships/$(request.auth.uid))
}
match /teams/{teamId} {
// Allow authenticated users to create new teams.
allow create: if
request.auth != null
// Allow access to the team doc.
allow get, update: if
request.auth != null &&
hasAccessToTeam(teamId)
// Allow read access to all subcollections of the team doc.
match /{subcollection}/{document=**} {
allow read: if
request.auth != null &&
hasAccessToTeam(teamId)
}
}
}
}
function hasAccessToTeam(teamId, role) {
return get(/databases/$(database)/documents/teams/$(teamId)/memberships/$(request.auth.uid)).data.role == role
}
rules_version = '2'
service cloud.firestore {
match /databases/{database}/documents {
... previous rules ...
function isReferenceTo(field, path) {
return path('/databases/(default)/documents' + path) == field
}
// Allow access to memberships.
match /{document=**}/memberships/{userId} {
allow read: if
request.auth != null &&
isReferenceTo(resource.data.user, "/users/" + request.auth.uid)
}
}
}
match /memberships/{membershipId} {
function hasCreatedTeam(teamId) {
return isReferenceTo(get(/databases/$(database)/documents/teams/$(teamId))-data.createdBy, "/users/" + request.auth.uid)
}
allow create: if
request.auth != null &&
membershipId == request.auth.uid &&
isReferenceTo(request.resource.data.user, "/users/" + request.auth.uid) &&
hasCreatedTeam(teamId)
}

Summary

Further questions

What about invitations?

Shouldn’t the membership collection be on the same level as the users and teams collection?

Can’t I use the JWT functionality to apply roles to users?

Is there a way to avoid additional security rules for users creating new teams?

--

--

Firebase Developers
Firebase Developers

Published in Firebase Developers

Tutorials, deep-dives, and random musings from Firebase developers all around the world. Views expressed are those of the authors and don’t necessarily reflect those of Firebase or its parent companies.

Richard Keil
Richard Keil

Written by Richard Keil

Co-Founder at Exposify and EstateSync.

Responses (8)