{"id":31939508,"url":"https://github.com/openfga/openfga-cedar-comparison","last_synced_at":"2025-10-14T08:29:11.085Z","repository":{"id":315465064,"uuid":"1034217776","full_name":"openfga/openfga-cedar-comparison","owner":"openfga","description":"Authorization Frameworks Comparison: Cedar vs OpenFGA","archived":false,"fork":false,"pushed_at":"2025-09-18T17:58:43.000Z","size":53,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-18T20:24:46.894Z","etag":null,"topics":["authorization","cedar","openfga","zanzibar"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openfga.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-08T03:35:41.000Z","updated_at":"2025-09-18T20:21:13.000Z","dependencies_parsed_at":"2025-09-18T20:31:42.727Z","dependency_job_id":"256d2f0e-3781-45f9-86fb-544b7b920cdc","html_url":"https://github.com/openfga/openfga-cedar-comparison","commit_stats":null,"previous_names":["openfga/openfga-cedar-comparison"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/openfga/openfga-cedar-comparison","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openfga%2Fopenfga-cedar-comparison","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openfga%2Fopenfga-cedar-comparison/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openfga%2Fopenfga-cedar-comparison/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openfga%2Fopenfga-cedar-comparison/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openfga","download_url":"https://codeload.github.com/openfga/openfga-cedar-comparison/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openfga%2Fopenfga-cedar-comparison/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279018311,"owners_count":26086342,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","cedar","openfga","zanzibar"],"created_at":"2025-10-14T08:29:06.374Z","updated_at":"2025-10-14T08:29:11.067Z","avatar_url":"https://github.com/openfga.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Authorization Frameworks Comparison: Cedar vs OpenFGA\n\n\u003e A comprehensive comparison of two modern authorization frameworks through a practical document management system example.\n\n## Table of Contents\n- [Overview](#overview)\n- [The Problem](#the-problem)\n- [Quick Start](#quick-start)\n- [Architecture Comparison](#architecture-comparison)\n- [Implementation Details](#implementation-details)\n- [Trade-offs Analysis](#trade-offs-analysis)\n- [When to Choose Each](#when-to-choose-each)\n\n## Overview\n\nThis repository demonstrates two different approaches for implementing modern application authorization by comparing two open source tools: [OpenFGA](https://openfga.dev/) and [Cedar](https://www.cedarpolicy.com).\n\nWe implement authorization for a **multi-tenant document management system** with organizations that own folders containing documents - a common real-world scenario that showcases the strengths and trade-offs of each approach.\n\n## The Problem\n\nWe need authorization for a document management system with these requirements:\n\n### Business Rules\n1. **Organization-based access**: Users can view documents in their organization\n2. **Ownership**: Document/folder owners have full access (view, edit, delete, share)\n3. **Explicit permissions**: Grant editor/viewer permissions on documents and folders\n4. **Inheritance**: Folder permissions apply to contained documents\n\n### Test Scenarios\n- ✅ **alice can view doc1**: She's the owner\n- ✅ **charlie can view doc2**: Organization member + folder viewer permission\n- ❌ **david cannot view doc1**: Different organization, no permissions\n- ✅ **bob can view doc4**: Explicit editor permission\n\n## Quick Start\n\n### Prerequisites\n- Go 1.19+\n- Docker and Docker Compose\n- curl (for setup scripts)\n\n### Try OpenFGA Example\n```bash\ncd openfga/\n./setup.sh\n./openfga-check alice doc1    # ✅ Owner access\n./openfga-check david doc1    # ❌ Cross-organization denied\n```\n\n### Try Cedar Example  \n```bash\ncd cedar/\n./setup.sh\n./cedar-check alice doc1     # ✅ Owner access\n./cedar-check david doc1     # ❌ Cross-organization denied\n```\n\nBoth examples provide identical authorization decisions using different approaches.\n\n## Architecture Comparison\n\n### OpenFGA: Relationship-Based Authorization\n\n```dsl.openfga\nmodel\n  schema 1.1\n\ntype user\n\ntype organization\n  relations\n    define member: [user]\n\ntype folder\n  relations\n    # define parent: [folder] -\u003e Not added because Cedar does not support recursion\n    define organization: [organization]\n    define owner: [user]\n    define editor: [user] or owner # or editor from parent \n    define viewer: [user] or editor or member from organization # or viewer from parent \n\n    define can_view: viewer\n    define can_edit: editor\n    define can_delete: owner\n    define can_share: owner or editor\n\ntype document\n  relations\n    define organization: [organization]\n    define parent_folder: [folder]\n    define owner: [user]\n    define editor: [user] or owner or editor from parent_folder\n    define viewer: [user] or editor or viewer from parent_folder or member from organization\n\n    define can_view: viewer\n    define can_edit: editor\n    define can_delete: owner\n    define can_share: owner or editor\n```\n\n\u003e Given that OpenFGA supports recursion, like inheriting folder's permissions, but Cedar does not, we won't be using recursion throughout this example.\n\nIn Cedar you can define an entity schema that you can use to validate policies, but is not a requirement. You can find the schema we use for this example [here](cedar/schema.cedarschema). You define the authorization policies in the [Cedar language](https://docs.cedarpolicy.com/policies/syntax-policy.html):\n\n```\n// Document Management Authorization Policies\n\n// Organization member can view organization documents\npermit(\n    principal,\n    action == DocumentManagement::Action::\"ViewDocument\",\n    resource\n) when {\n    principal.organization == resource.organization\n};\n\n// Organization member can view organization folders\npermit(\n    principal,\n    action == DocumentManagement::Action::\"ViewFolder\",\n    resource\n) when {\n    principal.organization == resource.organization\n};\n\n// Document owner can perform all actions on their documents\npermit(\n    principal,\n    action in [\n        DocumentManagement::Action::\"ViewDocument\",\n        DocumentManagement::Action::\"EditDocument\",\n        DocumentManagement::Action::\"DeleteDocument\",\n        DocumentManagement::Action::\"ShareDocument\"\n    ],\n    resource\n) when {\n    principal == resource.owner\n};\n\n// Document editor can edit and share documents they edit\npermit(\n    principal,\n    action in [\n        DocumentManagement::Action::\"EditDocument\",\n        DocumentManagement::Action::\"ShareDocument\"\n    ],\n    resource\n) when {\n    principal in resource.editors\n};\n\n// Document viewer can view documents\npermit(\n    principal,\n    action == DocumentManagement::Action::\"ViewDocument\",\n    resource\n) when {\n    principal in resource.viewers\n};\n\n// Folder owner can perform all actions on their folders\npermit(\n    principal,\n    action in [\n        DocumentManagement::Action::\"ViewFolder\",\n        DocumentManagement::Action::\"EditFolder\",\n        DocumentManagement::Action::\"DeleteFolder\",\n        DocumentManagement::Action::\"ShareFolder\"\n    ],\n    resource\n) when {\n    principal == resource.owner\n};\n\n// Folder editor can view, edit, and share folders (but not delete)\npermit(\n    principal,\n    action in [\n        DocumentManagement::Action::\"ViewFolder\",\n        DocumentManagement::Action::\"EditFolder\",\n        DocumentManagement::Action::\"ShareFolder\"\n    ],\n    resource\n) when {\n    principal in resource.editors\n};\n\n// Folder editor can view, edit, and share documents in their folders\npermit(\n    principal,\n    action in [\n        DocumentManagement::Action::\"ViewDocument\",\n        DocumentManagement::Action::\"EditDocument\",\n        DocumentManagement::Action::\"ShareDocument\"\n    ],\n    resource\n) when {\n    principal in resource.parent_folder.editors\n};\n\n// Folder owner can view, edit, and share documents in their folders\npermit(\n    principal,\n    action in [\n        DocumentManagement::Action::\"ViewDocument\",\n        DocumentManagement::Action::\"EditDocument\",\n        DocumentManagement::Action::\"ShareDocument\"\n    ],\n    resource\n) when {\n    principal == resource.parent_folder.owner\n};\n\n// Folder viewers can view folders\npermit(\n    principal,\n    action == DocumentManagement::Action::\"ViewFolder\",\n    resource\n) when {\n    principal in resource.viewers\n};\n\n// Folder viewers can view documents in folders\npermit(\n    principal,\n    action == DocumentManagement::Action::\"ViewDocument\",\n    resource\n) when {\n    principal in resource.parent_folder.viewers\n};\n```\n\nBoth policies are equivalent and hopefully self-explanatory. The approaches are very different though. In OpenFGA permissions are defined in terms of relations, which lets you define all the different ways a user can get a permission in a single line (e.g. ` define viewer: [user] or editor or viewer from parent_folder or member from organization`) while navigating resources hierarchies, and in Cedar you need to define define multiple `permit` clauses.\n\n## Key Architectural Differences\n\n**OpenFGA: Service-Based Authorization**\n- Runs as a separate service with its own database\n- All authorization data stored in OpenFGA\n- Single API call for authorization decisions\n- Requires network roundtrip for each check\n\n**Cedar: Library-Based Authorization** \n- Runs as an embedded library in your application\n- Data retrieved from your existing databases\n- No network calls, but requires you to load the data\n- Authorization logic coupled with data access\n\n## Implementation Examples\n\n### OpenFGA Authorization Check\n\n```go\nfunc checkAuthorization(fgaClient *client.OpenFgaClient, userID, documentID string) (bool, error) {\n    body := client.ClientCheckRequest{\n        User:     fmt.Sprintf(\"user:%s\", userID),\n        Relation: \"can_view\", \n        Object:   fmt.Sprintf(\"document:%s\", documentID),\n    }\n\n    data, err := fgaClient.Check(context.Background()).Body(body).Execute()\n    if err != nil {\n        return false, fmt.Errorf(\"check request failed: %w\", err)\n    }\n\n    return *data.Allowed, nil\n}\n```\n\n### Cedar Authorization Check\n\n```go\n// 1. Load data from your database\ndata, err := queryEntityData(db, userID, documentID)\nif err != nil {\n    return false, err\n}\n\n// 2. Build Cedar entities from the data\nentities := buildCedarEntities(data)\n\n// 3. Create authorization request\nrequest := cedar.Request{\n    Principal: cedar.NewEntityUID(\"User\", userID),\n    Action:    cedar.NewEntityUID(\"Action\", \"ViewDocument\"), \n    Resource:  cedar.NewEntityUID(\"Document\", documentID),\n}\n\n// 4. Authorize with Cedar\ndecision, _ := cedar.Authorize(policySet, entities, request)\nreturn decision == cedar.Allow, nil\n```\nIn the Cedar example, we are using this SQL query to retrieve the data required to know if a user can view a document:\n\n```sql\nWITH user_org AS (\n\t\tSELECT organization_id as user_org_id\n\t\tFROM organization_members \n\t\tWHERE user_id = $1 \n\t\tLIMIT 1\n\t),\n\tdoc_info AS (\n\t\tSELECT d.id as doc_id, d.organization_id as doc_org_id, d.folder_id, d.owner_id as doc_owner_id,\n\t\t\t   f.organization_id as folder_org_id, f.owner_id as folder_owner_id\n\t\tFROM documents d\n\t\tLEFT JOIN folders f ON d.folder_id = f.id\n\t\tWHERE d.id = $2\n\t),\n\tdoc_perms AS (\n\t\tSELECT dp.document_id, dp.user_id, dp.permission_type, 'document' as resource_type\n\t\tFROM document_permissions dp\n\t\tWHERE dp.document_id = $2\n\t),\n\tfolder_perms AS (\n\t\tSELECT fp.folder_id as document_id, fp.user_id, fp.permission_type, 'folder' as resource_type\n\t\tFROM folder_permissions fp\n\t\tJOIN doc_info di ON fp.folder_id = di.folder_id\n\t\tWHERE di.folder_id IS NOT NULL\n\t)\n\tSELECT \n\t\tuo.user_org_id, di.doc_id, di.doc_org_id, di.folder_id, di.doc_owner_id, di.folder_org_id, di.folder_owner_id,\n\t\tCOALESCE(dp.user_id, '') as perm_user_id,\n\t\tCOALESCE(dp.permission_type, '') as perm_type,\n\t\tCOALESCE(dp.resource_type, '') as resource_type\n\tFROM user_org uo\n\tCROSS JOIN doc_info di\n\tLEFT JOIN (\n\t\tSELECT * FROM doc_perms\n\t\tUNION ALL\n\t\tSELECT * FROM folder_perms\n\t) dp ON true\n```\n\nThere are other ways to write a single or multiple queries and get a similar results. After you retrieve the data, you need to convert it to an instance of a Cedar Entity. The [cedar/main.go](cedar/main.go) program has the full example.\n\n## OpenFGA's Contextual Tuples\n\nIn general, when using OpenFGA, you will store all the data required to make authorization decisions in OpenFGA. When using Cedar, you'll store it in your application.\n\nHowever, OpenFGA allows a hybrid model, where you can actually specify the data required to make the decision in [Contextual Tuples](https://openfga.dev/docs/interacting/contextual-tuples). Conceptually, you can do something equivalent to what the Cedar example shows, get all the data from a SQL database, and send it as part of the authorization request.\n\nIt would not make sense to use OpenFGA that way, though. If in all scenarios you are going to first retrieve the data from your database, Cedar is a better option.\n\nOn the other hand, combining having data in OpenFGA AND sending contextual data gives you a lot of flexibility. If you can easily synchronize data to OpenFGA, you'd do that. When you can't, because data is not stored in a database (e.g. the content of an access token), or because synchronizing it is hard, you can send it as part of the request.\n\n## Trade-offs Analysis\n\n| Aspect | OpenFGA | Cedar |\n|--------|---------|-------|\n| **Latency** | Network call required, but optimized for relationship queries | No network call, but requires data loading |\n| **Complexity** | Simple API calls, easy integration | Complex data loading and entity building |\n| **Maintainability** | Policy changes don't affect app code | Policy changes may require SQL changes |\n| **Operations** | Requires running separate service + database | No additional infrastructure |\n| **List Operations** | Native \"list all documents user can view\" | Requires custom SQL, post-filtering or experimental partial evaluation |\n| **Data Consistency** | Dual-write problem for data sync | Uses existing transactional data |\n| **Recursion** | Does support modeling recursive permissions | It does not support recursive permissions |\n\n### Detailed Trade-offs\n\n#### **Latency**\n- **OpenFGA**: Network roundtrip required, but queries are optimized and cacheable\n- **Cedar**: No network call, but data loading latency depends on query complexity\n\n#### **Access Control Complexity**\n- **OpenFGA**: Simple API calls - easily integrated into API gateways\n- **Cedar**: Requires data retrieval and transformation - more complex integration\n\n#### **Maintainability**  \n- **OpenFGA**: Policy changes isolated from application code\n- **Cedar**: Authorization logic coupled with database queries\n\n#### **Operations**\n- **OpenFGA**: Additional service to operate, but dedicated authorization infrastructure\n- **Cedar**: No extra infrastructure, but higher database load\n\n#### **Reverse Queries**\n- **OpenFGA**: Built-in [ListObjects](https://openfga.dev/docs/getting-started/perform-list-objects) and [ListUsers](https://openfga.dev/docs/getting-started/perform-list-users) APIs. The latency for those calls will heavily depend on the authorization model.\n- **Cedar**: Requires encoding authorization logic in SQL, post-filtering results, or use the experimental [partial evaluation implementation](https://www.cedarpolicy.com/blog/partial-evaluation) to generate a filter for your local database.\n\n## Performance Considerations\n\nGiven the differences in architecture, a performance comparison between both engines does not make sense:\n\n- The raw Authorize call from Cedar will always be much faster than the equivalent OpenFGA operation, as it does not require a network call.\n- The overall performance will depend on how each system retrieves the data required to make the decision. OpenFGA is designed to optimize how traverse the data. Data management is out of scope for Cedar.\n\n## When to Choose Each\n\n### Choose OpenFGA When:\n- You need **fine-grained permissions** with complex inheritance\n- **List operations** are important (\"show all documents user can view\")\n- You want **authorization data logic separate** from business logic \n- You require additional data when **additional data** when making authorization decisions\n- Your authorization requirements are **relationship based** rather than attribute-based\n\n### Choose Cedar When:\n- You have **rich entity attributes** that drive decisions\n- You want to **minimize infrastructure** complexity\n- Your authorization is **primarily attribute-based** rather than relationship-based\n- The application already has **all the data required to make authorization decisions**\n\n## Learning Resources\n\n### Cedar\n- [Cedar Documentation](https://docs.cedarpolicy.com/)\n- [Cedar Go SDK](https://github.com/cedar-policy/cedar-go)\n- [Cedar Policy Language Guide](https://docs.cedarpolicy.com/policies/syntax.html)\n- [Cedarland Blog](https://cedarland.blog/)\n\n### OpenFGA\n- [OpenFGA Documentation](https://openfga.dev/)\n- [OpenFGA Go SDK](https://github.com/openfga/go-sdk)  \n- [Zanzibar Paper](https://research.google/pubs/pub48190/) - The original Google paper\n- [OpenFGA Playground](https://play.fga.dev/) - Interactive modeling tool\n\n## Contributing\n\nSee [CONTRIBUTING](https://github.com/openfga/.github/blob/main/CONTRIBUTING.md).\n\n## Author\n\n[OpenFGA](https://github.com/openfga)\n\n## License\n\nThis project is licensed under the Apache-2.0 license. See the [LICENSE](https://github.com/openfga/language/blob/main/LICENSE) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenfga%2Fopenfga-cedar-comparison","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenfga%2Fopenfga-cedar-comparison","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenfga%2Fopenfga-cedar-comparison/lists"}