Introduction to RBAC and How Re:Earth is realizing Authorization
2026-05-14
1. Understanding Role-Based Access Control (RBAC)
What is RBAC and Why Does It Matter?
Role-Based Access Control (RBAC) is an approach to managing access to resources based on the roles users have within an organization. Rather than assigning permissions directly to individual users, RBAC assigns permissions to roles, and users are assigned to those roles. This matters because as applications scale, managing permissions at the individual level becomes unsustainable and error-prone.
Core Components of RBAC: Roles, Permissions, and Users
RBAC consists of three primary components: roles (job functions or titles that define authority levels), permissions (approved operations on resources), and users (individuals who are assigned roles). The relationships between these components create a structured security model where access decisions follow the principle of least privilege.
Benefits of Implementing RBAC in Modern Applications
RBAC simplifies administration by grouping permissions into roles, reduces security risks through better permission management, improves compliance by making access auditable, and enhances operational efficiency by aligning access with organizational structure. For modern applications with diverse user bases, RBAC provides both security and scalability.
Common RBAC Implementation Challenges
Despite its benefits, implementing RBAC can be challenging. Role explosion (creating too many specialized roles), keeping roles synchronized with organizational changes, managing role inheritance hierarchies, and handling cross-cutting concerns like time-based restrictions are common hurdles teams face when deploying RBAC systems.
2. The Evolution of Authorization Systems
Traditional Access Control Models
Early authorization systems used basic models like Discretionary Access Control (DAC) and Mandatory Access Control (MAC). In DAC, resource owners controlled access permissions, while MAC enforced access through security classifications. Though effective for simple systems, these models lacked the flexibility required for complex applications.
The Shift to Role-Based Systems
As organizations became more complex, RBAC emerged to overcome traditional models' limitations. It introduced roles as a bridge between users and permissions, making access management more intuitive and aligned with organizational structures—significantly reducing administrative burden.
From RBAC to Attribute-Based Access Control (ABAC)
Despite RBAC's improvements, modern applications demanded more sophisticated contextual decision-making. ABAC built upon RBAC by incorporating multiple attributes—including user properties, resource characteristics, and environmental conditions—to enable precise, dynamic access control.
Policy as Code: The Modern Approach
The newest evolution in authorization treats policies as code, applying software development practices to access rule management. This approach, exemplified by tools like Cerbos, keeps authorization logic separate from application code, ensuring consistent enforcement across services while maintaining transparency and auditability.
3. Re:Earth's Authorization Needs
Introduction to Re:Earth's Platform
Re:Earth is a web-based geospatial platform that enables users to create, share, and visualize 3D digital twin environments and geographic data without coding. The platform serves diverse users—from individual creators to enterprise teams—each requiring different levels of access to projects, datasets, and visualization tools. This diversity makes authorization particularly critical for maintaining both security and collaboration efficiency.
Unique Authorization Requirements for Reearth
Geospatial platforms like Re:Earth face unique authorization challenges that traditional web applications don't encounter:
Hierarchical Resource Management: Workspaces contain Projects, which contain Jobs. Each level requires granular permission control while maintaining inheritance logic that makes sense to users.
Public Publishing with Private Data: Re:Earth enables secure public sharing through tokenized links while maintaining data sovereignty. Users can generate shareable URLs that grant read-only access to specific project visualizations without exposing the underlying workspace, authentication credentials, or source datasets.
Balancing Security with Collaboration Features
The challenge in Re:Earth's authorization design is enabling seamless collaboration without compromising security. We identified several key requirements:
Workspace Isolation: Organizations using Re:Earth need complete isolation of their resources, ensuring that even with shared infrastructure, data never leaks across organizational boundaries.
Flexible Team Structures: Within workspaces, teams need varying levels of access. Am engineer might need full access to datasets but only viewing rights to published projects, while another member might need the inverse.
Scaling Authorization for Growing User Bases
As Re:Earth's user base grows, our authorization system must scale accordingly:
Performance at Scale: Authorization checks happen on every API request and UI interaction. These checks must be lightning-fast without sacrificing accuracy.
Role Management Complexity: As organizations grow, they tend to create more specialized roles. Our system needs to handle this growth without creating the "role explosion" problem where hundreds of micro-roles become unmanageable.
4. Why We Chose Cerbos
What is Cerbos?
Cerbos is an open-source, stateless authorization service that decouples access control logic from your application code. Policies are written in YAML and evaluated by a lightweight engine that can run as a sidecar, a standalone service, or an embedded library. Because policies live outside the application, teams can review, test, and version authorization rules the same way they manage any other piece of infrastructure-as-code. The source code is available on GitHub.
Evaluating Authorization Solutions
In early 2024, we faced a critical decision: our workspace-based role system wasn't scaling with our growing product ecosystem. We needed a centralized authorization service that could handle Re:Earth Flow, Visualizer, etc. while maintaining backward compatibility.
Our evaluation process compared three approaches:
Open Policy Agent (OPA): While powerful, Rego's Prolog-inspired syntax presented a steep learning curve. Also, it doesn't integrate well with Go.
Custom OAuth + Database Solution: Building our own RBAC on top of Auth0 offered full control but meant reinventing wheels around policy evaluation, caching, and audit logging.
Cerbos: Purpose-built for cloud-native authorization with YAML policies that our team could immediately understand and version control alongside our infrastructure-as-code.
Key Features of Cerbos That Aligned with Re:Earth
Service-Agnostic Resource Model: Each Re:Earth product defines its own resources while sharing common user and role infrastructure:
# Flow service resources
flow:project: ["read", "create", "edit", "delete"]
flow:deployment: ["read", "create", "execute", "delete"]
flow:job: ["read", "cancel", "retry"]
# Visualizer service resources
viz:scene: ["read", "edit", "publish"]
viz:layer: ["read", "create", "edit", "delete"]
Decoupled Policy Management Benefits
The separation between policy definition and enforcement transformed our development workflow:
- Each Products define resources and actions in their service code
- CI/CD pipeline validates, tests, and deploys policies to GCS
- Cerbos sidecar hot-reloads policies without service restarts
This architecture enabled us to migrate from workspace-wide roles to resource-specific permissions incrementally, service by service, without breaking existing integrations.
Integration Ease and Developer Experience
The account service acts as a translation layer:
The CheckPermission method centralizes authorization checks. It loads the user’s roles from the database, builds a Cerbos principal and a single resource from the requested service/resource/action, and calls Cerbos to evaluate the request. If Cerbos returns ALLOW for the given action, it returns Allowed: true; if the user has no roles or the action is not explicitly allowed, it returns Allowed: false.
The beauty lies in the simplicity: services make GraphQL queries for permissions without knowing about Cerbos, while the account service handles the complexity of policy evaluation. This abstraction layer means we can evolve our authorization strategy without touching dozens of microservices.
5. Our Implementation Journey
Initial Architecture Decisions
Our implementation follows a microservices pattern where each service (Flow, Visualizer, CMS) maintains its own resource definitions while sharing a centralized authorization service:
System Architecture Overview
The following diagram illustrates how authorization flows through our system:
Request Flow:
- User makes request to application service (Flow, Visualizer, etc.) with JWT token in header
- Application service forwards the request to Account service with JWT header
- Account service middleware validates JWT and fetches user information from database
- Account service queries Cerbos with user roles, service, resource, and action
- Cerbos evaluates policies and returns allow/deny decision
Key Architectural Choices:
- JWT tokens passed through headers for authentication chain
- Account service as central authorization point
- GraphQL for service-to-service communication
- Policy files stored in Google Cloud Storage for durability
- Automated policy deployment via GitHub Actions
Mapping Re:Earth Resources to Cerbos Policies
Each service defines its resources programmatically, which are then compiled into Cerbos policy files:
// Resource definition structure
const (
ServiceName = "flow"
// Resources
ResourceProject = "project"
ResourceDeployment = "deployment"
ResourceJob = "job"
// Actions
ActionRead = "read"
ActionCreate = "create"
ActionEdit = "edit"
ActionDelete = "delete"
)
These definitions generate YAML policies that Cerbos evaluates:
# Generated flow_project.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
resource: flow:project
rules:
- actions:
- read
effect: EFFECT_ALLOW
roles:
- reader
- actions:
- delete
effect: EFFECT_ALLOW
roles:
- maintainer
- owner
Role Hierarchy Design
Our role system implements a hierarchical permission model stored in MongoDB:
-- Database schema
Role: {id, name}
Permittable: {
id,
user_id,
role_ids[]
}
Role:
- reader
- editor
- maintainer
- owner
Each service can define which roles have access to specific actions, allowing flexible permission models across different services.
Handling Complex Permission Scenarios
Service-Specific Authorization: Each service maintains its own resource definitions and permission rules, allowing independent evolution of authorization models:
// GraphQL request structure from services to Account
type checkPermissionQuery struct {
CheckPermission struct {
Allowed bool
} `graphql:"checkPermission(input: {
service: $service, // "flow", "visualizer", "cms"
resource: $resource, // "project", "deployment", etc.
action: $action // "read", "create", "edit", "delete"
})"`
}
Workspace Isolation: The Account service extracts workspace context from the user data fetched via JWT validation, ensuring complete tenant isolation without requiring explicit workspace checks in each service's business logic.
CI/CD Integration: Our GitHub Actions workflow automatically:
- Generates policy files from Go definitions (make gen-policies)
- Syncs policies to Google Cloud Storage for the current environment
- Archives policies to Artifact Registry for production deployment distribution
- Triggers Cerbos Cloud Run deployment with updated policies
The Artifact Registry archive enables production environments to pull verified policy versions, ensuring consistency across different GCP projects and environments.
This architecture allows each service team to independently manage their authorization rules while maintaining centralized policy enforcement, providing both flexibility and security.
6. Real-World Authorization Scenarios in Re:Earth
Re:Earth is still evolving, but we already rely on Cerbos-backed RBAC in a few concrete parts of the product. This section focuses on scenarios that are in use today instead of trying to cover every possible case.
Project-Level Access Control
Users in Re:Earth belong to workspaces and have roles such as reader, editor, maintainer or owner.
These role assignments are stored per workspace in our database.
When a service needs to authorize an action, the account service:
- Looks up the user’s role for the target workspace or project.
- Builds a Cerbos principal with those roles.
- Builds a resource like
resourceoraction. - Calls a shared
CheckPermissionfunction to ask Cerbos if the action is allowed.
Workspace roles effectively act as “user groups”: a user can be an owner in one workspace and only a reader in another, without hard-coding permissions per user.
Public and URL-Based Sharing
Re:Earth supports sharing projects via URLs. To keep this consistent with the rest of our authorization model, the URL sharing endpoints also go through Cerbos.
When a request comes in through a shared URL, the Flow service:
- checks that the caller is allowed to access
projectAccess(for example, they have a maintainer role in the workspace), - looks up the
project_accessrecord by token, - verifies that the link is marked as public (
IsPublic()), - and finally loads the target project.
Internally, these operations are authorized using a dedicated projectAccess resource in our RBAC model.
7. Lessons Learned and Best Practices
Policy Testing Strategies
We validate authorization at a few different levels instead of relying only on application behaviur.
In the accounts server, an end-to-end style GraphQL test starts a Cerbos container, calls the checkPermission query, and asserts the allowed flag for different situations (no permittable, permittable with a role that can read, and actions that should still be denied). On top of that, the Cerbos interactor is covered by unit tests that mock repositories and the Cerbos gateway to exercise cases such as missing permittable, workspace membership, repository errors, and the final ALLOW/DENY result.
In Re:Earth Flow, usecases depend on a PermissionChecker interface; interactor tests inject a mock implementation so we can confirm that business logic honors whatever the permission check returns.
Performance Optimization Techniques
Our current Cerbos integration focuses on keeping each permission check simple and predictable.
For every CheckPermission call in the accounts server, we look up the user’s permittable once, collect both global and workspace-specific role IDs, and resolve them via a single FindByIDs call. We then build a compact Cerbos principal (user ID and role names) and a single resource (for example service:resource with an ID that may include workspace context) and call Cerbos once for the requested action.
We are not yet applying heavier optimizations like caching or batching, but this design keeps the payload small and the number of Cerbos round-trips bounded, which has been sufficient for our current workloads.
Documentation and Policy Visibility
To keep policies understandable, we maintain a small RBAC package per service that lists resources, actions, and roles in code.
For example, the Flow service defines resources such as asset, project, user, and workspace together with actions like read, list, create, edit, delete, and any in rbac/definition.go. These Go definitions act as a catalog for developers and as the source of truth when services call the accounts server’s checkPermission GraphQL endpoint using a ServiceName, resource, and action.
This code-first catalog makes it easy to answer questions like “who can do X on Y?” by reading the RBAC package, without needing to inspect Cerbos policies directly.
Scaling Our Authorization System
Finally, we learned a few things about scaling authorization across multiple services and use cases:
- Keep policy definition and enforcement decoupled: Application services such as the dashboard and Flow define their RBAC model in a small
rbacpackage (service name, resources, actions, roles), but they do not talk to Cerbos directly. Instead, they send acheckPermissionGraphQL query to the accounts server, which owns the Cerbos policies and evaluates the request. - Evolve from coarse- to fine-grained actions: Some resources, such as
projectAccess, currently use a genericanyaction. We are gradually introducing more specific actions (for exampleshare,unshare, andfetch) so we can tighten policies where needed without redesigning the whole model. - Centralize policy changes: Because enforcement lives in the accounts server, updating a Cerbos policy or the
CheckPermissionlogic in one place automatically affects all caller services that reference the sameservice,resource, andactionidentifiers.
8. Conclusion: Reimagining Authorization for Geospatial Platforms
Key Takeaways
Building authorization for a geospatial platform like Re:Earth is not just about protecting APIs. It is about enabling teams to work safely with shared workspaces, projects, and visualizations while still supporting public sharing when needed.
Our current approach is based on a few simple but important principles:
- RBAC as the foundation: We use role-based access control as the core model. Users belong to workspaces and are assigned roles such as
reader,editor,maintainer, orowner, and those roles drive what they can do on projects and other resources. - A central authorization service: Application services like Flow or the dashboard do not talk to Cerbos directly. Instead, they send a
checkPermissionGraphQL request to the accounts server, which owns the Cerbos integration and evaluates permissions based on user roles and workspace context. - Code-first RBAC definitions per service: Each service keeps a small
rbacpackage that declares itsServiceName, resources, actions, and roles. These definitions act as a catalog that both developers and the authorization layer rely on when asking “can this user perform this action on this resource?”. - URL sharing on top of the same model: Public sharing of projects via URLs is implemented using the same authorization pipeline. We use a
projectAccessresource together with a token andIsPublic()checks so that URL-based access is still controlled in a consistent way. - Testing at multiple levels: We back this design with tests on both sides of the boundary: GraphQL-level tests for the
checkPermissionquery in the accounts server, and unit tests around the Cerbos interactor and permission-aware usecases. This makes policy and wiring changes safer to roll out.
Recommendations for Similar Projects
If you are building a geospatial platform or any multi-tenant application with shared workspaces and public content, a few practices from our journey may be useful:
- Start by naming your resources and actions clearly: Before choosing any tool, make a small list of the resources (for example
workspace,project,asset) and actions (read,create,edit,delete,any) that actually matter to your product. - Separate definition from enforcement: Keep resource and action definitions close to the application (for example in a small
rbacpackage), but centralize enforcement in a dedicated service or module that owns the policy engine integration. - Use a policy engine instead of hard-coding rules: A tool like Cerbos lets you evaluate permissions consistently from one place while still evolving your application code and services independently.
- Invest early in tests around authorization: Even a small set of end-to-end checks for a
checkPermissionendpoint, plus unit tests around your translation layer (roles → principal, resource → attributes), can prevent subtle bugs later. - Keep things simple first: It’s better to have a simple and predictable RBAC model that everyone understands than a perfect model that nobody wants to touch.
Get Started with RBAC in Your Own Applications
For teams that are just starting to redesign authorization, one practical path is:
- Inventory your current access rules: List which users (or roles) are supposed to do what on which resources today, even if it is currently implemented in ad-hoc checks.
- Introduce a
checkPermissionusecase: Add a single entry point for permission checks in your codebase, and gradually route more authorization decisions through it. - Add a central policy service: Integrate a policy engine such as Cerbos behind that usecase so that the logic for ALLOW/DENY decisions is no longer scattered across services.
- Move service RBAC definitions into code: Create small RBAC packages per service that declare resources and actions, and use them when calling your
checkPermissionendpoint. - Iterate based on real usage: Observe how roles and permissions are used in practice, then refine actions and rules where they are too broad or too restrictive.
For Re:Earth, this approach has given us a unified way to reason about permissions across workspaces, projects, and URL sharing without over-complicating our services. We still have room to evolve, but we now have a foundation that is testable, explainable, and shared across the platform.
Eukaryaでは様々な職種で採用を行っています!OSSにコントリビュートしていただける皆様からの応募をお待ちしております!
Eukarya is hiring for various positions! We are looking forward to your application from everyone who can contribute to OSS!
Eukaryaは、Re:Earthと呼ばれるWebGISのSaaSの開発運営・研究開発を行っています。Web上で3Dを含むGIS(地図アプリの公開、データ管理、データ変換等)に関するあらゆる業務を完結できることを目指しています。ソースコードはほとんどOSSとしてGitHubで公開されています。
➔ Re:Earth / ➔ Eukarya / ➔ note / ➔ GitHub
Eukarya is developing and operating a WebGIS SaaS called Re:Earth. We aim to complete all GIS-related tasks including 3D (such as publishing map applications, data management, and data conversion) on the web. Most of the source code is published on GitHub as OSS.