From f52ad30540c38e85cb78e394172154400360ff9b Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Wed, 20 May 2026 22:15:51 -0300 Subject: [PATCH 01/13] seo: add per-page meta descriptions, JSON-LD structured data, fix H1 - Add per-page meta descriptions (140-160 chars) on key pages - Retitle authorization-concepts page for SEO - Fix homepage H1 by removing SVG leak (HeroLogo) - Install @stackql/docusaurus-plugin-structured-data - Emit Organization (CNCF/Linux Foundation), WebSite + SearchAction, BreadcrumbList, and SoftwareApplication JSON-LD - Add FAQPage JSON-LD to authorization-concepts (7 entries) - Add HowTo JSON-LD to getting-started overview (9 steps) --- docs/content/authorization-concepts.mdx | 72 +++- docs/content/getting-started/overview.mdx | 24 ++ docs/content/intro.mdx | 2 +- .../modeling/agents/mcp-authorization.mdx | 2 +- docs/content/modeling/agents/overview.mdx | 2 +- .../modeling/agents/rag-authorization.mdx | 2 +- docusaurus.config.js | 57 ++- package-lock.json | 377 +++++++++++++++++- package.json | 1 + .../LandingPage/HeroHeader/HeroLogo.tsx | 4 +- src/pages/index.tsx | 23 +- 11 files changed, 550 insertions(+), 16 deletions(-) diff --git a/docs/content/authorization-concepts.mdx b/docs/content/authorization-concepts.mdx index 1dc4af315f..d570722d19 100644 --- a/docs/content/authorization-concepts.mdx +++ b/docs/content/authorization-concepts.mdx @@ -1,12 +1,80 @@ --- -title: Authorization Concepts -description: Introduction to Authorization +title: 'Fine-Grained Authorization, ReBAC, ABAC & Zanzibar Explained | OpenFGA' +description: 'Learn fine-grained authorization concepts: ReBAC, RBAC, ABAC, PBAC, and Google Zanzibar. Understand how OpenFGA models permissions for modern apps.' sidebar_position: 1 slug: /authorization-concepts --- +import Head from '@docusaurus/Head'; import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; +export const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: [ + { + '@type': 'Question', + name: 'What is the difference between authentication and authorization?', + acceptedAnswer: { + '@type': 'Answer', + text: "Authentication ensures a user's identity. Authorization determines if a user can perform a certain action on a particular resource. For example, when you log in to Google, authentication verifies your username and password are correct, while authorization ensures that you can access a given Google service or feature.", + }, + }, + { + '@type': 'Question', + name: 'What is Fine-Grained Authorization?', + acceptedAnswer: { + '@type': 'Answer', + text: 'Fine-Grained Authorization (FGA) implies the ability to grant specific users permission to perform certain actions in specific resources. Well-designed FGA systems allow you to manage permissions for millions of objects and users, with permissions that can change rapidly. A notable example is Google Drive, where access can be granted to documents or folders, to individual users or groups.', + }, + }, + { + '@type': 'Question', + name: 'What is Role-Based Access Control?', + acceptedAnswer: { + '@type': 'Answer', + text: 'In Role-Based Access Control (RBAC), permissions are assigned to users based on their role in a system. For example, a user needs the editor role to edit content. RBAC systems enable you to define users, groups, roles, and permissions, then store them in a centralized location that applications access to make authorization decisions.', + }, + }, + { + '@type': 'Question', + name: 'What is Attribute-Based Access Control?', + acceptedAnswer: { + '@type': 'Answer', + text: 'In Attribute-Based Access Control (ABAC), permissions are granted based on a set of attributes that a user or resource possesses. For example, a user assigned both marketing and manager attributes is entitled to publish and delete posts that have a marketing attribute. Applications implementing ABAC retrieve information from multiple data sources to make authorization decisions.', + }, + }, + { + '@type': 'Question', + name: 'What is Policy-Based Access Control?', + acceptedAnswer: { + '@type': 'Answer', + text: "Policy-Based Access Control (PBAC) is the ability to manage authorization policies in a centralized way that's external to the application code. Most implementations of ABAC are also PBAC.", + }, + }, + { + '@type': 'Question', + name: 'What is Relationship-Based Access Control?', + acceptedAnswer: { + '@type': 'Answer', + text: "Relationship-Based Access Control (ReBAC) enables user access rules to be conditional on relations that a given user has with a given object and that object's relationship with other objects. For example, a user can view a document if the user has access to the document's parent folder. ReBAC is a superset of RBAC and can natively solve for ABAC when attributes are expressed as relationships.", + }, + }, + { + '@type': 'Question', + name: 'What is Zanzibar?', + acceptedAnswer: { + '@type': 'Answer', + text: "Zanzibar is Google's global authorization system across Google's product suite. It's based on ReBAC and uses object-relation-user tuples to store relationship data, then checks those relations for a match between a user and an object. ReBAC systems based on Zanzibar store authorization data in a centralized database that applications query via API. OpenFGA is an example of a Zanzibar-based authorization system.", + }, + }, + ], +}; + +<Head> + <script type="application/ld+json">{JSON.stringify(faqJsonLd)}</script> +</Head> + # Authorization Concepts ## Authentication and Authorization diff --git a/docs/content/getting-started/overview.mdx b/docs/content/getting-started/overview.mdx index d3341e7114..6d6ebf728c 100644 --- a/docs/content/getting-started/overview.mdx +++ b/docs/content/getting-started/overview.mdx @@ -3,10 +3,34 @@ id: overview title: 'Getting Started' slug: /getting-started sidebar_position: 0 +description: 'OpenFGA tutorial and quickstart: install the server, configure an authorization model, write tuples, and run your first permission checks in minutes.' --- +import Head from '@docusaurus/Head'; import { DocumentationNotice, IntroCard, CardGrid, ProductName } from '@components/Docs'; +export const howToJsonLd = { + '@context': 'https://schema.org', + '@type': 'HowTo', + name: 'Getting Started with OpenFGA', + description: 'Step-by-step guide to install OpenFGA, configure an authorization model, write relationship tuples, and run permission checks.', + step: [ + { '@type': 'HowToStep', position: 1, name: 'Setup OpenFGA', text: 'Setup an OpenFGA server.', url: 'https://openfga.dev/docs/getting-started/setup-openfga/overview' }, + { '@type': 'HowToStep', position: 2, name: 'Install SDK Client', text: 'Install the SDK for the language of your choice.', url: 'https://openfga.dev/docs/getting-started/install-sdk' }, + { '@type': 'HowToStep', position: 3, name: 'Create a Store', text: 'Create an OpenFGA store that owns an authorization model and relationship tuples.', url: 'https://openfga.dev/docs/getting-started/create-store' }, + { '@type': 'HowToStep', position: 4, name: 'Setup SDK Client for Store', text: 'Configure the SDK client for your store.', url: 'https://openfga.dev/docs/getting-started/setup-sdk-client' }, + { '@type': 'HowToStep', position: 5, name: 'Configure Authorization Model', text: 'Programmatically configure an authorization model for an OpenFGA store.', url: 'https://openfga.dev/docs/getting-started/configure-model' }, + { '@type': 'HowToStep', position: 6, name: 'Update Relationship Tuples', text: 'Programmatically write authorization data to an OpenFGA store.', url: 'https://openfga.dev/docs/getting-started/update-tuples' }, + { '@type': 'HowToStep', position: 7, name: 'Perform a Check', text: 'Programmatically perform an authorization check against an OpenFGA store.', url: 'https://openfga.dev/docs/getting-started/perform-check' }, + { '@type': 'HowToStep', position: 8, name: 'Perform a List Objects Request', text: 'Programmatically perform a list objects request against an OpenFGA store.', url: 'https://openfga.dev/docs/getting-started/perform-list-objects' }, + { '@type': 'HowToStep', position: 9, name: 'Integrate Within a Framework', text: 'Integrate authorization checks with a framework.', url: 'https://openfga.dev/docs/getting-started/framework' }, + ], +}; + +<Head> + <script type="application/ld+json">{JSON.stringify(howToJsonLd)}</script> +</Head> + <DocumentationNotice /> The following will provide a step-by-step guide on how to get started with <ProductName />. diff --git a/docs/content/intro.mdx b/docs/content/intro.mdx index 980d0a54ac..91df34a031 100644 --- a/docs/content/intro.mdx +++ b/docs/content/intro.mdx @@ -1,6 +1,6 @@ --- title: Introduction to FGA -description: Introduction to FGA +description: 'OpenFGA is an open-source fine-grained authorization (FGA) system. Build relationship-based, role-based, and attribute-based access control at scale.' sidebar_position: 1 slug: /fga --- diff --git a/docs/content/modeling/agents/mcp-authorization.mdx b/docs/content/modeling/agents/mcp-authorization.mdx index a92057a04a..f9c07e3f33 100644 --- a/docs/content/modeling/agents/mcp-authorization.mdx +++ b/docs/content/modeling/agents/mcp-authorization.mdx @@ -1,6 +1,6 @@ --- title: Authorization for MCP Servers -description: Controlling which tools users can access on an MCP server +description: 'Authorize MCP server tools with OpenFGA: control which tools each user can invoke based on roles, group membership, and time-limited temporal grants.' sidebar_position: 3 slug: /modeling/agents/mcp-authorization --- diff --git a/docs/content/modeling/agents/overview.mdx b/docs/content/modeling/agents/overview.mdx index c61e09399d..96e0336ec1 100644 --- a/docs/content/modeling/agents/overview.mdx +++ b/docs/content/modeling/agents/overview.mdx @@ -3,7 +3,7 @@ id: overview title: 'Authorization for Agents' slug: /modeling/agents sidebar_position: 0 -description: Authorization patterns for AI agents and automated processes +description: 'Authorization patterns for AI agents and automated processes: model agents as principals, secure RAG pipelines, and control MCP server tool access.' --- import { CardGrid, DocumentationNotice, IntroCard, ProductName, ProductNameFormat } from '@components/Docs'; diff --git a/docs/content/modeling/agents/rag-authorization.mdx b/docs/content/modeling/agents/rag-authorization.mdx index c5723b0ed2..48ca55edc9 100644 --- a/docs/content/modeling/agents/rag-authorization.mdx +++ b/docs/content/modeling/agents/rag-authorization.mdx @@ -1,6 +1,6 @@ --- title: RAG Authorization -description: Ensuring AI agents only retrieve documents users are authorized to access +description: 'Secure your RAG pipeline with OpenFGA: enforce document-level permissions so AI agents only retrieve content each user is authorized to access.' sidebar_position: 2 slug: /modeling/agents/rag-authorization --- diff --git a/docusaurus.config.js b/docusaurus.config.js index 9b16792576..2c66f50bd2 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -177,6 +177,7 @@ import dev.openfga.sdk.api.configuration.ClientConfiguration;`, }, }, ], + '@stackql/docusaurus-plugin-structured-data', ], presets: [ @@ -212,15 +213,63 @@ import dev.openfga.sdk.api.configuration.ClientConfiguration;`, /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ metadata: [ - { - name: 'keywords', - content: 'OpenFGA, open source, fine-grained-authorization, fine grained authorization, Zanzibar', - }, { property: 'og:image', content: 'https://openfga.dev/img/og-rich-embed.png', }, ], + structuredData: { + excludedRoutes: ['/blog/authors', '/blog/archive', '/blog/tags', '/api/service'], + verbose: false, + featuredImageDimensions: { width: 1200, height: 627 }, + authors: { + aaguiar: { authorId: 'aaguiar', url: 'https://github.com/aaguiarz', imageUrl: 'https://openfga.dev/img/blog/authors/andres.jpg', sameAs: [] }, + eharris: { authorId: 'eharris', url: 'https://github.com/ewanharris', imageUrl: 'https://openfga.dev/img/blog/authors/ewan.jpg', sameAs: [] }, + jakub: { authorId: 'jakub', url: 'https://github.com/curfew-marathon', imageUrl: 'https://openfga.dev/img/blog/authors/jakub.jpg', sameAs: [] }, + miparnisari: { authorId: 'miparnisari', url: 'https://github.com/miparnisari', imageUrl: 'https://openfga.dev/img/blog/authors/miparnisari.jpg', sameAs: [] }, + 'hello-caleb': { authorId: 'hello-caleb', url: 'https://github.com/hello-caleb', imageUrl: 'https://openfga.dev/img/blog/authors/caleb.jpg', sameAs: [] }, + tylernix: { authorId: 'tylernix', url: 'https://github.com/tylernix', imageUrl: 'https://openfga.dev/img/blog/authors/tyler.jpg', sameAs: [] }, + }, + organization: { + name: 'OpenFGA', + url: 'https://openfga.dev', + logo: 'https://openfga.dev/img/openfga_logo.png', + sameAs: [ + 'https://github.com/openfga', + 'https://twitter.com/openfga', + 'https://hachyderm.io/@openfga', + 'https://openfga.dev/community', + ], + parentOrganization: { + '@type': 'Organization', + name: 'Cloud Native Computing Foundation', + url: 'https://www.cncf.io', + parentOrganization: { + '@type': 'Organization', + name: 'The Linux Foundation', + url: 'https://www.linuxfoundation.org', + }, + }, + }, + website: { + inLanguage: 'en-US', + potentialAction: { + '@type': 'SearchAction', + target: { + '@type': 'EntryPoint', + urlTemplate: 'https://openfga.dev/search?q={search_term_string}', + }, + 'query-input': 'required name=search_term_string', + }, + }, + webpage: { + inLanguage: 'en-US', + datePublished: '2022-09-13', + }, + breadcrumbLabelMap: { + docs: 'Docs', + }, + }, colorMode: { defaultMode: 'dark', disableSwitch: true, diff --git a/package-lock.json b/package-lock.json index 5ec8844b5b..f2c8f26f56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@openfga/frontend-utils": "^0.2.0-beta.11", "@openfga/sdk": "^0.9.5", "@openfga/syntax-transformer": "^0.2.1", + "@stackql/docusaurus-plugin-structured-data": "^1.3.2", "assert-never": "1.4.0", "clsx": "2.1.1", "path-browserify": "1.0.1", @@ -5448,6 +5449,15 @@ "micromark-util-symbol": "^1.0.1" } }, + "node_modules/@stackql/docusaurus-plugin-structured-data": { + "version": "1.3.2", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/@stackql/docusaurus-plugin-structured-data/-/docusaurus-plugin-structured-data-1.3.2.tgz", + "integrity": "sha512-79wy4X8Kt4ZKJ1jlldyslWPdntiXv8r8pFZWOmUAYVa+Ix1QA2toOrOBBy1YUGwRSROxV2OyJQs4w90FgQNzzQ==", + "license": "MIT", + "dependencies": { + "jsdom": "^21.0.0" + } + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -6376,6 +6386,15 @@ "node": ">=14.16" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -7251,6 +7270,13 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7285,6 +7311,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, "node_modules/acorn-import-phases": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", @@ -9487,6 +9523,18 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -9503,6 +9551,29 @@ "node": ">= 14" } }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -9580,6 +9651,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", @@ -9885,6 +9962,19 @@ ], "license": "BSD-2-Clause" }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", @@ -10342,7 +10432,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", @@ -10364,7 +10453,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "optional": true, "engines": { @@ -12180,6 +12268,31 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-encoding-sniffer/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -13141,6 +13254,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -13490,6 +13609,133 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "21.1.2", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/jsdom/-/jsdom-21.1.2.tgz", + "integrity": "sha512-sCpFmK2jv+1sjff4u7fzft+pUh2KSUbUrEHYHyfSIbGTIcmnjyp83qg6qLwdJ/I3LpTXx33ACxeRL7Lsyc6lGQ==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.2", + "acorn-globals": "^7.0.0", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.4", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/jsdom/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.20.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -16674,6 +16920,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -19054,6 +19306,18 @@ "dev": true, "license": "MIT" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -20209,6 +20473,12 @@ "node": ">=0.10.0" } }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "license": "MIT" + }, "node_modules/rtlcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", @@ -20411,6 +20681,18 @@ "node": ">=11.0.0" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -21607,6 +21889,12 @@ "react-dom": ">=16.8.0 <20" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -21860,6 +22148,42 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", @@ -22660,6 +22984,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", @@ -22708,6 +23044,15 @@ "license": "MIT", "optional": true }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/webpack": { "version": "5.105.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", @@ -23074,6 +23419,19 @@ "node": ">=18" } }, + "node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "license": "MIT", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -23373,6 +23731,15 @@ "xml-js": "bin/cli.js" } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, "node_modules/xmlbuilder2": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.0.tgz", @@ -23389,6 +23756,12 @@ "node": ">=20.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index ed699d90a9..bac79fe112 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@openfga/frontend-utils": "^0.2.0-beta.11", "@openfga/sdk": "^0.9.5", "@openfga/syntax-transformer": "^0.2.1", + "@stackql/docusaurus-plugin-structured-data": "^1.3.2", "assert-never": "1.4.0", "clsx": "2.1.1", "path-browserify": "1.0.1", diff --git a/src/features/LandingPage/HeroHeader/HeroLogo.tsx b/src/features/LandingPage/HeroHeader/HeroLogo.tsx index 67531d867a..be445493bc 100644 --- a/src/features/LandingPage/HeroHeader/HeroLogo.tsx +++ b/src/features/LandingPage/HeroHeader/HeroLogo.tsx @@ -14,10 +14,8 @@ const HeroLogo: React.FC<HeroLogoProps> = ({ width = '413', height = '89', class fill="none" xmlns="http://www.w3.org/2000/svg" className={className} + aria-hidden="true" > - <title id="title" lang="en"> - OpenFGA Logo - + + + +
From 82b6a7635739bf717161a4d2ce04ab394e942079 Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Thu, 21 May 2026 20:05:31 -0300 Subject: [PATCH 02/13] feat: adding more content pages --- docs/content/adopters/agicap.mdx | 53 +++++++ docs/content/adopters/docker.mdx | 46 ++++++ docs/content/adopters/grafana.mdx | 55 +++++++ docs/content/adopters/headspace.mdx | 46 ++++++ docs/content/adopters/openlane.mdx | 44 ++++++ docs/content/adopters/overview.mdx | 38 +++++ docs/content/adopters/read-ai.mdx | 48 ++++++ docs/content/adopters/vitrolife.mdx | 47 ++++++ docs/content/adopters/zuplo.mdx | 54 +++++++ docs/content/alternatives/overview.mdx | 147 ++++++++++++++++++ docs/content/compare/auth0-fga.mdx | 37 +++++ docs/content/compare/cerbos.mdx | 43 +++++ docs/content/compare/keto.mdx | 39 +++++ docs/content/compare/keycloak.mdx | 58 +++++++ docs/content/compare/opa.mdx | 39 +++++ docs/content/compare/oso.mdx | 42 +++++ docs/content/compare/overview.mdx | 33 ++++ docs/content/compare/permit.mdx | 42 +++++ docs/content/compare/spicedb.mdx | 43 +++++ docs/content/compare/workos.mdx | 44 ++++++ .../setup-openfga/configuration.mdx | 56 +++++-- docs/content/industries/banking.mdx | 57 +++++++ docs/content/industries/crm.mdx | 54 +++++++ docs/content/industries/ecommerce.mdx | 60 +++++++ docs/content/industries/healthcare.mdx | 63 ++++++++ docs/content/industries/human-resources.mdx | 52 +++++++ docs/content/industries/lms.mdx | 54 +++++++ docs/content/industries/overview.mdx | 25 +++ docs/content/intro.mdx | 30 ++++ docs/content/learn/abac-vs-rebac.mdx | 44 ++++++ .../learn/fine-grained-authorization.mdx | 38 +++++ docs/content/learn/overview.mdx | 17 ++ docs/content/learn/policy-engine.mdx | 37 +++++ docs/content/learn/rbac-vs-rebac.mdx | 39 +++++ docs/content/learn/rebac.mdx | 41 +++++ docs/content/learn/zanzibar.mdx | 43 +++++ .../use-cases/ai-agent-authorization.mdx | 34 ++++ .../use-cases/mcp-server-authorization.mdx | 36 +++++ .../use-cases/microservices-authorization.mdx | 35 +++++ docs/content/use-cases/multi-tenant-saas.mdx | 41 +++++ docs/content/use-cases/overview.mdx | 25 +++ docs/content/use-cases/rag-authorization.mdx | 33 ++++ docs/sidebars.js | 84 ++++++++++ docusaurus.config.js | 4 + .../LandingPage/FeaturesSection/index.tsx | 4 +- src/features/LandingPage/HeroHeader/index.tsx | 15 ++ src/theme/NotFound.tsx | 2 +- static/robots.txt | 4 + 48 files changed, 2008 insertions(+), 17 deletions(-) create mode 100644 docs/content/adopters/agicap.mdx create mode 100644 docs/content/adopters/docker.mdx create mode 100644 docs/content/adopters/grafana.mdx create mode 100644 docs/content/adopters/headspace.mdx create mode 100644 docs/content/adopters/openlane.mdx create mode 100644 docs/content/adopters/overview.mdx create mode 100644 docs/content/adopters/read-ai.mdx create mode 100644 docs/content/adopters/vitrolife.mdx create mode 100644 docs/content/adopters/zuplo.mdx create mode 100644 docs/content/alternatives/overview.mdx create mode 100644 docs/content/compare/auth0-fga.mdx create mode 100644 docs/content/compare/cerbos.mdx create mode 100644 docs/content/compare/keto.mdx create mode 100644 docs/content/compare/keycloak.mdx create mode 100644 docs/content/compare/opa.mdx create mode 100644 docs/content/compare/oso.mdx create mode 100644 docs/content/compare/overview.mdx create mode 100644 docs/content/compare/permit.mdx create mode 100644 docs/content/compare/spicedb.mdx create mode 100644 docs/content/compare/workos.mdx create mode 100644 docs/content/industries/banking.mdx create mode 100644 docs/content/industries/crm.mdx create mode 100644 docs/content/industries/ecommerce.mdx create mode 100644 docs/content/industries/healthcare.mdx create mode 100644 docs/content/industries/human-resources.mdx create mode 100644 docs/content/industries/lms.mdx create mode 100644 docs/content/industries/overview.mdx create mode 100644 docs/content/learn/abac-vs-rebac.mdx create mode 100644 docs/content/learn/fine-grained-authorization.mdx create mode 100644 docs/content/learn/overview.mdx create mode 100644 docs/content/learn/policy-engine.mdx create mode 100644 docs/content/learn/rbac-vs-rebac.mdx create mode 100644 docs/content/learn/rebac.mdx create mode 100644 docs/content/learn/zanzibar.mdx create mode 100644 docs/content/use-cases/ai-agent-authorization.mdx create mode 100644 docs/content/use-cases/mcp-server-authorization.mdx create mode 100644 docs/content/use-cases/microservices-authorization.mdx create mode 100644 docs/content/use-cases/multi-tenant-saas.mdx create mode 100644 docs/content/use-cases/overview.mdx create mode 100644 docs/content/use-cases/rag-authorization.mdx create mode 100644 static/robots.txt diff --git a/docs/content/adopters/agicap.mdx b/docs/content/adopters/agicap.mdx new file mode 100644 index 0000000000..2335c6cdc8 --- /dev/null +++ b/docs/content/adopters/agicap.mdx @@ -0,0 +1,53 @@ +--- +title: Agicap Case Study +description: How European fintech Agicap runs OpenFGA in production for 8,000+ customers at 250 RPS with conditional ReBAC across every backend service. +sidebar_position: 2 +slug: /adopters/agicap +--- + +# Agicap: Fine-grained authorization for a European fintech platform + +[Agicap](https://agicap.com) is a European fintech that helps small, medium, and large enterprises manage cash flow in real time. Its SaaS platform serves more than 8,000 customers across industries, and every backend service in the platform validates access through OpenFGA. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Fintech / cash flow management | +| **In production since** | April 2023 | +| **Scale** | ~250 requests per second, 8,000+ customers | +| **Deployment** | Self-hosted, on-premises | +| **Key features used** | ReBAC, conditional relationships | + +## Why OpenFGA + +Agicap needed an open-source authorization layer with a strong community, on-premises deployment for compliance, and a model flexible enough to express financial-product permissions that pure RBAC could not. They evaluated alternatives such as Oso and concluded OpenFGA was the most stable option that fit those requirements, with approachable maintainers and clear documentation. + +The team specifically chose ReBAC over an RBAC redesign because it let them express fine-grained relationships without re-inventing authorization logic inside every service. Learn more about that trade-off in [RBAC vs ReBAC](/docs/authorization-concepts). + +## Architecture and scale + +- All backend services call OpenFGA via an internal **secure facade** rather than the OpenFGA API directly. The facade enforces application-level rules on top of OpenFGA so the data plane is never exposed. +- Authorization is enforced consistently across development, pre-production, load-test, and production environments. +- Performance work over time pushed Agicap from a deeper hierarchy to a flatter authorization model, which improved both query latency and scalability — a pattern documented in the [performance best practices](/docs/best-practices). + +## Engineering with the community + +Agicap is an active upstream contributor: + +- Engineers from the platform and SRE teams open pull requests against `openfga/openfga` to fix bugs and tune performance. +- The team participates in the monthly OpenFGA community call. +- Agicap has co-presented OpenFGA talks with maintainers at KubeCon EU 2024 (Paris) and KubeCon NA 2024 (Salt Lake City). + +When the team filed a critical performance issue, the upstream maintainers shipped a fix within 24 hours. + +## Outcomes + +- A single, evolvable authorization layer behind every backend service. +- Faster delivery of new permissions — schema changes replace code changes. +- Cost savings from running self-hosted instead of a proprietary alternative. +- Confidence at production scale with 8,000+ customers and continuous traffic. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Pauline Jamin, Head of Engineering – Finance and Core at Agicap, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga), and a [presentation in the OpenFGA community meeting on Agicap's OpenFGA deployment](https://www.youtube.com/watch?v=XBHqGFfe-K4). diff --git a/docs/content/adopters/docker.mdx b/docs/content/adopters/docker.mdx new file mode 100644 index 0000000000..40d331c670 --- /dev/null +++ b/docs/content/adopters/docker.mdx @@ -0,0 +1,46 @@ +--- +title: Docker Case Study +description: How Docker migrated to OpenFGA with a parallel-run strategy and now uses ReBAC to centralize permissions across products. +sidebar_position: 3 +slug: /adopters/docker +--- + +# Docker: Centralizing permissions with ReBAC + +[Docker](https://www.docker.com) provides tools that help developers build, share, run, and verify applications across environments. Docker adopted OpenFGA in early 2024 and uses it to centralize authorization across an expanding set of products. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Developer tools / platform | +| **In production since** | March 2024 | +| **Scale** | 100-150 requests per second | +| **Deployment** | Self-hosted | +| **Key features used** | ReBAC, DSL, SDKs and CLI | + +## Why OpenFGA + +Docker evaluated several access-control systems — including Authzed and Ory Keto, plus non-ReBAC options — before choosing OpenFGA. The decision came down to: + +- **ReBAC** as a more flexible model than RBAC for the products Docker builds. +- **Self-hosted, open source**, easy to run locally (a working stack via Docker Compose in under five minutes). +- **CNCF backing** and contributors with strong security pedigree. +- Mature **SDKs, APIs, and testing tools**. +- A responsive maintainer community. + +## Migration approach + +Docker ran OpenFGA in **parallel with the existing authorization system**: every permission check went to both engines, and results were compared. Once both systems consistently agreed, traffic was incrementally cut over to OpenFGA. The parallel-run pattern is one we recommend for any production migration — see the [adoption patterns guide](/docs/best-practices). + +## Outcomes + +- Permission changes that previously required code changes are now centralized in the authorization model file. +- New Docker products integrate into the access-control system faster. +- Operational overhead for permission updates dropped substantially. + +The early scaling pain points the team hit — particularly batch checks across many records — were addressed quickly by upstream releases. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Gurleen Sethi, Senior Software Engineer at Docker, Inc., available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/grafana.mdx b/docs/content/adopters/grafana.mdx new file mode 100644 index 0000000000..2650ee325c --- /dev/null +++ b/docs/content/adopters/grafana.mdx @@ -0,0 +1,55 @@ +--- +title: Grafana Labs Case Study +description: Why Grafana Labs replaced its single-tenant access control engine with OpenFGA to power multi-tenant Grafana Cloud and embedded OSS deployments. +sidebar_position: 4 +slug: /adopters/grafana +--- + +# Grafana Labs: From single-tenant engine to multi-tenant ReBAC + +[Grafana Labs](https://grafana.com) is the company behind Grafana, Loki, Tempo, Mimir, and the LGTM observability stack. Grafana adopted OpenFGA to replace an internal single-tenant access-control engine that no longer fit the multi-tenant architecture of Grafana Cloud. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Observability | +| **First experiments** | February 2024 | +| **Mainline integration** | August 2024 | +| **Version** | v1.10.0 | +| **Deployment** | Multi-tenant SaaS, embedded OSS, on-premises | + +## Why OpenFGA + +Grafana needed an engine that did **two** things competitors did not bundle: + +1. **Authorization evaluation** — like an OPA-style policy engine. +2. **A storage layer for permissions** — a tuple store with a per-tenant schema. + +That combination, plus OpenFGA's **CNCF affiliation** and explicit governance policy, made it preferable to building yet another in-house system or adopting a project that could change its license later. + +## Architecture and scale + +OpenFGA runs in three Grafana environments: + +- **Development and staging** — already serving internal production workloads. +- **External production** — deployed to a single cluster in a pre-production capacity, shadowing real traffic to validate consistency and performance before broader rollout. + +The team standardized on the **PostgreSQL adapter** after finding the MySQL adapter less mature. Refactoring Grafana's legacy schema toward OpenFGA-native modeling produced significant performance gains — an outcome echoed by the [source-of-truth best practice](/docs/best-practices). + +## Upstream investment + +- Grafana **maintains the SQLite adapter**, which was contributed back to OpenFGA so it can ship with embedded Grafana. +- One Grafana engineer is listed in `SECURITY-INSIGHTS.yml` as a core maintainer; three others have contributed changes. +- Future areas of contribution include **pluggable storage** (so non-core storage adapters work without rebuilding OpenFGA) and **observability** improvements. +- KubeCon EU 2025 talk: *From Chaos To Control: Migrating Access Control* by Jo Guerreiro and Poovamraj Thanganadar Thiagarajan. + +## Outcomes + +- One authorization platform spans Grafana Cloud (multi-tenant SaaS) and Grafana OSS (embedded), removing the need to maintain separate engines. +- Schema-driven iteration replaced engine-tuning work the team used to do manually. +- The team is targeting **list-users** to enable reverse permission search — showing all users who can access a given resource — a capability the legacy engine never had. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Joao Guerreiro, Senior Engineering Manager at Grafana Labs, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/headspace.mdx b/docs/content/adopters/headspace.mdx new file mode 100644 index 0000000000..91d8e8f35c --- /dev/null +++ b/docs/content/adopters/headspace.mdx @@ -0,0 +1,46 @@ +--- +title: Headspace Case Study +description: How Headspace authorizes Ebb, its empathetic AI companion, with OpenFGA — driving end-to-end checks down from 10–15 seconds to 10–15 milliseconds. +sidebar_position: 6 +slug: /adopters/headspace +--- + +# Headspace: Authorizing an empathetic AI companion at consumer scale + +[Headspace](https://www.headspace.com) is a global mental-health platform with over 105 million app downloads and 90 million lives reached. Its AI companion, Ebb, has handled more than 6 million conversations since launching, and every message Ebb processes runs through an OpenFGA authorization check. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Mental health / consumer health | +| **Use case** | AI companion (Ebb) gating | +| **Scale** | 90M+ lives, 105M+ downloads, 6M+ Ebb messages | +| **Deployment** | Self-hosted | +| **Key features used** | BatchCheck, contextual tuples, graph design, Terraform-managed model | + +## Why OpenFGA + +Ebb is gated on a combination of business rules: who the member is contracted through, which country they are messaging from, which language their app is set to, and whether their employer has opted them out. A pure RBAC system could not express this without exploding into a role per combination, and a hand-rolled SQL check ran 10–15 seconds in the worst case — unacceptable for a chat experience. + +The Headspace team chose OpenFGA so the AI gating rules could live in a single relationship graph the platform team owned, with the same model evaluated from every service that fronts Ebb. + +## Architecture + +- **Wrapper API in front of OpenFGA.** Application services do not call the OpenFGA store directly. They call an internal authorization service that fans out **four parallel [BatchCheck](/docs/interacting/relationship-queries) requests** — assigned-to-Ebb, country-allowed, language-allowed, and not-blocked-by-org — and combines the results. +- **Inverted graph for performance.** The original model put the AI feature at the top with users below; a check meant traversing the entire user population. Flipping the direction so the user is the object and Ebb access is reached through unions of small relations dropped end-to-end latency from 10–15 seconds to 10–15 milliseconds. +- **Bidirectional tuple writes.** When a member-to-feature relationship is written, the inverse tuple is written at the same time, keeping reads cheap in either direction. +- **Terraform-managed model and static tuples.** The authorization model and the static enablement tuples (countries, languages, default org policies) ship through the Headspace [OpenFGA Terraform provider](https://github.com/openfga/terraform-provider-openfga), so model changes go through the same review pipeline as infrastructure. +- **Hidden model version.** The wrapper API does not expose the OpenFGA model ID to consumers; rolling forward to a new model version is a deploy of the wrapper, not a coordinated change across every caller. +- **SDK 1.10 conflict resolution.** The team adopted the conflict-resolution behavior shipped in SDK 1.10 to safely handle concurrent tuple writes during high-traffic enrollment events. + +## Outcomes + +- **End-to-end Ebb authorization in 10-15 ms**, down from 10-15 seconds. +- **Per-user blocking added without touching call sites** — a new relation in the model and a tuple write was enough; no service had to ship code. +- **Single source of truth** for AI gating rules, owned by the platform team and reviewed in Terraform. +- **Operational headroom** to extend Ebb gating (new languages, new contracts, new opt-out criteria) without rewriting application code. + +## Source + +This case study is based on a [presentation in the OpenFGA community meeting by Jeremy, principal engineer at Headspace](https://www.youtube.com/watch?v=xCu39aG7B1A). Supporting public material on Ebb is available at [headspace.com](https://www.headspace.com). diff --git a/docs/content/adopters/openlane.mdx b/docs/content/adopters/openlane.mdx new file mode 100644 index 0000000000..e54c98b879 --- /dev/null +++ b/docs/content/adopters/openlane.mdx @@ -0,0 +1,44 @@ +--- +title: OpenLane Case Study +description: How compliance-automation startup OpenLane wires OpenFGA into ent at the data-access layer, with overfetch + BatchCheck replacing slow ListObjects. +sidebar_position: 7 +slug: /adopters/openlane +--- + +# OpenLane: Authorization at the data-access layer for compliance automation + +[OpenLane](https://theopenlane.io) is an open-source compliance automation platform that helps teams achieve and maintain SOC 2, ISO 27001, and similar attestations. OpenFGA is wired into its data-access layer, so every GraphQL query and mutation is authorized without each resolver having to remember to ask. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Compliance automation (GRC) | +| **Stack** | Go, GraphQL (gqlgen), [ent](https://entgo.io/) ORM, PostgreSQL, Kubernetes | +| **Deployment** | Self-hosted; separate Postgres databases for application data and OpenFGA | +| **Key features used** | BatchCheck, contextual tuples, object-owned cascading permissions, FGA-driven feature flags | + +## Why OpenFGA + +OpenLane builds the kind of platform whose customers will themselves be audited. Authorization had to be defensible end-to-end — every read, every write, every export — and could not live in a `if user.role == "admin"` switch sprinkled across resolvers. The team picked OpenFGA so authorization decisions were centralized, modeled as relationships, and could evolve without code changes in every service. + +A second motivation was packaging: OpenLane sells modules, and the same engine that grants access to a record can grant access to a feature. OpenFGA is the source of truth for **both**. + +## Architecture + +- **Authorization as ent middleware.** OpenLane uses [ent](https://entgo.io/) hooks to write tuples on every mutation and interceptors plus policies to evaluate every query. Resolvers do not call the OpenFGA SDK directly; the data-access layer does. +- **Object-owned mixin.** A reusable ent mixin attaches `owner` and parent relations to any record type, so cascading permissions ("an editor of the parent program can edit each control") are declared once and applied uniformly. +- **Overfetch + BatchCheck instead of ListObjects.** An early implementation used [ListObjects](/docs/interacting/relationship-queries) and saw ~8-second worst-case latency for large result sets. The team switched to overfetching candidates from Postgres (capped at 100 per page, up to 1,000 overfetched) and running [BatchCheck](/docs/interacting/relationship-queries) to filter in a single round trip. Total counts are computed via a separate query that short-circuits when the user is an admin. +- **In-house wrapper packages.** Three small Go packages — `FGX` (typed helpers around the OpenFGA SDK), `NFGA` (no-op fakes for unit tests), and `access-map` (declarative relation registration) — keep callers honest and make adding a new entity type a few lines of mixin configuration. +- **OpenFGA-as-feature-flags.** Module entitlements ("does this tenant have the policy module?") live in the same OpenFGA store as record-level permissions, so a check that returns `false` because the user is not an editor and a check that returns `false` because the tenant did not buy the module use the same call site. + +## Outcomes + +- **Authorization can't be forgotten.** Hooks and interceptors mean a new entity type inherits authorization automatically. +- **List-style endpoints went from ~8 s worst case to comfortably under a second** at expected page sizes, without changing the public API. +- **One store, two jobs.** Record permissions and feature entitlements live in OpenFGA, so packaging changes don't require a second policy system. +- **Auditable end-to-end.** Tuple writes happen in the same transaction boundary as the underlying data mutation, so the access graph and the data it protects don't drift. + +## Source + +This case study is based on a [presentation in the OpenFGA community meeting by Sarah Funkhouser, co-founder and head of engineering at OpenLane](https://www.youtube.com/watch?v=ZdlftEKQ0UA). The OpenLane platform itself is open source — the integration patterns above are visible in the [theopenlane GitHub organization](https://github.com/theopenlane). diff --git a/docs/content/adopters/overview.mdx b/docs/content/adopters/overview.mdx new file mode 100644 index 0000000000..a989e8577a --- /dev/null +++ b/docs/content/adopters/overview.mdx @@ -0,0 +1,38 @@ +--- +title: OpenFGA Adopters and Case Studies +description: Production OpenFGA case studies from Agicap, Docker, Grafana Labs, Headspace, OpenLane, Read AI, Vitrolife, Zuplo and other adopters running fine-grained authorization at scale. +sidebar_position: 1 +slug: /adopters +--- + +# OpenFGA in production + +OpenFGA is deployed in production at fintechs, observability platforms, AI products, developer-tool companies, and API platforms. The case studies below are based on public [CNCF TOC adopter interviews](https://github.com/cncf/toc/tree/main/projects/openfga). + +## Featured case studies + +| Adopter | Industry | In production since | Scale | +| --- | --- | --- | --- | +| [Read AI](/docs/adopters/read-ai) | AI meeting intelligence | April 2023 | 5,200 RPS peak, 5.3B+ tuples | +| [Agicap](/docs/adopters/agicap) | Fintech | April 2023 | ~250 RPS, 8,000+ customers | +| [Zuplo](/docs/adopters/zuplo) | API management | 2024 | 500+ RPS spikes, multi-region edge | +| [Grafana Labs](/docs/adopters/grafana) | Observability | 2024 | Multi-tenant SaaS + embedded OSS | +| [Docker](/docs/adopters/docker) | Developer tools | March 2024 | 100–150 RPS | +| [Headspace](/docs/adopters/headspace) | Mental health & consumer | 2024 | 90M lives, 6M Ebb messages, 10-15 ms p99 | +| [OpenLane](/docs/adopters/openlane) | Compliance SaaS | 2024 | ent ORM hooks, BatchCheck overfetch (100/1000) | +| [Vitrolife Group](/docs/adopters/vitrolife) | Healthcare | 2025 | Hybrid Entra + OpenFGA, hourly differential sync | + +## What these adopters have in common + +- **Self-hosted, open source.** Every adopter cited the ability to run OpenFGA themselves as a key reason for choosing it over proprietary offerings. +- **PostgreSQL at scale.** Production deployments are running on Postgres, with billions of tuples in the largest case. +- **ReBAC over RBAC.** Each team chose relationship-based access control for the flexibility it gives over flat role models. See [authorization concepts](/docs/authorization-concepts) for a refresher. +- **CNCF governance** matters. Teams explicitly contrasted CNCF stewardship with the licensing risk of source-available alternatives. + +## Adopter list + +OpenFGA is also publicly used by organizations including [Twilio](https://www.twilio.com), [Italia.it (Italian Government)](https://www.pagopa.gov.it/), [Mercado Libre](https://www.mercadolibre.com), [Wolt](https://wolt.com), [Canonical](https://canonical.com), and many more. The full, machine-readable list is maintained in the [`openfga/community` repository](https://github.com/openfga/community/blob/main/ADOPTERS.md). + +## Add your story + +If your team runs OpenFGA in production and wants to share lessons learned, open a pull request against the [`openfga/community` ADOPTERS file](https://github.com/openfga/community/blob/main/ADOPTERS.md) or join the [CNCF Slack `#openfga` channel](https://cloud-native.slack.com/archives/C06G1NNH47N). diff --git a/docs/content/adopters/read-ai.mdx b/docs/content/adopters/read-ai.mdx new file mode 100644 index 0000000000..a4f802e4eb --- /dev/null +++ b/docs/content/adopters/read-ai.mdx @@ -0,0 +1,48 @@ +--- +title: Read AI Case Study +description: How Read AI runs OpenFGA at 5,200 requests per second with 20ms p99 latency over more than 5 billion relationship tuples. +sidebar_position: 5 +slug: /adopters/read-ai +--- + +# Read AI: 5 billion tuples, 20ms p99 latency + +[Read AI](https://www.read.ai) is the AI meeting notetaker and assistant trusted by more than 100,000 organizations and 75% of the Fortune 500, adding more than one million new customers every month. OpenFGA backs the authorization layer that lets Read AI safely share intelligence across meetings, messages, email, and documents. + +## At a glance + +| | | +| --- | --- | +| **Industry** | AI productivity / meeting intelligence | +| **In production since** | April 28, 2023 | +| **Peak load** | 5,200 RPS | +| **Latency** | 20ms p99 / 1.8ms average | +| **Tuple count** | 5,323,283,829 (and growing) | +| **Version** | v1.8.16 | +| **Storage** | PostgreSQL | + +## Why OpenFGA + +Read AI ran a proprietary, organically built authorization system that hit performance and scalability ceilings as the platform grew. The team evaluated alternatives such as Authzed before choosing OpenFGA, citing: + +- **Zanzibar foundations** that aligned with the sharing semantics the product needed. +- **Documentation clarity**, especially the practical examples and modeling guides. +- The ability to **self-host** with predictable cost. +- Approachable, responsive maintainers. + +## Production at scale + +The self-hosted OpenFGA service handles peak load of **5,200 requests per second** with a **20ms p99 latency** and **1.8ms average latency**. The data store holds more than **5.3 billion tuples** and grows daily. + +OpenFGA upgrades are folded into a monthly cadence. The OpenFGA release pace is faster than Read AI's, but upgrades have been smooth with no significant backward-compatibility issues. + +## Outcomes + +- Confidence in secure data authorization across the entire product surface. +- Adoption of ReBAC best practices improved internal design decisions. +- Compute and hosting costs dropped versus the prior solution. +- OpenFGA has not been the bottleneck even at peak. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Andrew Powers, Software Engineering Manager at Read AI, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/vitrolife.mdx b/docs/content/adopters/vitrolife.mdx new file mode 100644 index 0000000000..08ae367e81 --- /dev/null +++ b/docs/content/adopters/vitrolife.mdx @@ -0,0 +1,47 @@ +--- +title: Vitrolife Group Case Study +description: How Vitrolife combines Microsoft Entra ID app roles with OpenFGA fine-grained ReBAC for a .NET 10 metadata platform serving IVF clinics worldwide. +sidebar_position: 8 +slug: /adopters/vitrolife +--- + +# Vitrolife Group: Hybrid Entra + OpenFGA authorization for a .NET healthcare platform + +The [Vitrolife Group](https://www.vitrolife.com) is a Swedish medical-device and software company serving IVF clinics worldwide. Its internal metadata platform — built on .NET 10 to organize landing zones, domains, platforms, and teams — uses a hybrid authorization design: Microsoft Entra ID app roles for coarse-grained access and OpenFGA for fine-grained, per-resource decisions. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Healthcare / medical devices (IVF) | +| **Stack** | .NET 10, ASP.NET Core, Microsoft Entra ID, [Wolverine](https://wolverinefx.net/), Aspire | +| **Use case** | Internal metadata platform: landing zones, domains, platforms, teams | +| **Deployment** | Self-hosted alongside the .NET API | +| **Key features used** | Contextual tuples, intersections, conditions, full + differential sync from OpenFGA | + +## Why OpenFGA + +Entra ID gave Vitrolife a managed identity layer for both human users and service principals, but app roles alone could not express *per-resource* permissions ("can edit *this* landing zone, not all of them"). The platform team layered OpenFGA underneath to model ownership and group membership without inventing a parallel directory. + +Crucially, the team wanted OpenFGA — not Entra — to be the **source of truth** for access groups, so that the application's own model of "who has access to what" did not depend on a directory operation in a separate system. + +## Architecture + +- **App-role grammar.** Every Entra app role follows `...`. Capabilities are `viewer` / `editor` / `admin`. The qualifier is either `all` (the principal may act on every record of that resource) or `self` (the principal may act only on records they have an OpenFGA relationship with). `all` injects a contextual tuple at request time; `self` falls through to a normal [Check](/docs/interacting/relationship-queries) against the relationship graph. +- **Users and service principals are uniform.** Because both human users and service principals carry app roles, the application code never branches on principal type — the OpenFGA tuple set treats them identically. +- **Type-safe C# wrappers.** `FGAObjectId` enforces `:` shape at compile time. An `FGATuple` builder makes tuple construction explicit, and relations are modeled as `snake_case` enums to match the OpenFGA wire format without stringly-typed bugs. +- **Group memberships as contextual tuples.** Entra group memberships are surfaced into OpenFGA via contextual tuples on each request, intersected with FGA group definitions so a principal must satisfy *both* the Entra group and the FGA relationship to gain access. +- **Atomic writes via transactional outbox.** SQL writes and OpenFGA tuple writes are coordinated through a [Wolverine](https://wolverinefx.net/) outbox: the database row and the queued FGA mutation commit together; consumers process the outbox to make the tuple change visible. This is eventually consistent within a small, bounded window. +- **OpenFGA → Entra sync.** A scheduled job reads OpenFGA as the source of truth and reconciles Entra access groups: an hourly **full sync** plus a **differential sync** triggered by outbox events. If a tuple is removed in OpenFGA, the corresponding Entra membership is removed on the next pass. +- **Vertical slices and Aspire local dev.** Features are organized as vertical slices; .NET Aspire orchestrates a local environment with a mocked Microsoft Graph and a local OpenFGA, so the team can run the full authorization path end-to-end on a developer laptop. + +## Outcomes + +- **One mental model for authorization** that spans humans, service principals, and resources, with Entra handling identity and OpenFGA handling relationships. +- **Per-resource permissions** without a custom directory — the existing Entra investment stays in place. +- **OpenFGA as the durable source of truth** for access groups, with Entra reconciled on a schedule rather than the other way around. +- **Atomic SQL + tuple writes** through the Wolverine outbox, removing the class of bugs where the application data and the access graph drift apart. + +## Source + +This case study is based on a [presentation in the OpenFGA community meeting by Simon Gottschlag, CTO at Co-native, working with the Vitrolife Group on its platform](https://www.youtube.com/watch?v=nwu5SiiMpM8). diff --git a/docs/content/adopters/zuplo.mdx b/docs/content/adopters/zuplo.mdx new file mode 100644 index 0000000000..72512d4819 --- /dev/null +++ b/docs/content/adopters/zuplo.mdx @@ -0,0 +1,54 @@ +--- +title: Zuplo Case Study +description: How API management platform Zuplo runs OpenFGA across multiple data centers with PostgreSQL global replication for edge authorization. +sidebar_position: 6 +slug: /adopters/zuplo +--- + +# Zuplo: Edge authorization across multiple data centers + +[Zuplo](https://zuplo.com) is a developer-first API management platform that helps teams build, deploy, and scale APIs globally. Zuplo uses OpenFGA to enforce fine-grained authorization at the edge across every region the platform runs in. + +## At a glance + +| | | +| --- | --- | +| **Industry** | API management | +| **In production since** | ~2024 (12 months in production after 18 months total adoption) | +| **Scale** | Several hundred RPS, with spikes above 500 RPS | +| **Version** | v1.8.x | +| **Storage** | PostgreSQL with global replication | +| **Deployment** | Multi-region edge | + +## Why OpenFGA + +As Zuplo's customer base shifted toward larger enterprises, simple project-membership rules were no longer enough. The team evaluated: + +- Axiomatics (AuthZEN-based) +- Aserto (AuthZEN-based) +- Okta FGA +- Building a custom solution in-house + +They chose OpenFGA because it is **open source and self-hostable**, and because **PostgreSQL as a backend** let Zuplo replicate authorization data globally and run checks at the edge — exactly the topology API management requires. + +## Architecture + +- **One authorization model** governs the entire product, single-tenant. +- The same model handles **user access and API key access** to product features. +- OpenFGA is deployed in production across multiple data centers worldwide. +- Performance work for major upgrades uses k6-based end-to-end load tests. + +## Outcomes + +- Updating and versioning the authorization model independently of the application code accelerated development cycles. +- Roles and permissions can be tested and refined without code changes. +- Authorization is centralized across teams while keeping concerns separate. +- Caching introduced upstream replaced a homegrown cache layer Zuplo had built before OpenFGA shipped its own. + +## Outlook + +Zuplo continues to file feature requests upstream and has expressed interest in publicly sharing more details about how it implements authorization at the edge. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Nate Totten, Co-founder & CTO of Zuplo, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/alternatives/overview.mdx b/docs/content/alternatives/overview.mdx new file mode 100644 index 0000000000..3980c55e7c --- /dev/null +++ b/docs/content/alternatives/overview.mdx @@ -0,0 +1,147 @@ +--- +title: OpenFGA Alternatives +description: An honest guide to OpenFGA alternatives — SpiceDB, Cerbos, Permit.io, Oso, Topaz, Permify, Ory Keto, OPA — and when each is the better fit. +sidebar_position: 1 +slug: /alternatives +--- + +# OpenFGA Alternatives + +If you're evaluating OpenFGA, you're almost certainly evaluating something else alongside it. This page is an honest hub: a short summary of each serious alternative, what makes it distinctive, and the cases where it is genuinely the better choice over OpenFGA. + +The authorization ecosystem in 2026 is crowded, and "best" depends on where the work lives — application code, infrastructure admission control, or a hosted control plane. Pick the engine whose centre of gravity matches your problem, not whichever has the loudest landing page. + +## Quick comparison + +| Alternative | Style | Hosted option | License | Best fit | +| --- | --- | --- | --- | --- | +| [SpiceDB](#spicedb) | Zanzibar / ReBAC | Authzed | Apache 2.0 | Want a Zanzibar engine with a commercial vendor behind it | +| [Cerbos](#cerbos) | PBAC (YAML policies) | Cerbos Hub | Apache 2.0 (PDP) | Stateless, attribute-driven decisions across many services | +| [Permit.io](#permit-io) | Control plane over OPA/Cedar | Hosted-first | Commercial | Want a SaaS UI on top of an OPA/Cedar engine | +| [Oso](#oso) | Polar DSL | Oso Cloud | Commercial (Cloud) | Want a managed engine with a single application-focused DSL | +| [Topaz](#topaz) | Zanzibar + Rego (hybrid) | Aserto | Apache 2.0 | Want ReBAC and Rego in one process, edge-deployed | +| [Permify](#permify) | Zanzibar / ReBAC | Permify Cloud | Apache 2.0 | Want a Zanzibar engine with a smaller, simpler surface | +| [Ory Keto](#ory-keto) | Zanzibar / ReBAC | Ory Network | Apache 2.0 + Enterprise | Already inside the Ory ecosystem (Kratos / Hydra) | +| [OPA](#opa) | General-purpose policy (Rego) | Self-hosted | Apache 2.0 (CNCF graduated) | Infrastructure / admission control, not application authorization | + +## When OpenFGA is the right pick + +Before the alternatives, the short version of where OpenFGA fits: + +- You want a **Zanzibar-style relationship engine** — typed model, tuple store, fast Check, reverse queries. +- You want it **CNCF-governed and self-hostable** on PostgreSQL, MySQL, or SQLite, with an in-memory mode for tests. +- You want **fine-grained, per-resource authorization** — document sharing, multi-tenant SaaS, AI agent / RAG access — rather than coarse role checks. +- You're comfortable owning the deployment (operator, Helm chart), or you're already using a hosted OpenFGA service such as **Auth0 FGA / Okta FGA** — managed OpenFGA from Auth0/Okta, the original creators who donated the project to the CNCF. + +If those bullets describe your problem, OpenFGA is purpose-built for it and you're in the right place. The rest of this page is for cases where the answer is genuinely something else. + +## SpiceDB + +[SpiceDB](https://authzed.com/spicedb) is the closest peer — a Zanzibar implementation with a typed schema language, multiple storage backends, and a Kubernetes operator. It's Apache 2.0 with a commercial vendor (Authzed) selling a managed and dedicated offering. + +**Pick SpiceDB over OpenFGA when:** + +- You specifically want a single commercial vendor on the support contract and roadmap, not a CNCF-governed project with multiple maintainers from different companies. +- You're already standardised on Authzed Cloud or Dedicated for hosting. +- Your team prefers SpiceDB's schema language ergonomics — both DSLs are expressive, and personal preference is a fine reason to choose. + +OpenFGA and SpiceDB will solve nearly the same problems. The decision usually comes down to governance model and which DSL the team finds easier to read. See the side-by-side: [OpenFGA vs SpiceDB](/docs/compare/spicedb). + +## Cerbos + +[Cerbos](https://www.cerbos.dev/) is a stateless policy decision point. Policies are YAML files, kept in Git, and the PDP evaluates them against attributes you pass at request time. There's no relationship database — that's a feature, not a gap. + +**Pick Cerbos over OpenFGA when:** + +- Authorization is **mostly attribute-driven**: claims from the JWT, resource metadata you already have at hand, request context. +- You don't need reverse queries (*"list every resource this user can read"*) — you only need yes/no on a known resource. +- You want decisions to scale horizontally without a shared database behind them. + +The honest framing: Cerbos and OpenFGA are answers to *different* questions. Cerbos shines on stateless ABAC across many services. OpenFGA shines when relationships change at write time and you need to query the graph. Many teams end up running both. See [OpenFGA vs Cerbos](/docs/compare/cerbos). + +## Permit.io + +[Permit.io](https://www.permit.io/) is a hosted control plane built on top of open-source engines (OPA / OPAL, Cedar). It bundles a UI, audit logs, low-code policy editing, and an MCP gateway. + +**Pick Permit.io over OpenFGA when:** + +- You want a **managed product with a UI** that non-engineers can edit policy in, not a library you embed in your stack. +- You want compliance certifications (SOC2/HIPAA/GDPR) included in the hosted product rather than something you assemble. +- You're happy delegating the engine choice to the vendor — Permit decides whether OPA or Cedar evaluates a given request. + +If self-hosting is the requirement, Permit isn't the right shape. If managed-with-a-UI is the requirement, OpenFGA isn't. See [OpenFGA vs Permit.io](/docs/compare/permit). + +## Oso + +[Oso](https://www.osohq.com/) ships **Oso Cloud** — a managed authorization service driven by the Polar DSL. The on-prem open-source library is in maintenance; the active product is the cloud service. + +**Pick Oso over OpenFGA when:** + +- You want a **single managed service** and don't want to operate an authorization engine. +- You like the Polar DSL specifically. Polar is expressive in a different way than the Zanzibar-style DSLs — closer to a logic language. +- Your scale targets match Oso Cloud's published numbers and you're comfortable with the commercial terms. + +The deployment-model trade-off is the main fork: OpenFGA is built to be self-hosted; Oso's current centre of gravity is the cloud. See [OpenFGA vs Oso](/docs/compare/oso). + +## Topaz + +[Topaz](https://www.topaz.sh/) is Aserto's open-source authorizer. Its distinctive choice is **combining a Zanzibar-style relationship store with Rego** — Topaz lets you write attribute rules in OPA's policy language alongside relationships in a directory. + +**Pick Topaz over OpenFGA when:** + +- You want both ReBAC and Rego in **one process** and don't want to wire two engines together yourself. +- You're deploying authorizers **at the edge** of each application and want the authorizer to embed cleanly there. +- You want to use existing Rego skills on policies you already maintain for OPA. + +OpenFGA covers most attribute-driven cases via [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples) without adding Rego. If you want Rego specifically, Topaz is the right tool for that. + +## Permify + +[Permify](https://permify.co/) is another open-source Zanzibar implementation with a smaller surface than OpenFGA or SpiceDB. It supports Postgres for storage and ships a self-hosted server plus a managed cloud. + +**Pick Permify over OpenFGA when:** + +- You prefer Permify's specific schema syntax or developer ergonomics after trying both. +- The smaller community and smaller feature surface is a feature, not a drawback, for your team. + +OpenFGA has CNCF governance, multiple production references documented in the [adopters file](https://github.com/openfga/community/blob/main/ADOPTERS.md), and a larger SDK matrix. For most teams that's the deciding factor; if it's not, Permify is a perfectly reasonable Zanzibar engine. + +## Ory Keto + +[Ory Keto](https://www.ory.com/keto/) is a Zanzibar implementation inside the Ory ecosystem — alongside Kratos (identity), Hydra (OAuth2), and Oathkeeper (proxy). Apache 2.0 plus an Enterprise edition and the hosted Ory Network. + +**Pick Ory Keto over OpenFGA when:** + +- You're **already running the Ory stack** (Kratos for users, Hydra for OAuth, Oathkeeper for proxying) and want one vendor across the identity-and-access surface. +- You want Ory Network as the managed option. + +If you're not already in the Ory ecosystem, OpenFGA's modeling tools, SDK matrix, and CNCF governance generally make it the easier standalone choice. See [OpenFGA vs Ory Keto](/docs/compare/keto). + +## OPA + +[Open Policy Agent](https://www.openpolicyagent.org/) (OPA) is a CNCF **graduated** general-purpose policy engine. Rego policies evaluate against any JSON input — Kubernetes admission, Terraform, service-mesh, application requests. + +**Pick OPA over OpenFGA when:** + +- You're solving **infrastructure or admission-control** problems — Kubernetes admission webhooks, Terraform plan checks, Envoy authorization filters. +- The same policy must apply across **many domains**, not just one application's resource model. +- You want a stateless engine where the data is supplied per request, not stored in a graph. + +OPA and OpenFGA solve different layers of the authorization stack — see [Policy Engines vs Relationship Engines](/docs/learn/policy-engine) for the longer version. Many teams run both: OPA at the platform layer, OpenFGA inside applications. The detailed comparison: [OpenFGA vs OPA](/docs/compare/opa). + +## How to actually choose + +Three questions cut through the marketing: + +1. **Where does the data live?** If your authorization data is mostly *relationships that change at write time* (group membership, sharing, ownership, hierarchy), pick a relationship engine — OpenFGA, SpiceDB, Permify, Keto, Topaz. If it's mostly *attributes you already have on the request*, pick a policy engine — Cerbos, OPA. +2. **Who operates it?** If you want a managed product with a UI, look at Permit.io and Oso Cloud first. If you want to self-host with open-source governance, look at OpenFGA, SpiceDB, Cerbos PDP, OPA. +3. **What ecosystem are you already in?** Already running Ory? Keto. Already running Authzed? SpiceDB. Already running Aserto? Topaz. The integration cost of a second vendor is real. + +If after that the answer is OpenFGA, the [Getting Started](/docs/getting-started) guide is twenty minutes of work. If it's something else, the linked comparison pages and the alternative's own docs will get you the rest of the way. + +## Related reading + +- [Compare overview](/docs/compare) — side-by-side detail on each engine +- [Policy Engines vs Relationship Engines](/docs/learn/policy-engine) +- [What is Fine-Grained Authorization?](/docs/learn/fine-grained-authorization) +- [Customer case studies](/docs/adopters) diff --git a/docs/content/compare/auth0-fga.mdx b/docs/content/compare/auth0-fga.mdx new file mode 100644 index 0000000000..16ab897955 --- /dev/null +++ b/docs/content/compare/auth0-fga.mdx @@ -0,0 +1,37 @@ +--- +title: OpenFGA vs Auth0 FGA +description: Compare self-hosted OpenFGA with Auth0 FGA / Okta FGA, the managed OpenFGA service from the original creators. +sidebar_position: 9 +slug: /compare/auth0-fga +--- + +# OpenFGA vs Auth0 FGA + +[Auth0 FGA](https://docs.fga.dev/) is a managed authorization service built on OpenFGA. The engine, DSL, and API are the same — the difference is who operates the infrastructure, and what runs on top of it. + +## Side by side + +| | OpenFGA | Auth0 FGA | +| --- | --- | --- | +| Delivery | Self-hosted open source | Managed SaaS from Auth0/Okta | +| License | Apache 2.0 | Commercial | +| Engine & API | OpenFGA | OpenFGA | +| Availability | Self-managed | Multi-region, active-active | +| Upgrades & backups | Your responsibility | Handled by Auth0 | +| Dashboard | Local Playground | Hosted dashboard with SSO | +| Logging | You build the pipeline | Logging API with retention | +| Support | Community | 24/7 monitoring, enterprise support | + +## When to pick OpenFGA (self-hosted) + +You want the engine to run inside your own infrastructure — for data residency, air-gapped environments, or full control over database, version, and topology — and you're comfortable owning availability, backups, and upgrades. + +## When to pick Auth0 FGA / Okta FGA + +You want the same engine without operating it, or you're already on Auth0 / Okta for identity and want authorization in the same vendor relationship with enterprise support. + +## Migration shape + +Because Auth0 FGA is OpenFGA, moving in either direction is mostly an operational exercise — the model and tuples are portable, and SDK calls don't change. Most teams export the model and tuples and then cut over. + +> Auth0 FGA facts above are drawn from [docs.fga.dev](https://docs.fga.dev/openfga-vs-auth0-fga) as of May 2026. Verify current SLA, region availability, and pricing with Auth0/Okta before making a decision. diff --git a/docs/content/compare/cerbos.mdx b/docs/content/compare/cerbos.mdx new file mode 100644 index 0000000000..94489024e2 --- /dev/null +++ b/docs/content/compare/cerbos.mdx @@ -0,0 +1,43 @@ +--- +title: OpenFGA vs Cerbos +description: Compare OpenFGA's Zanzibar-style ReBAC with Cerbos's stateless YAML-policy PDP for RBAC, ABAC, and policy-based authorization. +sidebar_position: 3 +slug: /compare/cerbos +--- + +# OpenFGA vs Cerbos + +OpenFGA and [Cerbos](https://www.cerbos.dev) solve different shapes of the authorization problem. OpenFGA is a Zanzibar-style **relationship database**: it stores tuples and answers questions like *"can user A read document D?"* across deeply nested relationships. Cerbos is a **stateless policy decision point** that evaluates YAML policies against the principal, resource, and action you pass in on each request. + +## Side by side + +| | OpenFGA | Cerbos | +| --- | --- | --- | +| Primary model | ReBAC (Zanzibar) | RBAC / ABAC / PBAC, plus ReBAC patterns | +| Relationship storage | First-class — tuples are the source of truth | None — policies are stateless; data is passed in | +| Language | OpenFGA DSL | YAML policies | +| Storage | PostgreSQL, MySQL, SQLite | Stateless; policies on disk, in Git, or in Cerbos Hub | +| Reverse queries | `list-objects`, `list-users` | Not first-class (stateless) | +| Governance | CNCF Sandbox | Open source PDP, commercial Hub | + +## When ReBAC + a tuple store is the right tool + +If your access rules involve **graphs that change at write time** — folder hierarchies, organization membership, document sharing, multi-tenant teams — storing those edges centrally and querying them is exactly what OpenFGA does. + +Real production deployments of this shape: + +- [Read AI](/docs/adopters/read-ai): 5.3B+ tuples, 5,200 RPS, 20 ms p99. +- [Agicap](/docs/adopters/agicap): conditional ReBAC, 8,000+ customers. +- [Grafana Labs](/docs/adopters/grafana): multi-tenant SaaS plus embedded OSS, single authorization platform. + +## When Cerbos's stateless PDP is the right tool + +- Authorization rules are mostly **attribute-based** (role, department, resource owner) and the data needed to decide is already on the request. +- You want to keep authorization logic in Git as YAML and avoid running a database for permissions. +- You don't need reverse queries (*"list every document user X can read"*) — these inherently require a stored relationship graph. + +## Combining both + +Some teams pair them: OpenFGA for relationship-heavy permissions (membership, sharing, hierarchies), and a separate policy engine for attribute-heavy compliance rules. OpenFGA's [contextual tuples](/docs/interacting/contextual-tuples) and [conditions](/docs/modeling/conditions) cover most ABAC needs without a second engine. + +> Comparison facts in this page are drawn from each project's public site as of May 2026. diff --git a/docs/content/compare/keto.mdx b/docs/content/compare/keto.mdx new file mode 100644 index 0000000000..9f335a3055 --- /dev/null +++ b/docs/content/compare/keto.mdx @@ -0,0 +1,39 @@ +--- +title: OpenFGA vs Ory Keto +description: Compare OpenFGA and Ory Keto, two open-source Zanzibar implementations, on language, governance, and ecosystem. +sidebar_position: 6 +slug: /compare/keto +--- + +# OpenFGA vs Ory Keto + +Both OpenFGA and [Ory Keto](https://www.ory.com/keto/) implement Google's Zanzibar paper. They store relationship tuples in a relational database and answer permission checks against a typed schema. The differences are mostly in **governance, language, and ecosystem**. + +## Side by side + +| | OpenFGA | Ory Keto | +| --- | --- | --- | +| Origin | Auth0/Okta → CNCF | Ory | +| Governance | CNCF Sandbox | Vendor (Ory) | +| Language | OpenFGA DSL | Ory Permission Language (OPL), TypeScript-flavored | +| License | Apache 2.0 | Apache 2.0 (open source) plus Ory Enterprise / Ory Network | +| Storage | PostgreSQL, MySQL, SQLite | PostgreSQL, MySQL, CockroachDB | +| Hosted offering | Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Ory Network (managed) | +| Ecosystem | OpenFGA SDKs, Playground, modeling guides | Part of the Ory stack (Hydra, Kratos, Oathkeeper, Keto) | + +## When OpenFGA is the right pick + +- You want **CNCF governance** and a project not steered by a single vendor. Adopters explicitly cited this as a deciding factor over alternatives whose license could change later. +- You prefer the **OpenFGA DSL** — a narrow language tailored to relationship modeling — over OPL's TypeScript-flavored configuration. +- Your authorization story is **standalone** rather than integrated with identity (Hydra/Kratos). OpenFGA does one thing: relationship-based authorization. + +## When Ory Keto is the right pick + +- You already run the **Ory stack** (Hydra for OAuth/OIDC, Kratos for identity, Oathkeeper for proxy) and want everything from one vendor. +- You want a **vendor-supported managed offering** (Ory Network) without the operational work of self-hosting. + +## Both are valid Zanzibar implementations + +If your team's main question is *"which Zanzibar-style engine should we run?"* either project will get you there. The split usually comes down to ecosystem fit (Ory stack vs. CNCF) and modeling-language taste. + +> Comparison facts in this page were collected from [ory.com/keto](https://www.ory.com/keto/) on 2026-05-20. diff --git a/docs/content/compare/keycloak.mdx b/docs/content/compare/keycloak.mdx new file mode 100644 index 0000000000..598609f79c --- /dev/null +++ b/docs/content/compare/keycloak.mdx @@ -0,0 +1,58 @@ +--- +title: OpenFGA vs Keycloak +description: Compare OpenFGA's Zanzibar-style fine-grained authorization with Keycloak Authorization Services, the policy engine bundled with the Keycloak IdP. +sidebar_position: 9 +slug: /compare/keycloak +--- + +# OpenFGA vs Keycloak + +[Keycloak](https://www.keycloak.org/) is an open source identity and access management server: OIDC/SAML SSO, user federation, social login, and an admin console. Its **Authorization Services** module adds policy-based access control on top of that identity stack — resources, scopes, permissions, and policies (role, user, time, JS, aggregated) evaluated by the Keycloak server. + +OpenFGA solves a narrower problem: fine-grained, relationship-based authorization at scale. It does not issue tokens or manage users. The two are often used together — Keycloak as the IdP, OpenFGA as the authorization engine. + +## Side by side + +| | OpenFGA | Keycloak Authorization Services | +| --- | --- | --- | +| Primary role | Authorization engine (ReBAC) | IdP + policy engine bundled | +| Model | Zanzibar-style typed DSL, conditions (CEL) | Resources, scopes, permissions, policies (role/user/time/JS/aggregated) | +| Storage of permissions | Relationship tuples in Postgres / MySQL / SQLite | Policies + resources stored in Keycloak realm DB | +| Identity | Identity-agnostic — works with any IdP, including Keycloak | Tied to Keycloak realms and users | +| Query shape | `check`, `list-objects`, `list-users`, `expand` | Token introspection / UMA permission ticket / policy evaluation endpoint | +| Governance | CNCF Sandbox | CNCF Incubating (donated by Red Hat) | +| License | Apache 2.0 | Apache 2.0 | +| Production references | [Read AI](/docs/adopters/read-ai) (5,200 RPS, 5.3B+ tuples), [Agicap](/docs/adopters/agicap), [Grafana Labs](/docs/adopters/grafana), [Docker](/docs/adopters/docker) | Widely deployed as IdP; Authorization Services usage less commonly profiled | + +## Where OpenFGA tends to win + +- **Fine-grained relationships at scale.** OpenFGA is purpose-built for Zanzibar-style ReBAC: hierarchical objects, parent-child inheritance, user-to-user-group-to-resource paths, and `list-objects` queries that return every resource a user can access. Keycloak Authorization Services models resources and scopes with policies but is not designed around relationship graphs over millions of objects. +- **Identity independence.** OpenFGA does not care who issued the token. You can pair it with Keycloak, Auth0, Okta, Cognito, or any custom IdP. Keycloak Authorization Services is bound to Keycloak realms. +- **Versioned model as code.** The OpenFGA DSL plus the [Playground](https://play.fga.dev) and Terraform provider give you a reviewable, versioned authorization model. Keycloak policies live in the realm and are typically managed through the admin console or the admin REST API. +- **Horizontal scale and dedicated storage.** OpenFGA scales the authorization tier independently of identity, on its own database. Keycloak Authorization Services scales with the Keycloak server itself. + +## Where Keycloak tends to win + +- **Single deployment for IdP + authorization.** If you need an open source IdP and want coarse-to-medium-grained authorization without a second service, Keycloak handles both in one server. +- **UMA 2.0 and permission tickets.** Keycloak implements User-Managed Access flows out of the box. OpenFGA does not — you would build the UMA layer separately if you need it. +- **Operator familiarity.** Many teams already run Keycloak. Adding policies there is incremental rather than introducing a new service. + +## Using them together + +The most common pairing: Keycloak as the IdP issuing OIDC tokens, OpenFGA as the authorization engine. Your application validates the Keycloak token, extracts the subject and any relevant claims, then asks OpenFGA `check`, `list-objects`, or `list-users` for permission decisions. Keycloak roles and group memberships can be mirrored into OpenFGA tuples, or passed as [contextual tuples](/docs/modeling/token-claims-contextual-tuples) at query time. + +Community resources on this pairing: + +- [keycloak-openfga-event-publisher](https://github.com/embesozzi/keycloak-openfga-event-publisher) — a Keycloak SPI that listens to admin events (user/group/role changes) and syncs them into OpenFGA tuples automatically. +- [keycloak-openfga-workshop](https://github.com/embesozzi/keycloak-openfga-workshop) — hands-on workshop wiring Keycloak as the IdP to OpenFGA as the authorization engine end-to-end. +- [Keycloak integration with OpenFGA based on Zanzibar for fine-grained authorization at scale](https://embesozzi.medium.com/keycloak-integration-with-openfga-based-on-zanzibar-for-fine-grained-authorization-at-scale-d3376de00f9a) — walkthrough of the same pattern with architecture diagrams. + +## When to pick OpenFGA + +You need fine-grained, relationship-based authorization (documents, folders, tenants, agents, RAG chunks) decoupled from your identity provider, with a typed model you can version and a query API designed for `check` and `list-objects` at scale. + +## When to pick Keycloak Authorization Services + +You're already on Keycloak, your authorization needs are role- and scope-shaped rather than relationship-shaped, and you'd rather extend the existing IdP than introduce a separate authorization service. + +> Comparison facts about Keycloak Authorization Services above are drawn from the [Keycloak Authorization Services Guide](https://www.keycloak.org/docs/latest/authorization_services/) as of May 2026. Verify current scope and capabilities with the Keycloak documentation before making a decision. diff --git a/docs/content/compare/opa.mdx b/docs/content/compare/opa.mdx new file mode 100644 index 0000000000..bdcf91a4d6 --- /dev/null +++ b/docs/content/compare/opa.mdx @@ -0,0 +1,39 @@ +--- +title: OpenFGA vs Open Policy Agent (OPA) +description: Compare OpenFGA's Zanzibar-style relationship engine with Open Policy Agent (OPA), the CNCF general-purpose policy engine using Rego. +sidebar_position: 7 +slug: /compare/opa +--- + +# OpenFGA vs Open Policy Agent (OPA) + +OpenFGA and [Open Policy Agent](https://www.openpolicyagent.org) are both CNCF projects, but they solve different problems. OPA is a **general-purpose policy engine** that evaluates [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) policies against arbitrary JSON input — it is widely used for Kubernetes admission control, infrastructure policy, and service-mesh authorization. OpenFGA is a **relationship database** that answers "can user A do X on resource B?" against a stored, typed graph of tuples. + +## Side by side + +| | OpenFGA | OPA | +| --- | --- | --- | +| Primary use | Application authorization (ReBAC) | General-purpose policy (admission, infra, app) | +| Model | Typed schema + relationship tuples | Rego policies + arbitrary JSON data | +| State | Stores tuples — relationships are the source of truth | Stateless; data is supplied per-query or bundled | +| Reverse queries | `list-objects`, `list-users` first-class | Not first-class — Rego evaluates per request | +| Governance | CNCF Sandbox | CNCF Graduated | +| License | Apache 2.0 | Apache 2.0 | + +## When OpenFGA is the right pick + +- Your authorization rules involve **relationships that change at write time** — group membership, document sharing, folder hierarchies, multi-tenant ownership — and you want a database to store and query them. +- You need **reverse queries** like *"list every document this user can read"* for UI rendering or filtered listings. These require a stored graph. +- You want a **narrow, typed DSL** for relationship modeling rather than a general logic language. + +## When OPA is the right pick + +- You need **policy across many domains** — Kubernetes admission, Terraform plans, service-mesh request authorization, CI gates — not only application authorization. +- Your decisions are mostly **attribute-based** and the input is already on the request (claims, resource metadata, labels). +- You prefer Rego's general expressiveness and an established ecosystem of policy bundles. + +## Using both together + +Pairing the two is common: OPA at the infrastructure and request-admission layer, OpenFGA inside the application for relationship-heavy permissions. OpenFGA's [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples) cover most ABAC needs inside an application; OPA covers everything outside it. + +> Comparison facts in this page were collected from [openpolicyagent.org](https://www.openpolicyagent.org) on 2026-05-20. diff --git a/docs/content/compare/oso.mdx b/docs/content/compare/oso.mdx new file mode 100644 index 0000000000..816592b535 --- /dev/null +++ b/docs/content/compare/oso.mdx @@ -0,0 +1,42 @@ +--- +title: OpenFGA vs Oso +description: Compare OpenFGA's open-source Zanzibar-style engine with Oso Cloud's hosted authorization service and the Polar policy language. +sidebar_position: 5 +slug: /compare/oso +--- + +# OpenFGA vs Oso + +OpenFGA is an open-source Zanzibar-style authorization engine governed by the CNCF. [Oso](https://www.osohq.com) ships **Oso Cloud**, a managed authorization service whose policies are written in **Polar**, Oso's domain-specific language. + +## Side by side + +| | OpenFGA | Oso | +| --- | --- | --- | +| Delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA) | Oso Cloud (hosted) | +| License | Apache 2.0 | Commercial (Cloud) | +| Governance | CNCF Sandbox | Single vendor | +| Language | OpenFGA DSL | Polar (general logic-programming-flavored DSL) | +| Model styles | ReBAC first; RBAC and ABAC via relations and conditions | RBAC / ReBAC / ABAC / "AnyBAC" via Polar | +| Data store | PostgreSQL, MySQL, SQLite (you run it) | Vendor-managed | + +## When OpenFGA is the right pick + +- You want a **self-hosted, open-source** authorization service with an Apache 2.0 license and CNCF governance — the consistent decision factor in every published [adopter interview](/docs/adopters). +- You want a **typed, narrow DSL** specifically for relationship modeling rather than a general policy language. The [FGA CLI](https://github.com/openfga/cli) makes the model-as-code workflow first-class — lint, test, and deploy the DSL alongside your application code, with a [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest) for infrastructure-as-code rollouts. +- You need to keep the **relationship data inside your own infrastructure**, for compliance or latency reasons (Zuplo's [edge deployment](/docs/adopters/zuplo) is a good example). + +## When Oso may be the right pick + +- You want a **fully managed** authorization service and prefer to express rules in Polar rather than a relationship DSL. +- Polar's general expressiveness fits your team's mental model better than a Zanzibar-style typed model would. + +## Architectural trade-off: dedicated tuple store + +OpenFGA stores relationship tuples in its own database (PostgreSQL, MySQL, or SQLite). Oso Cloud's pitch is the opposite — it queries your existing application database so you don't duplicate data. That's a real trade-off worth naming: a dedicated tuple store costs you a write path and storage, but in return you get a typed relationship model, predictable fan-out performance, multi-tenant isolation, and a consistency model that doesn't depend on your application schema. Read AI runs **5.3B+ tuples at 20 ms p99** on PostgreSQL ([case study](/docs/adopters/read-ai)) — the dedicated-store model scales. + +## Performance claims + +Oso publishes very high throughput numbers on its homepage. We don't reproduce vendor-published benchmarks here — but for reference, the largest publicly documented OpenFGA deployment ([Read AI](/docs/adopters/read-ai)) runs **5,200 RPS at 20 ms p99 over 5.3B+ tuples** on PostgreSQL, end to end in production traffic. + +> Comparison facts in this page were collected from [osohq.com](https://www.osohq.com) on 2026-05-20. diff --git a/docs/content/compare/overview.mdx b/docs/content/compare/overview.mdx new file mode 100644 index 0000000000..02a2d18a50 --- /dev/null +++ b/docs/content/compare/overview.mdx @@ -0,0 +1,33 @@ +--- +title: OpenFGA vs Alternatives +description: Compare OpenFGA with SpiceDB, Cerbos, Permit.io, Oso, Ory Keto, and OPA across model, storage, deployment, language, and governance. +sidebar_position: 1 +slug: /compare +--- + +# OpenFGA vs alternatives + +These pages compare OpenFGA to other authorization systems based on each project's publicly documented features as of May 2026. They are written from OpenFGA's perspective but stick to verifiable facts — model, storage, deployment, language, governance — rather than benchmark numbers we cannot reproduce. + +| Compared system | Style | Hosted? | License | +| --- | --- | --- | --- | +| [SpiceDB](/docs/compare/spicedb) | Zanzibar / ReBAC | Authzed (managed) and self-hosted | Apache 2.0 | +| [Cerbos](/docs/compare/cerbos) | PBAC (RBAC/ABAC/ReBAC via YAML) | Cerbos Hub and self-hosted | Apache 2.0 (PDP) | +| [Permit.io](/docs/compare/permit) | Control plane over OPA/Cedar | Hosted-first | Commercial | +| [Oso](/docs/compare/oso) | Polar DSL | Oso Cloud (managed) | Commercial (Cloud) | +| [Ory Keto](/docs/compare/keto) | Zanzibar / ReBAC | Ory Network and self-hosted | Apache 2.0 + Enterprise | +| [OPA](/docs/compare/opa) | General-purpose policy (Rego) | Self-hosted | Apache 2.0 (CNCF graduated) | +| [WorkOS FGA](/docs/compare/workos) | Hosted FGA bundled with WorkOS identity | Hosted only | Commercial | +| [Keycloak](/docs/compare/keycloak) | IdP with bundled Authorization Services (policy-based) | Self-hosted | Apache 2.0 | +| [Auth0 FGA](/docs/compare/auth0-fga) | Managed OpenFGA from Auth0/Okta | Hosted only | Commercial (managed service) | + +## Why OpenFGA + +OpenFGA's specific niche is: + +- **Zanzibar-style ReBAC** with a typed DSL and a relationship tuple store — designed for fine-grained authorization, not generic policy. +- **CNCF Sandbox project** with public governance, an ADOPTERS file, and a Security Insights manifest. +- **Self-hostable** with PostgreSQL, MySQL, and SQLite backends and an in-memory mode for tests. A managed offering is also available as **Auth0 FGA / Okta FGA** from Auth0/Okta, the team that originally built OpenFGA and donated it to the CNCF. +- **Production references** at [Read AI](/docs/adopters/read-ai), [Agicap](/docs/adopters/agicap), [Grafana Labs](/docs/adopters/grafana), [Docker](/docs/adopters/docker), and [Zuplo](/docs/adopters/zuplo). + +If you want a hosted policy-as-a-service, several entries above will be a better fit — or you can use **Auth0 FGA / Okta FGA**, a managed OpenFGA service from the original creators. If you need a self-hostable Zanzibar-style engine with first-class modeling tools, OpenFGA is built for that case. diff --git a/docs/content/compare/permit.mdx b/docs/content/compare/permit.mdx new file mode 100644 index 0000000000..acae592e96 --- /dev/null +++ b/docs/content/compare/permit.mdx @@ -0,0 +1,42 @@ +--- +title: OpenFGA vs Permit.io +description: Compare OpenFGA's self-hosted Zanzibar-style engine with Permit.io's hosted authorization control plane. +sidebar_position: 4 +slug: /compare/permit +--- + +# OpenFGA vs Permit.io + +OpenFGA is a self-hostable, CNCF-governed Zanzibar-style engine. [Permit.io](https://www.permit.io) is a commercial **hosted authorization control plane** that wraps existing policy engines (historically OPA / Cedar) with a UI, audit, MCP gateway, and SDKs. + +## Side by side + +| | OpenFGA | Permit.io | +| --- | --- | --- | +| Primary delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Hosted control plane (commercial) | +| License | Apache 2.0 | Commercial | +| Governance | CNCF Sandbox | Single vendor | +| Model | Zanzibar-style ReBAC, conditions for ABAC | RBAC, ABAC, ReBAC over OPA-style engines | +| Data residency | Wherever you run OpenFGA | Vendor-managed unless self-hosted PDP is purchased | +| First-party UI | OpenFGA Playground (web) | Full no-code/low-code admin UI | + +## When OpenFGA is the right pick + +- You want **self-hosted, open source, and CNCF-governed**. Every adopter we document picked OpenFGA partly to avoid licensing risk and vendor lock-in (see [customer case studies](/docs/adopters)). +- You need **regulatory data residency** — e.g. fintech (Agicap), healthcare, or air-gapped environments — that a hosted SaaS makes harder. +- You want a **single, typed model file** versioned alongside code, not a vendor admin UI as the source of truth. The [FGA CLI](https://github.com/openfga/cli) and [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest) make the model-as-code workflow first-class — write the DSL, lint and test it locally, diff it in PRs, and deploy it like any other code artifact. + +## When Permit.io may be the right pick + +- You want a turn-key hosted authorization product with built-in admin UI, audit, and SOC 2 / HIPAA / GDPR / CCPA compliance attestations from the vendor. +- You don't want to operate the authorization data store yourself and are willing to pay for managed infrastructure. + +## Migration shape + +Teams that move from a hosted control plane to OpenFGA typically: + +1. Re-express their current RBAC/ABAC rules in the OpenFGA DSL — relations replace roles, conditions cover attribute checks. +2. Run [parallel checks](/docs/best-practices) against both engines for a period, comparing decisions. Docker [used exactly this pattern](/docs/adopters/docker). +3. Cut over per-feature once both systems agree. + +> Permit.io product details above were collected from [permit.io](https://www.permit.io) on 2026-05-20. Verify current product scope with the vendor before making a decision. diff --git a/docs/content/compare/spicedb.mdx b/docs/content/compare/spicedb.mdx new file mode 100644 index 0000000000..792722768e --- /dev/null +++ b/docs/content/compare/spicedb.mdx @@ -0,0 +1,43 @@ +--- +title: OpenFGA vs SpiceDB +description: Compare OpenFGA and SpiceDB on Zanzibar foundations, schema language, storage backends, deployment, and governance. +sidebar_position: 2 +slug: /compare/spicedb +--- + +# OpenFGA vs SpiceDB + +OpenFGA and [SpiceDB](https://authzed.com/spicedb) are the two most prominent open-source implementations of Google's [Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/) paper. Both store relationship tuples and answer permission checks against a typed schema. The differences are in governance, modeling ergonomics, and storage strategy. + +## Side by side + +| | OpenFGA | SpiceDB | +| --- | --- | --- | +| Origin | Auth0/Okta, donated to CNCF | Authzed | +| Governance | CNCF Sandbox | Open core, Authzed-led | +| License | Apache 2.0 | Apache 2.0 | +| Model language | OpenFGA DSL (concise types + relations) | SpiceDB Schema | +| Storage | PostgreSQL, MySQL, SQLite, in-memory | PostgreSQL, MySQL, CockroachDB, Spanner, in-memory | +| Hosted offering | Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta, the original creators) | Authzed (managed) | +| Conditions / ABAC | Conditional tuples (CEL) | Caveats (CEL) | + +## Where OpenFGA tends to win + +- **CNCF governance.** Adopters such as [Grafana Labs](/docs/adopters/grafana) cited CNCF stewardship and an explicit governance policy as a reason to prefer OpenFGA over a vendor-led project that could re-license. +- **Modeling ergonomics.** The OpenFGA DSL aims to be the smallest readable expression of a relationship model; the [Playground](https://play.fga.dev) visualizes graphs and runs assertions in the browser. +- **SQLite + embedded.** [Grafana maintains the SQLite adapter](/docs/adopters/grafana) upstream so OpenFGA ships inside embedded Grafana OSS — a deployment shape SpiceDB does not target. + +## Where SpiceDB tends to win + +- **Wider storage matrix.** Out-of-the-box CockroachDB and Spanner adapters matter for teams that already standardize on those engines. +- **Authzed managed service.** A first-party SaaS removes operational work for teams that prefer to outsource the database tier. + +## When to pick OpenFGA + +You want a CNCF-governed, self-hosted Zanzibar-style engine, ideally on Postgres or MySQL, with a concise DSL and visual playground, and you value production references such as Read AI (5,200 RPS, 5.3B+ tuples), Agicap, Grafana, Docker, and Zuplo. See [the customer overview](/docs/adopters). + +## When to pick SpiceDB + +You need CockroachDB or Spanner storage, or you want a managed Zanzibar-style service from the same vendor that ships the engine. + +> Comparison facts in this page are drawn from each project's public site as of May 2026. License or feature claims may change — always confirm against the upstream project before making a decision. diff --git a/docs/content/compare/workos.mdx b/docs/content/compare/workos.mdx new file mode 100644 index 0000000000..e385f689c2 --- /dev/null +++ b/docs/content/compare/workos.mdx @@ -0,0 +1,44 @@ +--- +title: OpenFGA vs WorkOS FGA +description: Compare OpenFGA's self-hostable Zanzibar-style engine with WorkOS FGA, a hosted fine-grained authorization product bundled with WorkOS identity. +sidebar_position: 8 +slug: /compare/workos +--- + +# OpenFGA vs WorkOS FGA + +OpenFGA is a self-hostable, CNCF-governed Zanzibar-style authorization engine. [WorkOS FGA](https://workos.com/docs/fga) is a **hosted** fine-grained authorization product from WorkOS, designed to layer on top of WorkOS RBAC, SSO, Directory Sync, and AuthKit. It is sold and operated by WorkOS as part of its identity platform. + +## Side by side + +| | OpenFGA | WorkOS FGA | +| --- | --- | --- | +| Primary delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Hosted as part of WorkOS | +| License | Apache 2.0 | Commercial (vendor-hosted) | +| Governance | CNCF Sandbox | Single vendor (WorkOS) | +| Model | Zanzibar-style ReBAC, typed DSL, conditions (CEL) | Subjects, resources, roles, permissions, assignments — no schema DSL exposed | +| Storage | Postgres / MySQL / SQLite / in-memory (your infra) | Vendor-managed | +| Self-hosting | Yes | Not documented | +| Identity bundling | Identity-agnostic — works with any IdP | Tightly integrated with WorkOS RBAC, SSO, Directory Sync, AuthKit | + +## Where OpenFGA tends to win + +- **Self-hosting and data residency.** OpenFGA runs in your environment on your database, which matters for regulated workloads (fintech, healthcare) and air-gapped deployments. WorkOS FGA is documented as hosted. +- **Open governance.** OpenFGA is a CNCF Sandbox project with a public governance policy and ADOPTERS file. WorkOS FGA is a single-vendor commercial product. +- **Typed DSL and tooling.** A versioned authorization model in the OpenFGA DSL, the [Playground](https://play.fga.dev), and a Terraform provider give you the model as code. WorkOS FGA's public docs describe roles, permissions, and assignments rather than a typed schema language. +- **Production references at scale.** [Read AI](/docs/adopters/read-ai) (5,200 RPS, 5.3B+ tuples, 20 ms p99), [Headspace](/docs/adopters/headspace), [Agicap](/docs/adopters/agicap), [Grafana Labs](/docs/adopters/grafana), and [Docker](/docs/adopters/docker) all run OpenFGA in production. + +## Where WorkOS FGA tends to win + +- **Bundled with WorkOS identity.** If you are already on WorkOS for SSO, Directory Sync, or AuthKit, FGA plugs into the same dashboard and SDK surface — organization memberships are first-class subjects. The marketing page advertises **sub-50 ms p95 access checks** and incremental adoption alongside existing WorkOS RBAC roles. +- **No infrastructure to operate.** No database to provision, no engine to upgrade — WorkOS runs the service. + +## When to pick OpenFGA + +You want a self-hostable, CNCF-governed, Zanzibar-style engine with a typed DSL, a versionable model, and the option of either operating it yourself or using **Auth0 FGA / Okta FGA** — managed OpenFGA from Auth0/Okta, the original creators who donated the project to the CNCF. You value identity independence and have hard requirements on data residency, open governance, or compliance posture you control. + +## When to pick WorkOS FGA + +You're already on the WorkOS platform, you want fine-grained authorization bundled into the same vendor relationship as your identity stack, and you prefer a hosted offering over running an authorization service yourself. + +> Comparison facts about WorkOS FGA above are drawn from [workos.com/docs/fga](https://workos.com/docs/fga) as of May 2026. Verify current scope, pricing, and deployment options with WorkOS before making a decision. diff --git a/docs/content/getting-started/setup-openfga/configuration.mdx b/docs/content/getting-started/setup-openfga/configuration.mdx index 42e8798ec3..e6b72c1881 100644 --- a/docs/content/getting-started/setup-openfga/configuration.mdx +++ b/docs/content/getting-started/setup-openfga/configuration.mdx @@ -101,7 +101,7 @@ docker run docker.io/openfga/openfga:latest run \ ## List of options -The following table lists the configuration options for the OpenFGA server [v1.8.9](https://github.com/openfga/openfga/releases/tag/v1.8.9), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.8.9/.config-schema.json). +The following table lists the configuration options for the OpenFGA server [v1.16.0](https://github.com/openfga/openfga/releases/tag/v1.16.0), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.16.0/.config-schema.json). | Config File | Env Var | Flag Name | Type | Description | Default Value | |-------------|---------|-----------|------|-------------|---------------| @@ -116,29 +116,39 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `maxConditionEvaluationCost` |
OPENFGA_MAX_CONDITION_EVALUATION_COST
| `max-condition-evaluation-cost` | integer | The maximum cost for CEL condition evaluation before a request returns an error (default is 100). | `100` | | `changelogHorizonOffset` |
OPENFGA_CHANGELOG_HORIZON_OFFSET
| `changelog-horizon-offset` | integer | The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. | | | `resolveNodeLimit` |
OPENFGA_RESOLVE_NODE_LIMIT
| `resolve-node-limit` | integer | Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). | `25` | -| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `100` | +| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `10` | | `listObjectsDeadline` |
OPENFGA_LIST_OBJECTS_DEADLINE
| `list-objects-deadline` | string (duration) | The timeout deadline for serving ListObjects requests | `3s` | | `listObjectsMaxResults` |
OPENFGA_LIST_OBJECTS_MAX_RESULTS
| `list-objects-max-results` | integer | The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned | `1000` | +| `listObjectsPipelineEnabled` |
OPENFGA_LIST_OBJECTS_PIPELINE_ENABLED
| `list-objects-pipeline-enabled` | boolean | Enables the ListObjects pipeline optimization algorithm, which can significantly improve the latency of ListObjects requests. When enabled, the server will attempt to resolve intermediate nodes in the ListObjects resolution tree concurrently. This optimization is most effective for workloads with large and complex authorization models, but may not suit all cases. Can be disabled if it causes increased resource usage. | `true` | | `listUsersDeadline` |
OPENFGA_LIST_USERS_DEADLINE
| `list-users-deadline` | string (duration) | The timeout deadline for serving ListUsers requests. If 0s, there is no deadline | `3s` | | `listUsersMaxResults` |
OPENFGA_LIST_USERS_MAX_RESULTS
| `list-users-max-results` | integer | The maximum results to return in ListUsers API response. If 0, all results can be returned | `1000` | +| `readChangesMaxPageSize` |
OPENFGA_READ_CHANGES_MAX_PAGE_SIZE
| `read-changes-max-page-size` | integer | The maximum page size allowed for ReadChanges API requests | `100` | | `requestDurationDatastoreQueryCountBuckets` |
OPENFGA_REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS
| `request-duration-datastore-query-count-buckets` | []integer | Datastore query count buckets used to label the histogram metric for measuring request duration. | `50,200` | | `requestDurationDispatchCountBuckets` |
OPENFGA_REQUEST_DURATION_DISPATCH_COUNT_BUCKETS
| `request-duration-dispatch-count-buckets` | []integer | Dispatch count buckets used to label the histogram metric for measuring request duration. | `50,200` | | `contextPropagationToDatastore` |
OPENFGA_CONTEXT_PROPAGATION_TO_DATASTORE
| `context-propagation-to-datastore` | boolean | Propagate a requests context to the datastore implementation. Settings this parameter can result in connection pool draining on request aborts and timeouts. | `false` | -| `experimentals` |
OPENFGA_EXPERIMENTALS
| `experimentals` | []string (enum=[`enable-check-optimizations`, `enable-list-objects-optimizations`, `enable-access-control`]) | a list of experimental features to enable | `` | +| `experimentals` |
OPENFGA_EXPERIMENTALS
| `experimentals` | []string (enum=[`enable-check-optimizations`, `enable-list-objects-optimizations`, `enable-access-control`, `datastore_throttling`, `pipeline_list_objects`, `authzen`]) | a comma-separated list of experimental features to enable | `pipeline_list_objects` | +| `authzen.baseURL` |
OPENFGA_AUTHZEN_BASE_URL
| `authzen-base-url` | string | The canonical absolute base URL published in AuthZEN discovery metadata. It may include an optional path prefix. | | | `accessControl.enabled` |
OPENFGA_ACCESS_CONTROL_ENABLED
| `access-control-enabled` | boolean | Enable/disable the access control store. | `false` | | `accessControl.storeId` |
OPENFGA_ACCESS_CONTROL_STORE_ID
| `access-control-store-id` | string | The storeId to be used for the access control store. | | | `accessControl.modelId` |
OPENFGA_ACCESS_CONTROL_MODEL_ID
| `access-control-model-id` | string | The modelId to be used for the access control store. | | -| `playground.enabled` |
OPENFGA_PLAYGROUND_ENABLED
| `playground-enabled` | boolean | Enable/disable the OpenFGA Playground. | `true` | -| `playground.port` |
OPENFGA_PLAYGROUND_PORT
| `playground-port` | integer | The port to serve the local OpenFGA Playground on. | `3000` | +| `playground.enabled` |
OPENFGA_PLAYGROUND_ENABLED
| `playground-enabled` | boolean | Enable/disable the OpenFGA Playground. The Playground can only be run when the authentication method is set to 'none'. Note that the built-in Playground is intended for local development and testing purposes, and is not recommended for production use. It has been deprecated and will be removed in a subsequent release. | `false` | +| `playground.port` |
OPENFGA_PLAYGROUND_PORT
| `playground-port` | integer | Deprecated: The port to serve the local OpenFGA Playground on. Use 'addr' instead. | `3000` | +| `playground.addr` |
OPENFGA_PLAYGROUND_ADDR
| `playground-addr` | string | The host:port address to serve the local OpenFGA Playground on. | | | `profiler.enabled` |
OPENFGA_PROFILER_ENABLED
| `profiler-enabled` | boolean | Enabled/disable pprof profiling. | `false` | | `profiler.addr` |
OPENFGA_PROFILER_ADDR
| `profiler-addr` | string | The host:port address to serve the pprof profiler server on. | `:3001` | | `datastore.engine` |
OPENFGA_DATASTORE_ENGINE
| `datastore-engine` | string (enum=[`memory`, `postgres`, `mysql`, `sqlite`]) | The datastore engine that will be used for persistence. | `memory` | | `datastore.uri` |
OPENFGA_DATASTORE_URI
| `datastore-uri` | string | The connection uri to use to connect to the datastore (for any engine other than 'memory'). | | +| `datastore.secondaryUri` |
OPENFGA_DATASTORE_SECONDARY_URI
| `datastore-secondary-uri` | string | The connection uri to use to connect to the secondary datastore (for postgres only). | | | `datastore.username` |
OPENFGA_DATASTORE_USERNAME
| `datastore-username` | string | The connection username to connect to the datastore (overwrites any username provided in the connection uri). | | +| `datastore.secondaryUsername` |
OPENFGA_DATASTORE_SECONDARY_USERNAME
| `datastore-secondary-username` | string | The connection username to connect to the secondary datastore (overwrites any username provided in the connection uri). | | | `datastore.password` |
OPENFGA_DATASTORE_PASSWORD
| `datastore-password` | string | The connection password to connect to the datastore (overwrites any password provided in the connection uri). | | +| `datastore.secondaryPassword` |
OPENFGA_DATASTORE_SECONDARY_PASSWORD
| `datastore-secondary-password` | string | The connection password to connect to the secondary datastore (overwrites any password provided in the connection uri). | | | `datastore.maxCacheSize` |
OPENFGA_DATASTORE_MAX_CACHE_SIZE
| `datastore-max-cache-size` | integer | The maximum number of authorization models that will be cached in memory | `100000` | +| `datastore.maxTypesystemCacheSize` |
OPENFGA_DATASTORE_MAX_TYPESYSTEM_CACHE_SIZE
| `datastore-max-typesystem-cache-size` | integer | The maximum number of type system models that will be cached in memory | `100000` | | `datastore.maxOpenConns` |
OPENFGA_DATASTORE_MAX_OPEN_CONNS
| `datastore-max-open-conns` | integer | The maximum number of open connections to the datastore. | `30` | +| `datastore.minOpenConns` |
OPENFGA_DATASTORE_MIN_OPEN_CONNS
| `datastore-min-open-conns` | integer | The minimum number of open connections to the datastore. This is only available for PostgreSQL. | `0` | | `datastore.maxIdleConns` |
OPENFGA_DATASTORE_MAX_IDLE_CONNS
| `datastore-max-idle-conns` | integer | the maximum number of connections to the datastore in the idle connection pool. | `10` | +| `datastore.minIdleConns` |
OPENFGA_DATASTORE_MIN_IDLE_CONNS
| `datastore-min-idle-conns` | integer | the minimum number of connections to the datastore in the idle connection pool. This is only available for PostgreSQL. | `0` | | `datastore.connMaxIdleTime` |
OPENFGA_DATASTORE_CONN_MAX_IDLE_TIME
| `datastore-conn-max-idle-time` | string (duration) | the maximum amount of time a connection to the datastore may be idle | `0s` | | `datastore.connMaxLifetime` |
OPENFGA_DATASTORE_CONN_MAX_LIFETIME
| `datastore-conn-max-lifetime` | string (duration) | the maximum amount of time a connection to the datastore may be reused | `0s` | | `datastore.metrics.enabled` |
OPENFGA_DATASTORE_METRICS_ENABLED
| `datastore-metrics-enabled` | boolean | enable/disable sql metrics for the datastore | `false` | @@ -150,6 +160,7 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `authn.oidc.subjects` |
OPENFGA_AUTHN_OIDC_SUBJECTS
| `authn-oidc-subjects` | []string | the OIDC subject names that will be accepted as valid when verifying the `sub` field of the JWTs. If empty, every `sub` will be allowed | | | `authn.oidc.clientIdClaims` |
OPENFGA_AUTHN_OIDC_CLIENT_ID_CLAIMS
| `authn-oidc-client-id-claims` | []string | the OIDC client id claims that will be used to parse the clientID - configure in order of priority (first is highest). Defaults to [`azp`, `client_id`] | | | `grpc.addr` |
OPENFGA_GRPC_ADDR
| `grpc-addr` | string | The host:port address to serve the grpc server on. | `0.0.0.0:8081` | +| `grpc.maxRecvMsgBytes` |
OPENFGA_GRPC_MAX_RECV_MSG_BYTES
| `grpc-max-recv-msg-bytes` | integer | The maximum size, in bytes, of a received gRPC message. | `616448` | | `grpc.tls.enabled` |
OPENFGA_GRPC_TLS_ENABLED
| `grpc-tls-enabled` | boolean | Enables or disables transport layer security (TLS). | `false` | | `grpc.tls.cert` |
OPENFGA_GRPC_TLS_CERT
| `grpc-tls-cert` | string | The (absolute) file path of the certificate to use for the TLS connection. | | | `grpc.tls.key` |
OPENFGA_GRPC_TLS_KEY
| `grpc-tls-key` | string | The (absolute) file path of the TLS key that should be used for the TLS connection. | | @@ -165,10 +176,11 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `log.level` |
OPENFGA_LOG_LEVEL
| `log-level` | string (enum=[`none`, `debug`, `info`, `warn`, `error`, `panic`, `fatal`]) | The log level to set. For production we recommend 'info' format. | `info` | | `log.timestampFormat` |
OPENFGA_LOG_TIMESTAMP_FORMAT
| `log-timestamp-format` | string (enum=[`Unix`, `ISO8601`]) | The timestamp format to use for the log output. | `Unix` | | `trace.enabled` |
OPENFGA_TRACE_ENABLED
| `trace-enabled` | boolean | Enable tracing. | `false` | -| `trace.otlp.endpoint` |
OPENFGA_TRACE_OTLP_ENDPOINT
| `trace-otlp-endpoint` | string | The grpc endpoint of the trace collector | `0.0.0.0:4317` | +| `trace.otlp.endpoint` |
OPENFGA_TRACE_OTLP_ENDPOINT,OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,OTEL_EXPORTER_OTLP_ENDPOINT
| `trace-otlp-endpoint,otel-exporter-otlp-traces-endpoint,otel-exporter-otlp-endpoint` | string | The grpc endpoint of the trace collector | `0.0.0.0:4317` | | `trace.otlp.tls.enabled` |
OPENFGA_TRACE_OTLP_TLS_ENABLED
| `trace-otlp-tls-enabled` | boolean | Whether to use TLS connection for the trace collector | `false` | -| `trace.sampleRatio` |
OPENFGA_TRACE_SAMPLE_RATIO
| `trace-sample-ratio` | number | The fraction of traces to sample. 1 means all, 0 means none. | `0.2` | -| `trace.serviceName` |
OPENFGA_TRACE_SERVICE_NAME
| `trace-service-name` | string | The service name included in sampled traces. | `openfga` | +| `trace.sampleRatio` |
OPENFGA_TRACE_SAMPLE_RATIO,OTEL_TRACES_SAMPLER_ARG
| `trace-sample-ratio,otel-traces-sampler-arg` | number | The fraction of traces to sample. 1 means all, 0 means none. | `0.2` | +| `trace.serviceName` |
OPENFGA_TRACE_SERVICE_NAME,OTEL_SERVICE_NAME
| `trace-service-name,otel-service-name` | string | The service name included in sampled traces. | `openfga` | +| `trace.resourceAttributes` |
OTEL_RESOURCE_ATTRIBUTES
| `otel-resource-attributes` | string | Key-value pairs to be used as resource attributes | | | `metrics.enabled` |
OPENFGA_METRICS_ENABLED
| `metrics-enabled` | boolean | enable/disable prometheus metrics on the '/metrics' endpoint | `true` | | `metrics.addr` |
OPENFGA_METRICS_ADDR
| `metrics-addr` | string | the host:port address to serve the prometheus metrics server on | `0.0.0.0:2112` | | `metrics.enableRPCHistograms` |
OPENFGA_METRICS_ENABLE_RPC_HISTOGRAMS
| `metrics-enable-rpc-histograms` | boolean | enables prometheus histogram metrics for RPC latency distributions | `false` | @@ -179,21 +191,37 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `checkQueryCache.enabled` |
OPENFGA_CHECK_QUERY_CACHE_ENABLED
| `check-query-cache-enabled` | boolean | enable caching of Check requests. The key is a string representing a query, and the value is a boolean. For example, if you have a relation `define viewer: owner or editor`, and the query is Check(user:anne, viewer, doc:1), we'll evaluate the `owner` relation and the `editor` relation and cache both results: (user:anne, viewer, doc:1) -> allowed=true and (user:anne, owner, doc:1) -> allowed=true. The cache is stored in-memory; the cached values are overwritten on every change in the result, and cleared after the configured TTL. This flag improves latency, but turns Check and ListObjects into eventually consistent APIs. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. | `false` | | `checkQueryCache.limit` |
OPENFGA_CHECK_QUERY_CACHE_LIMIT
| `check-query-cache-limit` | integer | DEPRECATED use OPENFGA_CHECK_CACHE_LIMIT. If caching of Check and ListObjects calls is enabled, this is the size limit (in items) of the cache | `10000` | | `checkQueryCache.ttl` |
OPENFGA_CHECK_QUERY_CACHE_TTL
| `check-query-cache-ttl` | string (duration) | if caching of Check and ListObjects is enabled, this is the TTL of each value | `10s` | -| `cacheController.enabled` |
OPENFGA_CACHE_CONTROLLER_ENABLED
| `cache-controller-enabled` | boolean | enabling dynamic invalidation of check query cache and check iterator cache based on whether there are recent tuple writes. If enabled, cache will be invalidated when either 1) there are tuples written to the store OR 2) the check query cache or check iterator cache TTL has expired. | `false` | -| `cacheController.ttl` |
OPENFGA_CACHE_CONTROLLER_TTL
| `cache-controller-ttl` | string (duration) | if cache controller is enabled, control how frequent read changes are invoked internally to query for recent tuple writes to the store. | `10s` | +| `cacheController.enabled` |
OPENFGA_CACHE_CONTROLLER_ENABLED
| `cache-controller-enabled` | boolean | enable invalidation of check query cache and iterator cache based on recent tuple writes. Invalidation is triggered by Check and List Objects requests, which periodically check the datastore's changelog table for writes and invalidate cache entries earlier than recent writes. Invalidations from Check requests are rate-limited by cache-controller-ttl, whereas List Objects requests invalidate every time if list objects iterator cache is enabled. | `false` | +| `cacheController.ttl` |
OPENFGA_CACHE_CONTROLLER_TTL
| `cache-controller-ttl` | string (duration) | if cache controller is enabled, this is the minimum time interval for Check requests to trigger cache invalidation. List Objects requests may trigger invalidation even sooner if list objects iterator cache is enabled. | `10s` | +| `cacheTTLJitterPercentage` |
OPENFGA_CACHE_TTL_JITTER_PERCENTAGE
| `cache-ttl-jitter-percentage` | integer | A percentage (0-100) of the base TTL added as random jitter to each cache entry's TTL, spreading out expirations to prevent thundering herd effects. For example, a value of 10 with a base TTL of 10s means each entry gets a TTL between 10s and 11s. | | | `checkDispatchThrottling.enabled` |
OPENFGA_CHECK_DISPATCH_THROTTLING_ENABLED
| `check-dispatch-throttling-enabled` | boolean | enable throttling when check request's number of dispatches is high | `false` | | `checkDispatchThrottling.frequency` |
OPENFGA_CHECK_DISPATCH_THROTTLING_FREQUENCY
| `check-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling | `10µs` | | `checkDispatchThrottling.threshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_THRESHOLD
| `check-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a check request | `100` | | `checkDispatchThrottling.maxThreshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD
| `check-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when list objects request's number of dispatches is high | `false` | -| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list objects request. A higher value will result in more aggressive throttling | `10µs` | -| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list objects request | `100` | -| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list objects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `listObjectsIteratorCache.enabled` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_ENABLED
| `list-objects-iterator-cache-enabled` | boolean | enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. | `false` | +| `listObjectsIteratorCache.maxResults` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS
| `list-objects-iterator-cache-max-results` | integer | if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key | `10000` | +| `listObjectsIteratorCache.ttl` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_TTL
| `list-objects-iterator-cache-ttl` | string (duration) | if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value | `10s` | +| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when ListObjects request's number of dispatches is high. Only applies when pipeline is disabled. | `false` | +| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling | `10µs` | +| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a ListObjects request | `100` | +| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | | `listUsersDispatchThrottling.enabled` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_ENABLED
| `list-users-dispatch-throttling-enabled` | boolean | enable throttling when list users request's number of dispatches is high | `false` | | `listUsersDispatchThrottling.frequency` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_FREQUENCY
| `list-users-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling | `10µs` | | `listUsersDispatchThrottling.threshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_THRESHOLD
| `list-users-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list users request | `100` | | `listUsersDispatchThrottling.maxThreshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-users-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `checkDatastoreThrottle.threshold` |
OPENFGA_CHECK_DATASTORE_THROTTLE_THRESHOLD
| `check-datastore-throttle-threshold` | integer | define the number of datastore requests allowed before being throttled. A value of 0 means throttling is disabled. | | +| `checkDatastoreThrottle.duration` |
OPENFGA_CHECK_DATASTORE_THROTTLE_DURATION
| `check-datastore-throttle-duration` | string (duration) | defines the time for which the datastore request will be suspended for being throttled. | `0s` | +| `listObjectsDatastoreThrottle.threshold` |
OPENFGA_LIST_OBJECTS_DATASTORE_THROTTLE_THRESHOLD
| `list-objects-datastore-throttle-threshold` | integer | define the number of datastore requests allowed before being throttled. A value of 0 means throttling is disabled. | | +| `listObjectsDatastoreThrottle.duration` |
OPENFGA_LIST_OBJECTS_DATASTORE_THROTTLE_DURATION
| `list-objects-datastore-throttle-duration` | string (duration) | defines the time for which the datastore request will be suspended for being throttled. | `0s` | +| `listUsersDatastoreThrottle.threshold` |
OPENFGA_LIST_USERS_DATASTORE_THROTTLE_THRESHOLD
| `list-users-datastore-throttle-threshold` | integer | define the number of datastore requests allowed before being throttled. A value of 0 means throttling is disabled. | | +| `listUsersDatastoreThrottle.duration` |
OPENFGA_LIST_USERS_DATASTORE_THROTTLE_DURATION
| `list-users-datastore-throttle-duration` | string (duration) | defines the time for which the datastore request will be suspended for being throttled. | `0s` | +| `sharedIterator.enabled` |
OPENFGA_SHARED_ITERATOR_ENABLED
| `shared-iterator-enabled` | boolean | enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. | `false` | +| `sharedIterator.limit` |
OPENFGA_SHARED_ITERATOR_LIMIT
| `shared-iterator-limit` | integer | if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. | `1000000` | | `requestTimeout` |
OPENFGA_REQUEST_TIMEOUT
| `request-timeout` | string (duration) | The timeout duration for a request. | `3s` | +| `shutdownTimeout` |
OPENFGA_SHUTDOWN_TIMEOUT
| `shutdown-timeout` | string (duration) | The timeout duration for a graceful shutdown. | `10s` | +| `planner.initialGuess` |
OPENFGA_PLANNER_INITIAL_GUESS
| `planner-initial-guess` | string (duration) | The initial guess for the planners estimation. | `10ms` | +| `planner.evictionThreshold` |
OPENFGA_PLANNER_EVICTION_THRESHOLD
| `planner-eviction-threshold` | | How long a planner key can be unused before being evicted. | `0` | +| `planner.cleanupInterval` |
OPENFGA_PLANNER_CLEANUP_INTERVAL
| `planner-cleanup-interval` | string (duration) | How often the planner checks for stale keys. | `0` | ## Related Sections diff --git a/docs/content/industries/banking.mdx b/docs/content/industries/banking.mdx new file mode 100644 index 0000000000..4abaca19b5 --- /dev/null +++ b/docs/content/industries/banking.mdx @@ -0,0 +1,57 @@ +--- +title: Banking Authorization with OpenFGA +description: Model account managers, account owners, per-transaction limits, and delegation in OpenFGA for banking and fintech applications. +sidebar_position: 3 +slug: /industries/banking +--- + +# Banking Authorization with OpenFGA + +Banking and fintech authorization isn't just "who can read this record?" — it's "who can move *how much* money out of *which* account, and who approved that delegation?". A relationship-based model captures the answer without scattering the rules across application code. + +The full working model is in [openfga/sample-stores/stores/banking](https://github.com/openfga/sample-stores/tree/main/stores/banking). + +## Core resources and relations + +- **organization** — the bank tenant. +- **account** — has an `owner`, optional `co_owners`, and an `account_manager` assigned by the bank. +- **transaction** — initiated against an `account`, subject to a per-actor amount limit. + +The interesting relations: + +- `owner` and `co_owner` on `account` — full read and full transaction rights up to a personal limit. +- `account_manager` on `account` — bank staff who can view the account and initiate transactions, but only up to a *manager-specific* limit that may differ from the owner's. +- `delegate` on `account` — owners can delegate a subset of rights to another person (a spouse, a bookkeeper) with a separate, lower limit. + +## Why this is hard in role-only systems + +A typical "manager", "owner", "delegate" RBAC scheme answers *what action is allowed* but not *up to what amount*. Hard-coding the amount in application logic means: + +- The auth decision is split across two systems (an RBAC check + an `if amount > X` branch). +- Audit can't answer "who could have wired more than $50k from account A on date D" without joining application code with role tables. +- Delegation expiry, per-transaction overrides, and dual-control approvals each become bespoke features. + +OpenFGA expresses the limit *as part of the relationship* using [conditions](/docs/modeling/conditions), so the check `can_initiate_transfer` takes the transaction amount as a contextual parameter and returns a single allow/deny. + +## Where this maps to OpenFGA features + +| Banking requirement | OpenFGA feature | +| --- | --- | +| Per-actor transaction limits | [conditions](/docs/modeling/conditions) on `can_initiate_transfer` | +| Owner / co-owner / delegate roles | direct relations on `account` | +| Delegated authority (bookkeeper) | userset relations + condition on amount | +| Time-bounded delegation | [contextual tuples](/docs/interacting/contextual-tuples) carrying expiry | +| Tenant isolation per bank brand | tenant-scoped types, see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | +| Audit who-could-have-done-what | [Read Changes API](/docs/interacting/read-tuple-changes) | + +## Common extensions + +The base sample covers individual accounts. Adopters typically extend it with: + +- **Joint accounts and trusts.** Add a `beneficiary` relation that grants read access only. +- **Corporate accounts.** Introduce a `company` type; assign signatories via `signatory` and require N-of-M via your application calling `Check` for each required signer. +- **Step-up authentication.** When the transaction amount exceeds a threshold, pass a contextual tuple `mfa_verified=true` and gate the relevant relation on it. + +## Working sample + +The schema, sample tuples, and assertions are in [openfga/sample-stores/stores/banking](https://github.com/openfga/sample-stores/tree/main/stores/banking). For the broader pattern of "permissions plus runtime context", see [Modeling ABAC](/docs/best-practices/modeling-abac) and [Contextual and Time-Based Authorization](/docs/modeling/contextual-time-based-authorization). diff --git a/docs/content/industries/crm.mdx b/docs/content/industries/crm.mdx new file mode 100644 index 0000000000..3d04d138ba --- /dev/null +++ b/docs/content/industries/crm.mdx @@ -0,0 +1,54 @@ +--- +title: CRM Authorization with OpenFGA +description: Model accounts, contacts, leads, opportunities, and territory-based access in OpenFGA for Salesforce-style and HubSpot-style CRM systems. +sidebar_position: 6 +slug: /industries/crm +--- + +# CRM Authorization with OpenFGA + +CRM platforms — Salesforce, HubSpot, Pipedrive, Zoho — share an authorization shape that pure RBAC handles badly: a sales rep owns *their* accounts and deals, a manager sees *the team's* pipeline, and admins see everything. Account ownership is per-record and changes constantly as deals are reassigned. + +The full sample is in [openfga/sample-stores/stores/crm](https://github.com/openfga/sample-stores/tree/main/stores/crm). + +## Core resources and relations + +- **organization** — the tenant. Roles: `admin`, `sales_manager`, `sales_rep`. +- **account** — has an `owner` (the sales rep) and inherits visibility for managers and admins from the org. +- **contact** — belongs to an `account`; visibility flows through the parent. +- **lead** — has an `owner` who can convert; only admins delete. +- **opportunity** — belongs to an `account`, has its own `owner`; close-deal permissions limited to owner or sales manager. +- **task** and **engagement** — visible to owner and sales managers; only the owner can mark a task complete. + +## What the model gets right + +**Per-record ownership, not just role.** A sales rep doesn't see *every* account in the org — just the ones they own. Reassigning an account is a single tuple write (delete the old `owner`, write the new one), and the next `Check` reflects it instantly. + +**Inherited visibility on contacts.** Contacts don't carry their own ACL. They inherit from the parent account through [parent-child relationships](/docs/modeling/parent-child), so reassigning the account also reassigns visibility to its contacts. + +**Manager rollup without flattening.** A sales manager sees the team's pipeline by virtue of the org-level role, not by being added as an owner on each account. The model keeps "I own this deal" distinct from "I can see this deal" — important for forecasting and commission audits. + +**Close-deal as its own permission.** "Can edit the opportunity" and "can close the deal" are different relations. Reps edit their own deals but only owners or sales managers can mark won/lost. + +## Where this maps to OpenFGA features + +| CRM requirement | OpenFGA feature | +| --- | --- | +| Per-rep account ownership | direct `owner` relation on `account` | +| Manager sees the team's pipeline | org-level `sales_manager` role | +| Contact inherits from account | [parent-child relationships](/docs/modeling/parent-child) | +| Lead conversion vs. lead deletion | separate `can_convert` / `can_delete` relations | +| Territory-based access | userset relations: territory members → accounts | +| Multi-tenant CRM SaaS | tenant-scoped types, see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | +| Reassign all of a rep's accounts | bulk tuple writes against `owner` | + +## Common extensions + +- **Territories.** Add a `territory` type with `member` relations; grant accounts to a territory and the member set inherits visibility. +- **Account teams.** Multiple reps on a strategic account — `account_executive`, `solutions_engineer`, `customer_success` as distinct relations rather than a bag of "team members". +- **Pipeline-stage gating.** Use [conditions](/docs/modeling/conditions) so certain edits (e.g. discount approval) are only allowed when the opportunity's stage matches a runtime parameter. +- **Sharing rules.** Express Salesforce-style "share with my manager's manager's team" as a userset traversal rather than a nightly job. + +## Working sample + +Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/crm](https://github.com/openfga/sample-stores/tree/main/stores/crm). For the broader pattern of role at the tenant plus per-record ownership, see [Modeling Roles](/docs/best-practices/modeling-roles). diff --git a/docs/content/industries/ecommerce.mdx b/docs/content/industries/ecommerce.mdx new file mode 100644 index 0000000000..b789b10ee0 --- /dev/null +++ b/docs/content/industries/ecommerce.mdx @@ -0,0 +1,60 @@ +--- +title: E-commerce Authorization with OpenFGA +description: Model multi-store organizations, staff roles, products, orders, and refunds in OpenFGA for Shopify-style and BigCommerce-style platforms. +sidebar_position: 4 +slug: /industries/ecommerce +--- + +# E-commerce Authorization with OpenFGA + +E-commerce platforms — Shopify-style multi-store SaaS, BigCommerce, Etsy-like marketplaces — share an authorization shape that role-only systems struggle with: a merchant has *one organization* but *many stores*, and staff often have different permissions in different stores. A category manager might be a `manager` in the apparel store and just a `staff` member in the home-goods store. + +OpenFGA models the org/store split natively. The full sample lives in [openfga/sample-stores/stores/ecommerce](https://github.com/openfga/sample-stores/tree/main/stores/ecommerce). + +## Core resources and relations + +Two-tier role model: + +- **organization** roles — `admin`, `store_manager`, `member`. Granted at the org level, inherited where it makes sense. +- **store** roles — `owner`, `manager`, `staff`. Per-store and *not* automatically inherited from org membership. + +Object types: + +- **organization** — the merchant tenant. +- **store** — belongs to an `organization`, has its own `owner`, `manager`s, and `staff`. +- **product** — belongs to a `store`. Managers create/edit/delete; the staff member who authored a product can edit it; everyone in the store can read. +- **customer** — staff view, managers edit, admins delete. +- **order** — the customer who placed the order can view and cancel; staff fulfill; managers refund; admins delete. +- **store_settings** — owners configure, staff view. + +## What the model gets right + +**Org admin ≠ store owner.** A platform admin who needs to see every store's data is *not* automatically an owner of any individual store. Role inheritance is opt-in, expressed in the model rather than buried in application code. + +**Author-edit on products.** The staff member who created a product can edit it, even though staff in general can only view. This is a classic ReBAC pattern (`creator` relation as a fallback for `manager`). + +**Customer self-service.** Customers see only their own orders by being the direct subject of `customer` on the order, which lets the same `Check` API serve both the merchant admin UI and the customer-facing storefront. + +**Refund vs. delete.** Refund is a manager-level permission; delete is admin only. Two separate relations means audits can answer "who could have refunded this order" independently of "who could have deleted it". + +## Where this maps to OpenFGA features + +| E-commerce requirement | OpenFGA feature | +| --- | --- | +| Multi-store organizations | [parent-child relationships](/docs/modeling/parent-child) | +| Per-store roles | direct relations on `store` | +| Author can edit own product | union of `manager` and `creator` relations | +| Customer self-service on orders | direct `customer` relation on `order` | +| Refund vs. delete distinction | separate relations rather than a single `manage` | +| Tenant isolation per merchant | see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | +| Marketplaces with seller workspaces | seller as a `store`-equivalent type | + +## Common extensions + +- **Seller marketplaces (Etsy-like).** Add a `seller` type that owns one or more stores; platform admins moderate but don't own. +- **Returns and exchanges.** Treat each return as its own object with its own approval chain (manager creates, admin approves) rather than overloading the order's relations. +- **Promotions and discounts.** Discount codes can be modeled as objects with `creator`, `approver`, and `viewer` relations — useful when finance must approve before a code goes live. + +## Working sample + +Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/ecommerce](https://github.com/openfga/sample-stores/tree/main/stores/ecommerce). For the broader multi-tenant question (one OpenFGA store across many merchants), see [Multi-Tenant SaaS](/docs/use-cases/multi-tenant-saas). diff --git a/docs/content/industries/healthcare.mdx b/docs/content/industries/healthcare.mdx new file mode 100644 index 0000000000..7d05689f13 --- /dev/null +++ b/docs/content/industries/healthcare.mdx @@ -0,0 +1,63 @@ +--- +title: Healthcare Authorization with OpenFGA +description: Model patients, providers, encounters, and PHI access in OpenFGA. Care teams, facility hierarchy, and sensitive-field permissions. +sidebar_position: 2 +slug: /industries/healthcare +--- + +# Healthcare Authorization with OpenFGA + +Healthcare applications — EHR/EMR systems like Epic, Cerner, or Athenahealth, plus patient portals, telehealth, and clinical research tools — share a few hard authorization problems: + +- A single patient is seen by many providers across many facilities, and the *care team* changes per encounter. +- Some fields on a patient record (allergies, blood type, date of birth) are more sensitive than others and need a separate permission. +- Records like diagnoses and treatments don't have their own ACL — they inherit visibility from the encounter and patient they belong to. +- Facility directors need to manage facility metadata without inheriting access to every patient seen there. + +A relationship-based model handles all of this naturally. The full sample is in [openfga/sample-stores/stores/healthcare](https://github.com/openfga/sample-stores/tree/main/stores/healthcare). + +## Core resources and relations + +The healthcare sample defines these object types: + +- **organization** — the tenant (a hospital network, a clinic group). Roles: `admin`, `provider`, `nurse`, `medical_records`, `member`. +- **facility** — a physical site. Has a `director` and a parent `organization`. +- **patient** — belongs to an `organization`. The care team grants `can_view_record` to assigned providers and nurses, and a separate `can_view_sensitive` permission gates PHI fields. +- **encounter** — a visit. Belongs to a `patient` and a `facility`. Authorized providers can `can_view`, `can_create`, but typically not `can_edit` after the encounter closes. +- **diagnosis** and **treatment** — inherit visibility from `encounter` → `patient`, so the care team automatically sees them without separate tuples. +- **medication** (formulary) — admin-only writes, everyone-reads. + +## What the model gets right + +**Care-team scoping.** Rather than granting a provider access to all patients in a facility, the model writes a tuple per `(provider, patient)` care-team relationship. Revocation is one tuple delete. + +**PHI as a distinct permission.** `can_view_record` and `can_view_sensitive` are separate relations. A nurse on the care team can see the chart but not the sensitive fields without an explicit grant — useful when separating clinical-care views from billing or research access. + +**Record inheritance.** Diagnoses and treatments use object-to-object relationships ([parent-child](/docs/modeling/parent-child)) so the care team inherits access through the encounter and patient. No separate ACL maintenance on every clinical note. + +**Facility hierarchy.** Facility directors are scoped to facility-level data (staff lists, equipment, schedules) — they do *not* inherit `can_view_sensitive` on patients seen at their facility. That separation is hard in role-only systems and easy in OpenFGA. + +## Where this maps to OpenFGA features + +| Healthcare requirement | OpenFGA feature | +| --- | --- | +| Care-team membership per patient | direct tuples on `patient` | +| PHI vs. non-PHI fields | two relations: `can_view_record`, `can_view_sensitive` | +| Diagnosis/treatment inheritance | [parent-child relationships](/docs/modeling/parent-child) | +| Multi-tenant org isolation | tenant-scoped types, see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | +| Time-bounded access (rotations, locums) | [contextual tuples](/docs/interacting/contextual-tuples) | +| Break-glass access logging | tuple writes flow through the [Read Changes API](/docs/interacting/read-tuple-changes) | + +## Compliance posture + +OpenFGA itself isn't a HIPAA product — compliance is a property of how you run it. What OpenFGA does give you: + +- A single, queryable source of truth for *who can see which patient record* at any moment, which audit programs typically require. +- Append-only tuple change history via the Read Changes API. +- Separation between checks (read-only) and writes (admin-only) at the API level. + +For the operational side — encryption at rest, logging, deployment in a HIPAA boundary — see [Running OpenFGA in Production](/docs/best-practices/running-in-production). + +## Working sample + +The full schema, sample tuples, and assertion tests for the model above live in [openfga/sample-stores/stores/healthcare](https://github.com/openfga/sample-stores/tree/main/stores/healthcare). You can load it with the [FGA CLI](/docs/getting-started/cli) and run the included assertions to see the model behave end-to-end. diff --git a/docs/content/industries/human-resources.mdx b/docs/content/industries/human-resources.mdx new file mode 100644 index 0000000000..4284a5a05b --- /dev/null +++ b/docs/content/industries/human-resources.mdx @@ -0,0 +1,52 @@ +--- +title: HR & HRIS Authorization with OpenFGA +description: Model employee records, manager hierarchies, payroll, benefits, and PII isolation in OpenFGA for Workday-style HRIS and directory systems. +sidebar_position: 5 +slug: /industries/human-resources +--- + +# HR & HRIS Authorization with OpenFGA + +HRIS platforms — Workday, BambooHR, Rippling, plus internal directory systems — have to answer authorization questions that role-only systems struggle with: *the employee* always sees their own record, *direct managers* see their reports but not all employees, *HR* sees everything, and *PII* (SSN, date of birth, home address) is gated separately from the rest of the profile. + +The full sample model is in [openfga/sample-stores/stores/human-resources](https://github.com/openfga/sample-stores/tree/main/stores/human-resources). + +## Core resources and relations + +- **organization** — the tenant (employer). Roles: `admin`, `hr_manager`, `member`. +- **employee** — has a direct `self` relation to the user, a `manager` relation, and `can_view_sensitive` gated to the employee themself plus HR. +- **team** — nests recursively via a `parent_team` relation; a team `lead` inherits from parent teams. +- **payroll** and **benefits** — admin/HR-manager only. +- **time_off_request** — initiated by an `employee`, approved by their `manager`. + +## What the model gets right + +**Employee self-service.** The employee is the direct subject on their own record, so the same `Check` API serves the employee portal *and* the HR admin console — no separate query path. + +**Manager hierarchy without inheritance to PII.** A manager can see direct reports' non-sensitive profile data, but `can_view_sensitive` does *not* flow through the manager chain. Skip-level managers see organizational data but not SSNs. + +**PII as a distinct relation.** `can_view_record` and `can_view_sensitive` are separate, so you can grant a recruiter or a contractor partial profile access without exposing identifiers covered by GDPR, CCPA, or local equivalents. + +**Time-off approvals routed by relationship.** The approver is whoever the `manager` relation points at on the day of the request — no separate "approval routing" table. + +## Where this maps to OpenFGA features + +| HR requirement | OpenFGA feature | +| --- | --- | +| Employee self-service on own record | direct `self` relation on `employee` | +| Manager-of-direct-reports view | `manager` relation, evaluated per-employee | +| Skip-level org chart visibility | recursive `parent_team` on `team` | +| PII vs. non-PII separation | two relations: `can_view_record`, `can_view_sensitive` | +| Payroll / benefits restricted to HR | direct relations on `payroll` / `benefits` | +| Time-off approval routing | `approver` resolves through `manager` | +| Multi-tenant HRIS SaaS | tenant-scoped types, see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | + +## Common extensions + +- **Compensation reviews.** A `compensation_review` object with `reviewer` (manager), `approver` (HR), and `subject` (employee) — three relations, three views into the same object. +- **Org redesigns.** Re-parenting a team is one tuple write; everyone above and below sees the new hierarchy on the next `Check`. +- **Contractors and contingent workers.** Add a `contractor` type with a subset of `employee` relations rather than overloading employee with a type flag. + +## Working sample + +Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/human-resources](https://github.com/openfga/sample-stores/tree/main/stores/human-resources). For the broader pattern of "role at the org, scoped relationships per record", see [Modeling Roles](/docs/best-practices/modeling-roles). diff --git a/docs/content/industries/lms.mdx b/docs/content/industries/lms.mdx new file mode 100644 index 0000000000..ecd56805ba --- /dev/null +++ b/docs/content/industries/lms.mdx @@ -0,0 +1,54 @@ +--- +title: LMS Authorization with OpenFGA +description: Model courses, classes, enrollments, content authorship, and grading workflows in OpenFGA for Canvas-style and Moodle-style learning platforms. +sidebar_position: 7 +slug: /industries/lms +--- + +# LMS Authorization with OpenFGA + +Learning management systems — Canvas, Moodle, Blackboard, plus internal corporate training platforms — combine curriculum hierarchy with per-user enrollment. A student enrolled in *one* class section sees *that* class's materials but not the rest of the course catalog; an instructor authoring a quiz can edit it even when their general role is "instructor, not owner". + +The full sample lives in [openfga/sample-stores/stores/lms](https://github.com/openfga/sample-stores/tree/main/stores/lms). + +## Core resources and relations + +- **organization** — the tenant (school or training department). Roles: `admin`, `instructor`, `student`. +- **course** — has an `owner` who publishes; enrolled `instructor`s edit content; enrolled `student`s view. +- **class** — a section of a course with its own enrollment list; instructors and admins enroll students. +- **content** — authored by a user; the `author` can edit/delete; instructors org-wide can edit. +- **activity** — assignments and quizzes. The `creator` and any course `instructor` can grade; only the assigned `student` can submit. +- **collection** — groupings of materials; collection `owner` and instructors organize, only admins delete. + +## What the model gets right + +**Enrollment is per-class, not per-course.** A student enrolled in section A of *Intro to Biology* doesn't automatically see section B. Modeling the class as its own object with its own enrollment list keeps cross-section data isolated, which matters for grades and discussion threads. + +**Author-edit on content.** Whoever authored a piece of content can keep editing it even after their role changes — implemented as a `creator` (or `author`) relation in union with the broader `instructor` relation. + +**Submit is exclusive to the assigned student.** Only the student the activity is assigned to can submit, preventing one student from submitting on behalf of another. The grading side is open to instructors and the activity's creator independently. + +**Publish gated to owners.** A course can be edited by enrolled instructors but only published by the `owner` or an admin. This separates "draft work" from "goes live to students", which curriculum review processes depend on. + +## Where this maps to OpenFGA features + +| LMS requirement | OpenFGA feature | +| --- | --- | +| Per-class enrollment | direct relations on `class`, evaluated per-section | +| Course → class hierarchy | [parent-child relationships](/docs/modeling/parent-child) | +| Author can edit own content | union of `instructor` and `author` relations | +| Submit-only-if-assigned | direct `student` relation on `activity` | +| Grade-by-creator-or-instructor | union relation on `activity` | +| Publish gated to owners | distinct `can_publish` separate from `can_edit` | +| Multi-tenant LMS SaaS | tenant-scoped types, see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | + +## Common extensions + +- **Cohorts and programs.** Group classes into a `program` type with its own enrollment list — useful for multi-course certifications. +- **Proctored exams.** Add a `proctor` relation on activity, distinct from `creator` and `instructor`, so exam supervision can be delegated without granting grading rights. +- **Time-bounded enrollment.** Use [conditions](/docs/modeling/conditions) to expire student access at the end of a term without rewriting tuples. +- **Parent / guardian access.** A `guardian` relation on the student lets a parent view a child's grades through a single tuple, without granting any course-edit rights. + +## Working sample + +Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/lms](https://github.com/openfga/sample-stores/tree/main/stores/lms). For the broader pattern of "role at the org, scoped relationships per record", see [Modeling Roles](/docs/best-practices/modeling-roles). diff --git a/docs/content/industries/overview.mdx b/docs/content/industries/overview.mdx new file mode 100644 index 0000000000..1798c32253 --- /dev/null +++ b/docs/content/industries/overview.mdx @@ -0,0 +1,25 @@ +--- +title: OpenFGA by Industry +description: How healthcare, banking, e-commerce, HR, CRM, and LMS teams model fine-grained authorization with OpenFGA. +sidebar_position: 1 +slug: /industries +--- + +# OpenFGA by Industry + +OpenFGA's [modeling language](/docs/configuration-language) is general-purpose, but the *shape* of an authorization model differs by industry. Healthcare worries about who can see PHI on a given encounter; banking worries about per-transaction limits and delegation; e-commerce worries about which staff member can refund an order in which store. The pages below walk through how each domain is typically modeled, with links to working samples in [openfga/sample-stores](https://github.com/openfga/sample-stores#industry-examples). + +## Industries + +- **[Healthcare](/docs/industries/healthcare)** — patients, providers, encounters, and PHI access. Care-team membership, facility hierarchy, and a separate permission for sensitive fields (allergies, blood type, DOB). +- **[Banking](/docs/industries/banking)** — account managers, account owners, and per-transaction limits. Delegation between owners and staff, with conditions on transaction amount. +- **[E-commerce](/docs/industries/ecommerce)** — multi-store organizations with org-level and store-level roles. Shoppers manage their own orders; staff fulfill them; managers refund; admins delete. +- **[Human Resources](/docs/industries/human-resources)** — employee records, manager hierarchies, and per-field sensitivity (compensation, performance reviews) gated by HRBP role and reporting line. +- **[CRM](/docs/industries/crm)** — accounts, opportunities, and territories with owner / team / read-only-collaborator relations and per-field visibility for pipeline and forecast data. +- **[Learning Management](/docs/industries/lms)** — courses, cohorts, and enrollments with instructor / TA / learner roles and assignment-level grading permissions. + +## Why industry-specific models matter + +The OpenFGA team has published [23+ sample stores](https://github.com/openfga/sample-stores#industry-examples) covering accounting, ads, applicant tracking, calendars, call centers, chat, CRM, developer portals, expenses, file storage, hospitality, HR, IoT, issue tracking, knowledge bases, KMS, LMS, manufacturing, payments, and real estate. The pages above are the industries where OpenFGA adopters most often ask "is this how others do it?" — and the answer is meaningful enough to be worth writing down. + +If your industry isn't here, the pattern pages under [Use Cases](/docs/use-cases) (multi-tenant SaaS, microservices, AI agents, RAG, MCP) are domain-neutral and apply across verticals. diff --git a/docs/content/intro.mdx b/docs/content/intro.mdx index 91df34a031..ca4b7f1f98 100644 --- a/docs/content/intro.mdx +++ b/docs/content/intro.mdx @@ -63,6 +63,36 @@ Inspired by [Google’s Zanzibar](https://zanzibar.academy), Google’s internal description: 'Learn about how to get started with modeling your permission system in {ProductName}.', link: './modeling/getting-started', id: './modeling/getting-started', + }, + { + title: 'Adopters', + description: 'Production case studies from Agicap, Docker, Grafana Labs, Headspace, OpenLane, Read AI, Vitrolife, and Zuplo.', + link: './customers', + id: './customers', + }, + { + title: 'Use Cases', + description: 'Patterns for AI agents, RAG, MCP servers, multi-tenant SaaS, and microservices.', + link: './use-cases', + id: './use-cases', + }, + { + title: 'Industries', + description: 'Sample models for healthcare, banking, e-commerce, HR, CRM, and LMS.', + link: './industries', + id: './industries', + }, + { + title: 'Learn Authorization', + description: 'Zanzibar, ReBAC vs RBAC, ABAC vs ReBAC, and fine-grained authorization explained.', + link: './learn', + id: './learn', + }, + { + title: 'Compare', + description: 'OpenFGA versus SpiceDB, Cerbos, Permit.io, Oso, Ory Keto, OPA, and other engines.', + link: './compare', + id: './compare', } ]} /> diff --git a/docs/content/learn/abac-vs-rebac.mdx b/docs/content/learn/abac-vs-rebac.mdx new file mode 100644 index 0000000000..aa5156d723 --- /dev/null +++ b/docs/content/learn/abac-vs-rebac.mdx @@ -0,0 +1,44 @@ +--- +title: ABAC vs ReBAC +description: ABAC decides on attributes; ReBAC decides on relationships. Learn which fits which problem — and how OpenFGA covers both via conditions. +sidebar_position: 5 +slug: /learn/abac-vs-rebac +--- + +# ABAC vs. ReBAC + +**Attribute-Based Access Control (ABAC)** decides based on attributes of the principal, resource, and request — *"role = engineer AND region = EU"*. **Relationship-Based Access Control (ReBAC)** decides based on relationships in a stored graph — *"user is editor of doc, which is in folder shared with team"*. + +| | ABAC | ReBAC | +| --- | --- | --- | +| Source of truth | Attributes on the request and resource | Stored relationship tuples | +| Ergonomics for hierarchy | Needs explicit attribute lookups | Native | +| Ergonomics for attribute checks | Native | Needs conditions/contextual data | +| Reverse queries | Hard — must enumerate resources | First-class | +| Common engines | OPA (Rego), Cedar, AWS IAM | OpenFGA, SpiceDB, Ory Keto | + +## When ABAC fits + +- The decision is **stateless from the engine's perspective** — everything needed is on the request: claims, headers, resource metadata. +- Rules involve **comparisons** (`amount < limit`, `region = EU`, `time within business_hours`). +- You want policies versioned in Git as code or YAML, not stored as data. + +## When ReBAC fits + +- The decision depends on **relationships that change at write time** — membership, sharing, hierarchy. +- You need **reverse queries** for UI rendering or filtered listings. +- Permissions need to traverse multi-hop graphs (team → project → folder → document). + +## OpenFGA does both + +OpenFGA is a ReBAC engine, but it covers ABAC needs through [conditions](/docs/modeling/conditions) (CEL expressions evaluated at check time) and [contextual tuples](/docs/interacting/contextual-tuples) (request-time data passed into the check). For most applications that's enough — you don't need a second engine. Agicap, for example, runs [conditional ReBAC](/docs/adopters/agicap) for attribute-driven rules inside a Zanzibar model. + +## When you might want both + +If you also need policy outside the application — Kubernetes admission, Terraform validation, service-mesh request rules — pair OpenFGA with a [policy engine](/docs/learn/policy-engine) like OPA. OpenFGA inside the app, OPA at the infrastructure layer. + +## Related reading + +- [ReBAC overview](/docs/learn/rebac) +- [RBAC vs. ReBAC](/docs/learn/rbac-vs-rebac) +- [Conditions](/docs/modeling/conditions) diff --git a/docs/content/learn/fine-grained-authorization.mdx b/docs/content/learn/fine-grained-authorization.mdx new file mode 100644 index 0000000000..b40e0d349f --- /dev/null +++ b/docs/content/learn/fine-grained-authorization.mdx @@ -0,0 +1,38 @@ +--- +title: What is Fine-Grained Authorization? +description: Fine-grained authorization decides access at the resource and action level. Learn what FGA is, what it buys you, and how OpenFGA implements it. +sidebar_position: 6 +slug: /learn/fine-grained-authorization +--- + +# What is Fine-Grained Authorization? + +**Fine-grained authorization (FGA)** means deciding access at the level of the individual resource and action, rather than at the role or coarse-scope level. *"Alice can edit document-42"* is fine-grained; *"Alice is an editor"* is not. + +## What "fine-grained" actually buys you + +- **Per-resource sharing.** A user can be granted access to one document without inheriting access to everything in the workspace. +- **Hierarchical inheritance.** Access to a folder grants access to its documents — but only that folder, not every folder. +- **Reverse queries.** *"List every document this user can read"* — the query a UI needs to render correctly. +- **Cross-tenant collaboration.** Granting a single resource to an external user without making them a tenant member. + +Coarse-grained models can simulate these with enough effort, but the authorization layer ends up duplicating a graph database in roles tables. Fine-grained engines store the graph directly. + +## How OpenFGA implements FGA + +- A typed [model](/docs/configuration-language) defines resource types and the relations between them. +- [Tuples](/docs/concepts) record specific relationships between specific principals and specific resources. +- The [check API](/docs/interacting/relationship-queries) answers per-action questions in milliseconds. +- [Conditions](/docs/modeling/conditions) cover attribute-driven cases inside the same model. + +## Where FGA matters most + +- **Document management and collaboration** (Google Drive, Notion, Figma patterns). +- **Multi-tenant SaaS** with external sharing. +- **AI agents and RAG**, where each user must only see their slice of the corpus — covered in [AI agent authorization](/docs/use-cases/ai-agent-authorization). + +## Related reading + +- [Authorization Concepts](/docs/authorization-concepts) +- [ReBAC overview](/docs/learn/rebac) +- [Customer case studies](/docs/adopters) diff --git a/docs/content/learn/overview.mdx b/docs/content/learn/overview.mdx new file mode 100644 index 0000000000..c4a5945384 --- /dev/null +++ b/docs/content/learn/overview.mdx @@ -0,0 +1,17 @@ +--- +title: Learn Authorization +description: Authorization concepts explained — Zanzibar, ReBAC, RBAC, ABAC, fine-grained authorization, and policy engines — with OpenFGA examples. +sidebar_position: 1 +slug: /learn +--- + +# Learn Authorization + +A short reference for the concepts behind OpenFGA. Each page is a focused explainer with an OpenFGA example so you can map the theory to the model language. + +- **[Google Zanzibar](/docs/learn/zanzibar)** — the paper that started the ReBAC wave. +- **[ReBAC: Relationship-Based Access Control](/docs/learn/rebac)** — what it is, when to use it. +- **[RBAC vs. ReBAC](/docs/learn/rbac-vs-rebac)** — when roles run out. +- **[ABAC vs. ReBAC](/docs/learn/abac-vs-rebac)** — attributes versus relationships. +- **[Fine-Grained Authorization](/docs/learn/fine-grained-authorization)** — what "fine-grained" actually buys you. +- **[Policy Engines vs. Relationship Engines](/docs/learn/policy-engine)** — Rego, Cedar, OPL, and where OpenFGA fits. diff --git a/docs/content/learn/policy-engine.mdx b/docs/content/learn/policy-engine.mdx new file mode 100644 index 0000000000..b705efbc8f --- /dev/null +++ b/docs/content/learn/policy-engine.mdx @@ -0,0 +1,37 @@ +--- +title: Policy Engines vs Relationship Engines +description: Policy engines like OPA and Cedar evaluate rules over data. Relationship engines like OpenFGA store and query the graph. Here's when to use which. +sidebar_position: 7 +slug: /learn/policy-engine +--- + +# Policy Engines vs. Relationship Engines + +The authorization toolbox has two broad shapes: + +- **Policy engines** — OPA (Rego), Cedar, Ory's OPL, Oso's Polar — evaluate policies expressed in a DSL against input data passed on each request. The engine is **stateless**; you supply the data. +- **Relationship engines** — OpenFGA, SpiceDB, Ory Keto, all in the [Zanzibar](/docs/learn/zanzibar) tradition — store relationship tuples in a database and answer queries against the stored graph. The engine **is the database**. + +## When a policy engine fits + +- Decisions are mostly **attribute-driven** — claims, resource metadata, request context. +- The same policy needs to apply across **multiple domains** — Kubernetes admission, Terraform, service-mesh, application requests. +- You want policy in **Git as code**, reviewed via PR, deployed as bundles. + +OPA's [graduated CNCF status](https://www.cncf.io/projects/open-policy-agent-opa/) and broad ecosystem make it the default choice in this category. + +## When a relationship engine fits + +- Decisions depend on **relationships that change at write time** — group membership, document sharing, folder hierarchy, multi-tenant ownership. +- You need **reverse queries** — *"list every resource this user can read"* — which inherently require a stored graph. +- Permissions are **per-resource and per-user**, not just per-attribute. + +## You can use both + +Pairing them is common: a relationship engine for application authorization, a policy engine at the infrastructure or admission layer. Inside the application, OpenFGA covers most attribute-driven rules with [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples), so a second engine isn't always needed. + +## Related reading + +- [OpenFGA vs. OPA](/docs/compare/opa) +- [OpenFGA vs. Cerbos](/docs/compare/cerbos) +- [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac) diff --git a/docs/content/learn/rbac-vs-rebac.mdx b/docs/content/learn/rbac-vs-rebac.mdx new file mode 100644 index 0000000000..5e042cfcbb --- /dev/null +++ b/docs/content/learn/rbac-vs-rebac.mdx @@ -0,0 +1,39 @@ +--- +title: RBAC vs ReBAC +description: RBAC assigns roles to users; ReBAC models relationships between users and resources. Learn when roles run out and ReBAC takes over. +sidebar_position: 4 +slug: /learn/rbac-vs-rebac +--- + +# RBAC vs. ReBAC + +**Role-Based Access Control (RBAC)** assigns named roles to users; the role implies a set of permissions. **Relationship-Based Access Control (ReBAC)** models permissions as relationships between specific users and specific resources. + +| | RBAC | ReBAC | +| --- | --- | --- | +| Unit of grant | Role assigned to user | Relation between user and resource | +| Hierarchy | Awkward — needs role-per-tenant tricks | Native — relations traverse parent edges | +| Sharing | Per-resource roles explode | One tuple per share | +| Reverse queries | Needs a separate index | First-class (`list-objects`) | +| Best fit | Small teams, flat access models | Hierarchies, sharing, multi-tenancy | + +## Where RBAC runs out + +The classic break is multi-tenancy. With RBAC, "admin of organization 42" becomes a distinct role from "admin of organization 43" — multiplied by every tenant. Tools paper over this with `org:admin` *scopes*, but at that point you've reinvented relationships, badly. + +Sharing breaks RBAC for the same reason: "viewer of doc-42" is not a useful role to assign across an entire organization. + +## Where ReBAC fits naturally + +- "User X is a member of team Y, which owns project Z, which contains document D" — four tuples, one check, no role explosion. +- Cross-tenant sharing — a user in one org can be an `editor` of a single document in another org without granting them anything else. + +## Doing both in OpenFGA + +OpenFGA is a [ReBAC](/docs/learn/rebac) engine that **also models RBAC cleanly**: a role is a relation on a tenant, and the model lets the same user be a different role in different tenants without any duplication. The [Modeling Roles](/docs/best-practices/modeling-roles) guide walks through the pattern. + +## Related reading + +- [ReBAC overview](/docs/learn/rebac) +- [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac) +- [Modeling roles](/docs/best-practices/modeling-roles) diff --git a/docs/content/learn/rebac.mdx b/docs/content/learn/rebac.mdx new file mode 100644 index 0000000000..1bf971404b --- /dev/null +++ b/docs/content/learn/rebac.mdx @@ -0,0 +1,41 @@ +--- +title: What is ReBAC (Relationship-Based Access Control)? +description: ReBAC models permissions as relationships between users and resources. Learn what ReBAC is, when to use it, and how OpenFGA implements it. +sidebar_position: 3 +slug: /learn/rebac +--- + +# What is ReBAC? + +**Relationship-Based Access Control (ReBAC)** models permissions as relationships between users and resources, rather than as roles assigned to users or attributes attached to objects. *"Alice is an editor of doc-42"* and *"doc-42 is in folder-7"* and *"folder-7 is in workspace-A"* are all relationships; whether Alice can read doc-42 falls out of traversing those relationships. + +## Why ReBAC + +Most real authorization questions are graph questions: + +- "Can this user read this document?" depends on whether they're a member of the team that owns the project that contains the document. +- "Can this user comment on this issue?" depends on whether they belong to the repository's organization. +- "Can this agent invoke this tool?" depends on whether the user who delegated to the agent has access to it. + +Roles can model the simple cases but blow up combinatorially as soon as hierarchy or sharing enters the picture. ReBAC stores the edges directly and queries them. + +## ReBAC in OpenFGA + +OpenFGA is a ReBAC engine in the [Zanzibar](/docs/learn/zanzibar) tradition. You define types and relations in a typed [DSL](/docs/configuration-language), write tuples like `(document:42, editor, user:alice)`, and call [check](/docs/interacting/relationship-queries) or `list-objects`. + +## When ReBAC is the right tool + +- Permissions involve **hierarchy or sharing** — folders, workspaces, organizations, teams. +- You need **reverse queries** — *"list every document this user can read"*. +- Permissions change at **write time** (someone is added to a team) rather than evaluated from request attributes. + +## When something else fits better + +- Pure attribute checks (department equals "engineering", region equals "EU") are better served by attributes — see [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac). OpenFGA covers these with [conditions](/docs/modeling/conditions). +- Infrastructure or admission policy across many domains — see [policy engines](/docs/learn/policy-engine). + +## Related reading + +- [RBAC vs. ReBAC](/docs/learn/rbac-vs-rebac) +- [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac) +- [Authorization Concepts](/docs/authorization-concepts) diff --git a/docs/content/learn/zanzibar.mdx b/docs/content/learn/zanzibar.mdx new file mode 100644 index 0000000000..d11728aa2b --- /dev/null +++ b/docs/content/learn/zanzibar.mdx @@ -0,0 +1,43 @@ +--- +title: What is Google Zanzibar? +description: Google Zanzibar is the paper behind Google's global authorization system. Learn what Zanzibar is, what it solved, and how OpenFGA implements it. +sidebar_position: 2 +slug: /learn/zanzibar +--- + +# What is Google Zanzibar? + +[Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/) is a 2019 paper from Google describing the authorization system used by Google Drive, YouTube, Calendar, Cloud, and most other Google products. It is the source of the design pattern OpenFGA implements. + +## What Zanzibar actually is + +Zanzibar is a globally distributed database of **relationship tuples** that answers two questions in milliseconds at Google scale: + +1. *"Is user U related to object O via relation R?"* (check) +2. *"What objects of type T is user U related to via relation R?"* (reverse index / list-objects) + +Two ideas make it work: + +- A **typed schema** (called a *namespace configuration* in the paper) defining types and the relations between them — for example, that `document#viewer` includes `document#editor`. +- A **tuple store** indexed for both forward and reverse queries, with a consistency mechanism (Zookies) that lets clients tie permission checks to the version of the data they read. + +## What Zanzibar solved + +Before Zanzibar, every Google product had its own authorization layer. Cross-product features ("share a Drive doc to a Calendar event guest") meant authorization logic had to be duplicated and kept in sync. Zanzibar gave Google one model, one store, one decision surface. + +## How OpenFGA maps to Zanzibar + +OpenFGA implements the same model: + +- **Schema** — written in the [OpenFGA DSL](/docs/configuration-language). +- **Tuples** — stored in PostgreSQL, MySQL, or SQLite. +- **Check** and **List-objects** — exposed via the [API](/docs/interacting/relationship-queries). +- **Conditions** (CEL) — OpenFGA's equivalent of caveats for attribute-based decisions. + +OpenFGA does not replicate Zanzibar's globally distributed Spanner-backed architecture; it is designed to run on your existing databases. For most applications, that's the point — Zanzibar's *model* is what's valuable, not its operational scale. + +## Related reading + +- [ReBAC: Relationship-Based Access Control](/docs/learn/rebac) +- [Fine-Grained Authorization](/docs/learn/fine-grained-authorization) +- [Authorization Concepts](/docs/authorization-concepts) diff --git a/docs/content/use-cases/ai-agent-authorization.mdx b/docs/content/use-cases/ai-agent-authorization.mdx new file mode 100644 index 0000000000..b07c45534e --- /dev/null +++ b/docs/content/use-cases/ai-agent-authorization.mdx @@ -0,0 +1,34 @@ +--- +title: AI Agent Authorization with OpenFGA +description: Authorize AI agents with OpenFGA. Model agents as principals, delegate user permissions, and enforce least privilege for autonomous and copilot agents. +sidebar_position: 2 +slug: /use-cases/ai-agent-authorization +--- + +# AI Agent Authorization + +Autonomous and copilot agents act on behalf of users — but "on behalf of" is not the same as "as." A well-modeled agent has its **own identity**, inherits **only the permissions it actually needs**, and can be revoked independently of the user it serves. + +OpenFGA models this directly: + +- **Agents are first-class principals.** They appear on the left side of tuples like users do, so checks, list-objects, and list-users all work on agent identities. +- **Permissions are delegated, not copied.** Relations like `can_act_on_behalf_of` make the delegation explicit and revocable. +- **Scope is bounded.** An agent can be granted access to a specific workspace, document set, or tool — not the user's entire footprint. + +## The modeling pieces + +The modeling guides below cover the building blocks: + +- [Agents as principals](/docs/modeling/agents/agents-as-principals) — define an `agent` type and use it like a user. +- [Task-based authorization](/docs/modeling/agents/task-based-authorization) — bound an agent to a single task or session. +- [RAG authorization](/docs/use-cases/rag-authorization) — filter retrieved context by the user's permissions before the model sees it. +- [MCP server authorization](/docs/use-cases/mcp-server-authorization) — protect tools and resources exposed via Model Context Protocol. + +## Why ReBAC fits agent workflows + +Agent workflows are graph-shaped: a user grants an agent access to a workspace; the workspace contains documents; some documents are shared from other workspaces. Roles can't model that without exploding into per-resource role definitions. Relationships do it natively — and OpenFGA's [reverse queries](/docs/interacting/relationship-queries) (`list-objects`, `list-users`) let the agent ask *"what can I see?"* without scanning everything. + +## Related reading + +- [Authorization for AI Agents](/docs/modeling/agents) — the full modeling guide, with examples. +- [OpenFGA vs. policy engines for agents](/docs/compare) — when relationship modeling is the right tool for agent workflows. diff --git a/docs/content/use-cases/mcp-server-authorization.mdx b/docs/content/use-cases/mcp-server-authorization.mdx new file mode 100644 index 0000000000..5eeb25c4ad --- /dev/null +++ b/docs/content/use-cases/mcp-server-authorization.mdx @@ -0,0 +1,36 @@ +--- +title: MCP Server Authorization with OpenFGA +description: Authorize Model Context Protocol (MCP) servers with OpenFGA. Per-tool, per-resource permission checks on every MCP request. +sidebar_position: 4 +slug: /use-cases/mcp-server-authorization +--- + +# MCP Server Authorization + +A Model Context Protocol server exposes tools and resources to an AI client. That makes it an authorization surface: every tool call and every resource read should be checked against the calling user (and the calling agent, if they're separate principals). + +## The pattern + +Model the MCP server's surface in OpenFGA: + +- `tool` and `resource` types, each with relations like `can_invoke` or `can_read`. +- `user` and `agent` types as principals. +- Relationship tuples that grant specific users or agents access to specific tools and resources. + +On every MCP request: + +1. Identify the caller (user, agent, or both via [agents as principals](/docs/modeling/agents/agents-as-principals)). +2. Call [check](/docs/interacting/relationship-queries) against the requested tool or resource. +3. Allow or deny. + +For listing tools and resources to the client, use [list-objects](/docs/interacting/relationship-queries) so the client only ever sees what it could actually invoke. + +## Bounding agent scope + +A common pitfall is granting agents the same broad role as the user they serve. Instead, model the delegation explicitly: the user grants the agent access to *this workspace* or *this set of tools* for *this session*, and revoke when done. The [task-based authorization](/docs/modeling/agents/task-based-authorization) guide walks through this. + +## Related reading + +- [MCP authorization modeling guide](/docs/modeling/agents/mcp-authorization) +- [AI agent authorization](/docs/use-cases/ai-agent-authorization) +- [Authorization for AI Agents overview](/docs/modeling/agents) diff --git a/docs/content/use-cases/microservices-authorization.mdx b/docs/content/use-cases/microservices-authorization.mdx new file mode 100644 index 0000000000..4ce085e229 --- /dev/null +++ b/docs/content/use-cases/microservices-authorization.mdx @@ -0,0 +1,35 @@ +--- +title: Microservices Authorization with OpenFGA +description: Centralize authorization across microservices with OpenFGA. One model, one store, one set of decisions — instead of a roles table per service. +sidebar_position: 6 +slug: /use-cases/microservices-authorization +--- + +# Microservices Authorization + +Microservices repeat the same pattern: each service ends up with its own roles table, its own permission checks, and its own subtle drift. OpenFGA centralizes that into a single authorization service that every microservice consults. + +## The pattern + +- **One OpenFGA store** holds the model and tuples for the whole system. +- **Each microservice** calls `check`, `list-objects`, or `list-users` over the OpenFGA API rather than implementing its own permission logic. +- **Writes go through the service that owns the relationship** (e.g. the membership service writes `member` tuples; the document service writes `can_share` tuples). + +## Why this beats per-service authorization + +- **One model to reason about.** Cross-service questions (*"can this user read this document via their team's project access?"*) become a single graph traversal instead of a coordination dance. +- **No more drifted roles tables.** A relation defined once in the OpenFGA model is the same relation in every service. +- **Reverse queries work across services.** `list-objects` returns every resource the user can access, regardless of which service owns it. + +## Operational shape + +- **Latency.** Run OpenFGA close to the calling services. Read AI runs [5,200 RPS at 20 ms p99](/docs/adopters/read-ai) on PostgreSQL — a useful reference for sizing. +- **Caching.** OpenFGA supports check caching; pair with short cache TTLs in clients for hot paths. +- **Observability.** Treat authorization decisions as a first-class metric. Track check rate, latency, and authorization failures per service. + +## Related reading + +- [Running OpenFGA in production](/docs/best-practices/running-in-production) +- [Source of truth](/docs/best-practices/source-of-truth) +- [Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) +- [Customer case studies](/docs/adopters) diff --git a/docs/content/use-cases/multi-tenant-saas.mdx b/docs/content/use-cases/multi-tenant-saas.mdx new file mode 100644 index 0000000000..0c1c289d37 --- /dev/null +++ b/docs/content/use-cases/multi-tenant-saas.mdx @@ -0,0 +1,41 @@ +--- +title: Multi-Tenant SaaS Authorization with OpenFGA +description: Authorize multi-tenant SaaS applications with OpenFGA. Strict tenant isolation, shared infrastructure, and cross-tenant sharing where you want it. +sidebar_position: 5 +slug: /use-cases/multi-tenant-saas +--- + +# Multi-Tenant SaaS Authorization + +Multi-tenant SaaS is one of OpenFGA's most common production shapes. The same binary serves every tenant; tenant boundaries are enforced inside the authorization model rather than at the database or service layer. + +## The pattern + +Model an `organization` (or `tenant`) type and make every other resource belong to it: + +- `organization` has members, admins, and owners. +- `workspace`, `project`, and `document` types each declare a `parent` relation pointing at an `organization`. +- Relations like `can_view` and `can_edit` traverse the parent edge so organization membership grants the right access automatically. + +This gives you: + +- **Hard tenant isolation by default.** A user only has tuples for the organizations they belong to, so list-objects naturally returns nothing cross-tenant. +- **Cross-tenant sharing when you want it.** A document can also have individually granted users from other organizations — same model, no special case. +- **A single store for all tenants.** Operationally cheaper than per-tenant databases; the tuple count grows linearly with usage rather than fan-out. + +## Production references + +- [Grafana Labs](/docs/adopters/grafana) — single platform serving multi-tenant Grafana Cloud and embedded OSS. +- [Agicap](/docs/adopters/agicap) — 8,000+ customer organizations on a single OpenFGA store with conditional ReBAC. +- [Read AI](/docs/adopters/read-ai) — 5.3B+ tuples across many tenants on PostgreSQL. + +## Modeling building blocks + +- [Modeling parent-child](/docs/modeling/parent-child) +- [Modeling roles](/docs/best-practices/modeling-roles) +- [Source of truth](/docs/best-practices/source-of-truth) + +## Related reading + +- [Customer case studies](/docs/adopters) +- [Microservices authorization](/docs/use-cases/microservices-authorization) diff --git a/docs/content/use-cases/overview.mdx b/docs/content/use-cases/overview.mdx new file mode 100644 index 0000000000..4b8736e7eb --- /dev/null +++ b/docs/content/use-cases/overview.mdx @@ -0,0 +1,25 @@ +--- +title: OpenFGA Use Cases +description: Production-ready OpenFGA patterns for AI agents, RAG, MCP servers, multi-tenant SaaS, and microservices authorization. +sidebar_position: 1 +slug: /use-cases +--- + +# OpenFGA Use Cases + +OpenFGA is a Zanzibar-style relationship engine. The patterns below are the ones that show up most often in production — each links to the modeling guide and, where available, a customer reference that runs the pattern at scale. + +## AI and agent authorization + +- **[AI agent authorization](/docs/use-cases/ai-agent-authorization)** — modeling agents as principals, delegating user permissions, and bounding what an autonomous agent can do. +- **[RAG authorization](/docs/use-cases/rag-authorization)** — filtering retrieved documents by the user's permissions before they reach the model. +- **[MCP server authorization](/docs/use-cases/mcp-server-authorization)** — enforcing tool and resource access in a Model Context Protocol server. + +## Application authorization + +- **[Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas)** — one OpenFGA store, many tenants, with strict isolation. Used by Grafana Labs, Agicap, and others. +- **[Microservices authorization](/docs/use-cases/microservices-authorization)** — a central authorization service that every microservice consults, instead of each service rolling its own roles table. + +## Customer references + +The [adopters section](/docs/adopters) documents how Read AI, Agicap, Grafana, Docker, and Zuplo run OpenFGA in production — including the patterns above at scale. diff --git a/docs/content/use-cases/rag-authorization.mdx b/docs/content/use-cases/rag-authorization.mdx new file mode 100644 index 0000000000..e41b0d7334 --- /dev/null +++ b/docs/content/use-cases/rag-authorization.mdx @@ -0,0 +1,33 @@ +--- +title: RAG Authorization with OpenFGA +description: Enforce per-user permissions on retrieval-augmented generation. Filter retrieved documents through OpenFGA before they reach the model. +sidebar_position: 3 +slug: /use-cases/rag-authorization +--- + +# RAG Authorization + +Retrieval-augmented generation pipelines retrieve documents from a corpus and hand them to the model as context. If the corpus contains anything the asking user shouldn't see, the model will happily summarize it back to them. The fix is to **filter retrieval results by the user's permissions** before the model ever sees them. + +## The pattern + +1. The user asks the agent a question. +2. The retriever pulls candidate documents (vector search, keyword, hybrid). +3. **OpenFGA filters** the candidates: for each candidate `doc:X`, check whether `user:Y` has `can_view`. Or call [list-objects](/docs/interacting/relationship-queries) once to get the full set of documents this user can read, then intersect. +4. Only the surviving documents are passed to the model as context. + +The same model and prompt now produce different — and correct — answers per user, because the context they see is scoped to what they're allowed to see. + +## Why list-objects matters here + +For small corpora, per-document checks are fine. For larger ones, `list-objects` is dramatically cheaper: one call returns the full set of documents the user can read, and you intersect that with the retriever's candidates. This is exactly the case OpenFGA's reverse queries are designed for. + +## Conditions and contextual data + +If access depends on document attributes (classification, region, time window) as well as relationships, use [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples). Both are evaluated at check time without round-tripping back to your application. + +## Related reading + +- [RAG authorization modeling guide](/docs/modeling/agents/rag-authorization) +- [AI agent authorization](/docs/use-cases/ai-agent-authorization) +- [List-objects API](/docs/interacting/relationship-queries) diff --git a/docs/sidebars.js b/docs/sidebars.js index 03a5f542c7..b21149b255 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -470,6 +470,90 @@ const sidebars = { } ], }, + { + type: 'category', + collapsible: true, + collapsed: true, + label: 'Adopters', + link: { type: 'doc', id: 'content/adopters/overview' }, + items: [ + { type: 'doc', label: 'Agicap', id: 'content/adopters/agicap' }, + { type: 'doc', label: 'Docker', id: 'content/adopters/docker' }, + { type: 'doc', label: 'Grafana Labs', id: 'content/adopters/grafana' }, + { type: 'doc', label: 'Read AI', id: 'content/adopters/read-ai' }, + { type: 'doc', label: 'Zuplo', id: 'content/adopters/zuplo' }, + { type: 'doc', label: 'Headspace', id: 'content/adopters/headspace' }, + { type: 'doc', label: 'OpenLane', id: 'content/adopters/openlane' }, + { type: 'doc', label: 'Vitrolife Group', id: 'content/adopters/vitrolife' }, + ], + }, + { + type: 'category', + collapsible: true, + collapsed: true, + label: 'Industries', + link: { type: 'doc', id: 'content/industries/overview' }, + items: [ + { type: 'doc', label: 'Healthcare', id: 'content/industries/healthcare' }, + { type: 'doc', label: 'Banking', id: 'content/industries/banking' }, + { type: 'doc', label: 'E-commerce', id: 'content/industries/ecommerce' }, + { type: 'doc', label: 'Human Resources', id: 'content/industries/human-resources' }, + { type: 'doc', label: 'CRM', id: 'content/industries/crm' }, + { type: 'doc', label: 'Learning Management', id: 'content/industries/lms' }, + ], + }, + { + type: 'category', + collapsible: true, + collapsed: true, + label: 'Use Cases', + link: { type: 'doc', id: 'content/use-cases/overview' }, + items: [ + { type: 'doc', label: 'AI Agent Authorization', id: 'content/use-cases/ai-agent-authorization' }, + { type: 'doc', label: 'RAG Authorization', id: 'content/use-cases/rag-authorization' }, + { type: 'doc', label: 'MCP Server Authorization', id: 'content/use-cases/mcp-server-authorization' }, + { type: 'doc', label: 'Multi-Tenant SaaS', id: 'content/use-cases/multi-tenant-saas' }, + { type: 'doc', label: 'Microservices Authorization', id: 'content/use-cases/microservices-authorization' }, + ], + }, + { + type: 'category', + collapsible: true, + collapsed: true, + label: 'Learn', + link: { type: 'doc', id: 'content/learn/overview' }, + items: [ + { type: 'doc', label: 'Zanzibar', id: 'content/learn/zanzibar' }, + { type: 'doc', label: 'What is ReBAC?', id: 'content/learn/rebac' }, + { type: 'doc', label: 'RBAC vs ReBAC', id: 'content/learn/rbac-vs-rebac' }, + { type: 'doc', label: 'ABAC vs ReBAC', id: 'content/learn/abac-vs-rebac' }, + { type: 'doc', label: 'Fine-Grained Authorization', id: 'content/learn/fine-grained-authorization' }, + { type: 'doc', label: 'Policy vs Relationship Engines', id: 'content/learn/policy-engine' }, + ], + }, + { + type: 'doc', + label: 'Alternatives', + id: 'content/alternatives/overview', + }, + { + type: 'category', + collapsible: true, + collapsed: true, + label: 'Compare', + link: { type: 'doc', id: 'content/compare/overview' }, + items: [ + { type: 'doc', label: 'OpenFGA vs SpiceDB', id: 'content/compare/spicedb' }, + { type: 'doc', label: 'OpenFGA vs Cerbos', id: 'content/compare/cerbos' }, + { type: 'doc', label: 'OpenFGA vs Permit.io', id: 'content/compare/permit' }, + { type: 'doc', label: 'OpenFGA vs Oso', id: 'content/compare/oso' }, + { type: 'doc', label: 'OpenFGA vs Ory Keto', id: 'content/compare/keto' }, + { type: 'doc', label: 'OpenFGA vs OPA', id: 'content/compare/opa' }, + { type: 'doc', label: 'OpenFGA vs WorkOS FGA', id: 'content/compare/workos' }, + { type: 'doc', label: 'OpenFGA vs Keycloak', id: 'content/compare/keycloak' }, + { type: 'doc', label: 'OpenFGA vs Auth0 FGA', id: 'content/compare/auth0-fga' }, + ], + }, ], }; diff --git a/docusaurus.config.js b/docusaurus.config.js index 2c66f50bd2..b1b147c09f 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -217,6 +217,10 @@ import dev.openfga.sdk.api.configuration.ClientConfiguration;`, property: 'og:image', content: 'https://openfga.dev/img/og-rich-embed.png', }, + { property: 'og:type', content: 'website' }, + { name: 'twitter:card', content: 'summary_large_image' }, + { name: 'twitter:site', content: '@openfga' }, + { name: 'twitter:image', content: 'https://openfga.dev/img/og-rich-embed.png' }, ], structuredData: { excludedRoutes: ['/blog/authors', '/blog/archive', '/blog/tags', '/api/service'], diff --git a/src/features/LandingPage/FeaturesSection/index.tsx b/src/features/LandingPage/FeaturesSection/index.tsx index 066aeae8f1..2959159421 100644 --- a/src/features/LandingPage/FeaturesSection/index.tsx +++ b/src/features/LandingPage/FeaturesSection/index.tsx @@ -59,8 +59,8 @@ const features = [ icon: , title: 'Get Involved', content: [ - "Join OpenFGA's active Slack and GitHub community, check out existing RFCs to understand where the project is headed, and learn more about how to take part by reading our CONTRIBUTING.md.", - 'Learn how to get involved → ', + "Join OpenFGA's active Slack and GitHub community, check out existing RFCs to understand where the project is headed, and learn more about how to take part by reading our CONTRIBUTING.md.", + 'Learn how to get involved → ', ], }, ]; diff --git a/src/features/LandingPage/HeroHeader/index.tsx b/src/features/LandingPage/HeroHeader/index.tsx index 02ca84b28b..4fbebe980a 100644 --- a/src/features/LandingPage/HeroHeader/index.tsx +++ b/src/features/LandingPage/HeroHeader/index.tsx @@ -65,6 +65,21 @@ const HeroHeader = () => {

+ + OpenFGA + {} {siteConfig.tagline}

diff --git a/src/theme/NotFound.tsx b/src/theme/NotFound.tsx index decc3f7c25..aab110d37b 100644 --- a/src/theme/NotFound.tsx +++ b/src/theme/NotFound.tsx @@ -28,7 +28,7 @@ const NotFound = (): JSX.Element => (

If you navigated here from a broken link on the OpenFGA website, please{' '} - + open an issue on GitHub . diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000000..5346c6ca6d --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://openfga.dev/sitemap.xml From ee6109c647752cfc9d3d0a2114d2b37b303bd3d5 Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 16:57:44 -0300 Subject: [PATCH 03/13] feat: removed adopters/compare and improved the rest --- docs/content/adopters/agicap.mdx | 53 ------- docs/content/adopters/docker.mdx | 46 ------ docs/content/adopters/grafana.mdx | 55 ------- docs/content/adopters/headspace.mdx | 46 ------ docs/content/adopters/openlane.mdx | 44 ------ docs/content/adopters/overview.mdx | 38 ----- docs/content/adopters/read-ai.mdx | 48 ------ docs/content/adopters/vitrolife.mdx | 47 ------ docs/content/adopters/zuplo.mdx | 54 ------- docs/content/alternatives/overview.mdx | 147 ------------------ docs/content/compare/auth0-fga.mdx | 37 ----- docs/content/compare/cerbos.mdx | 11 +- docs/content/compare/keto.mdx | 39 ----- docs/content/compare/keycloak.mdx | 58 ------- docs/content/compare/opa.mdx | 39 ----- docs/content/compare/oso.mdx | 2 +- docs/content/compare/overview.mdx | 33 ---- docs/content/compare/permit.mdx | 9 +- docs/content/compare/spicedb.mdx | 43 ----- docs/content/compare/workos.mdx | 44 ------ .../industries/applicant-tracking-system.mdx | 59 +++++++ docs/content/industries/banking.mdx | 14 +- docs/content/industries/crm.mdx | 12 +- docs/content/industries/ecommerce.mdx | 16 +- docs/content/industries/healthcare.mdx | 18 ++- docs/content/industries/human-resources.mdx | 12 +- docs/content/industries/lms.mdx | 12 +- docs/content/industries/overview.mdx | 13 +- docs/content/intro.mdx | 12 -- docs/content/learn/abac-vs-rebac.mdx | 36 ++++- .../learn/fine-grained-authorization.mdx | 24 ++- docs/content/learn/overview.mdx | 8 +- docs/content/learn/policy-engine.mdx | 20 ++- docs/content/learn/rbac-vs-rebac.mdx | 32 +++- docs/content/learn/rebac.mdx | 34 +++- docs/content/learn/zanzibar.mdx | 49 ++++-- .../use-cases/ai-agent-authorization.mdx | 9 +- .../use-cases/mcp-server-authorization.mdx | 45 ++++-- .../use-cases/microservices-authorization.mdx | 41 ++++- docs/content/use-cases/multi-tenant-saas.mdx | 46 +++++- docs/content/use-cases/overview.mdx | 11 +- docs/content/use-cases/rag-authorization.mdx | 28 +++- docs/sidebars.js | 41 +---- static/llms.txt | 36 +++++ 44 files changed, 474 insertions(+), 1047 deletions(-) delete mode 100644 docs/content/adopters/agicap.mdx delete mode 100644 docs/content/adopters/docker.mdx delete mode 100644 docs/content/adopters/grafana.mdx delete mode 100644 docs/content/adopters/headspace.mdx delete mode 100644 docs/content/adopters/openlane.mdx delete mode 100644 docs/content/adopters/overview.mdx delete mode 100644 docs/content/adopters/read-ai.mdx delete mode 100644 docs/content/adopters/vitrolife.mdx delete mode 100644 docs/content/adopters/zuplo.mdx delete mode 100644 docs/content/alternatives/overview.mdx delete mode 100644 docs/content/compare/auth0-fga.mdx delete mode 100644 docs/content/compare/keto.mdx delete mode 100644 docs/content/compare/keycloak.mdx delete mode 100644 docs/content/compare/opa.mdx delete mode 100644 docs/content/compare/overview.mdx delete mode 100644 docs/content/compare/spicedb.mdx delete mode 100644 docs/content/compare/workos.mdx create mode 100644 docs/content/industries/applicant-tracking-system.mdx diff --git a/docs/content/adopters/agicap.mdx b/docs/content/adopters/agicap.mdx deleted file mode 100644 index 2335c6cdc8..0000000000 --- a/docs/content/adopters/agicap.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Agicap Case Study -description: How European fintech Agicap runs OpenFGA in production for 8,000+ customers at 250 RPS with conditional ReBAC across every backend service. -sidebar_position: 2 -slug: /adopters/agicap ---- - -# Agicap: Fine-grained authorization for a European fintech platform - -[Agicap](https://agicap.com) is a European fintech that helps small, medium, and large enterprises manage cash flow in real time. Its SaaS platform serves more than 8,000 customers across industries, and every backend service in the platform validates access through OpenFGA. - -## At a glance - -| | | -| --- | --- | -| **Industry** | Fintech / cash flow management | -| **In production since** | April 2023 | -| **Scale** | ~250 requests per second, 8,000+ customers | -| **Deployment** | Self-hosted, on-premises | -| **Key features used** | ReBAC, conditional relationships | - -## Why OpenFGA - -Agicap needed an open-source authorization layer with a strong community, on-premises deployment for compliance, and a model flexible enough to express financial-product permissions that pure RBAC could not. They evaluated alternatives such as Oso and concluded OpenFGA was the most stable option that fit those requirements, with approachable maintainers and clear documentation. - -The team specifically chose ReBAC over an RBAC redesign because it let them express fine-grained relationships without re-inventing authorization logic inside every service. Learn more about that trade-off in [RBAC vs ReBAC](/docs/authorization-concepts). - -## Architecture and scale - -- All backend services call OpenFGA via an internal **secure facade** rather than the OpenFGA API directly. The facade enforces application-level rules on top of OpenFGA so the data plane is never exposed. -- Authorization is enforced consistently across development, pre-production, load-test, and production environments. -- Performance work over time pushed Agicap from a deeper hierarchy to a flatter authorization model, which improved both query latency and scalability — a pattern documented in the [performance best practices](/docs/best-practices). - -## Engineering with the community - -Agicap is an active upstream contributor: - -- Engineers from the platform and SRE teams open pull requests against `openfga/openfga` to fix bugs and tune performance. -- The team participates in the monthly OpenFGA community call. -- Agicap has co-presented OpenFGA talks with maintainers at KubeCon EU 2024 (Paris) and KubeCon NA 2024 (Salt Lake City). - -When the team filed a critical performance issue, the upstream maintainers shipped a fix within 24 hours. - -## Outcomes - -- A single, evolvable authorization layer behind every backend service. -- Faster delivery of new permissions — schema changes replace code changes. -- Cost savings from running self-hosted instead of a proprietary alternative. -- Confidence at production scale with 8,000+ customers and continuous traffic. - -## Source - -This case study is based on the public CNCF TOC adopter interview with Pauline Jamin, Head of Engineering – Finance and Core at Agicap, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga), and a [presentation in the OpenFGA community meeting on Agicap's OpenFGA deployment](https://www.youtube.com/watch?v=XBHqGFfe-K4). diff --git a/docs/content/adopters/docker.mdx b/docs/content/adopters/docker.mdx deleted file mode 100644 index 40d331c670..0000000000 --- a/docs/content/adopters/docker.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Docker Case Study -description: How Docker migrated to OpenFGA with a parallel-run strategy and now uses ReBAC to centralize permissions across products. -sidebar_position: 3 -slug: /adopters/docker ---- - -# Docker: Centralizing permissions with ReBAC - -[Docker](https://www.docker.com) provides tools that help developers build, share, run, and verify applications across environments. Docker adopted OpenFGA in early 2024 and uses it to centralize authorization across an expanding set of products. - -## At a glance - -| | | -| --- | --- | -| **Industry** | Developer tools / platform | -| **In production since** | March 2024 | -| **Scale** | 100-150 requests per second | -| **Deployment** | Self-hosted | -| **Key features used** | ReBAC, DSL, SDKs and CLI | - -## Why OpenFGA - -Docker evaluated several access-control systems — including Authzed and Ory Keto, plus non-ReBAC options — before choosing OpenFGA. The decision came down to: - -- **ReBAC** as a more flexible model than RBAC for the products Docker builds. -- **Self-hosted, open source**, easy to run locally (a working stack via Docker Compose in under five minutes). -- **CNCF backing** and contributors with strong security pedigree. -- Mature **SDKs, APIs, and testing tools**. -- A responsive maintainer community. - -## Migration approach - -Docker ran OpenFGA in **parallel with the existing authorization system**: every permission check went to both engines, and results were compared. Once both systems consistently agreed, traffic was incrementally cut over to OpenFGA. The parallel-run pattern is one we recommend for any production migration — see the [adoption patterns guide](/docs/best-practices). - -## Outcomes - -- Permission changes that previously required code changes are now centralized in the authorization model file. -- New Docker products integrate into the access-control system faster. -- Operational overhead for permission updates dropped substantially. - -The early scaling pain points the team hit — particularly batch checks across many records — were addressed quickly by upstream releases. - -## Source - -This case study is based on the public CNCF TOC adopter interview with Gurleen Sethi, Senior Software Engineer at Docker, Inc., available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/grafana.mdx b/docs/content/adopters/grafana.mdx deleted file mode 100644 index 2650ee325c..0000000000 --- a/docs/content/adopters/grafana.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Grafana Labs Case Study -description: Why Grafana Labs replaced its single-tenant access control engine with OpenFGA to power multi-tenant Grafana Cloud and embedded OSS deployments. -sidebar_position: 4 -slug: /adopters/grafana ---- - -# Grafana Labs: From single-tenant engine to multi-tenant ReBAC - -[Grafana Labs](https://grafana.com) is the company behind Grafana, Loki, Tempo, Mimir, and the LGTM observability stack. Grafana adopted OpenFGA to replace an internal single-tenant access-control engine that no longer fit the multi-tenant architecture of Grafana Cloud. - -## At a glance - -| | | -| --- | --- | -| **Industry** | Observability | -| **First experiments** | February 2024 | -| **Mainline integration** | August 2024 | -| **Version** | v1.10.0 | -| **Deployment** | Multi-tenant SaaS, embedded OSS, on-premises | - -## Why OpenFGA - -Grafana needed an engine that did **two** things competitors did not bundle: - -1. **Authorization evaluation** — like an OPA-style policy engine. -2. **A storage layer for permissions** — a tuple store with a per-tenant schema. - -That combination, plus OpenFGA's **CNCF affiliation** and explicit governance policy, made it preferable to building yet another in-house system or adopting a project that could change its license later. - -## Architecture and scale - -OpenFGA runs in three Grafana environments: - -- **Development and staging** — already serving internal production workloads. -- **External production** — deployed to a single cluster in a pre-production capacity, shadowing real traffic to validate consistency and performance before broader rollout. - -The team standardized on the **PostgreSQL adapter** after finding the MySQL adapter less mature. Refactoring Grafana's legacy schema toward OpenFGA-native modeling produced significant performance gains — an outcome echoed by the [source-of-truth best practice](/docs/best-practices). - -## Upstream investment - -- Grafana **maintains the SQLite adapter**, which was contributed back to OpenFGA so it can ship with embedded Grafana. -- One Grafana engineer is listed in `SECURITY-INSIGHTS.yml` as a core maintainer; three others have contributed changes. -- Future areas of contribution include **pluggable storage** (so non-core storage adapters work without rebuilding OpenFGA) and **observability** improvements. -- KubeCon EU 2025 talk: *From Chaos To Control: Migrating Access Control* by Jo Guerreiro and Poovamraj Thanganadar Thiagarajan. - -## Outcomes - -- One authorization platform spans Grafana Cloud (multi-tenant SaaS) and Grafana OSS (embedded), removing the need to maintain separate engines. -- Schema-driven iteration replaced engine-tuning work the team used to do manually. -- The team is targeting **list-users** to enable reverse permission search — showing all users who can access a given resource — a capability the legacy engine never had. - -## Source - -This case study is based on the public CNCF TOC adopter interview with Joao Guerreiro, Senior Engineering Manager at Grafana Labs, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/headspace.mdx b/docs/content/adopters/headspace.mdx deleted file mode 100644 index 91d8e8f35c..0000000000 --- a/docs/content/adopters/headspace.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Headspace Case Study -description: How Headspace authorizes Ebb, its empathetic AI companion, with OpenFGA — driving end-to-end checks down from 10–15 seconds to 10–15 milliseconds. -sidebar_position: 6 -slug: /adopters/headspace ---- - -# Headspace: Authorizing an empathetic AI companion at consumer scale - -[Headspace](https://www.headspace.com) is a global mental-health platform with over 105 million app downloads and 90 million lives reached. Its AI companion, Ebb, has handled more than 6 million conversations since launching, and every message Ebb processes runs through an OpenFGA authorization check. - -## At a glance - -| | | -| --- | --- | -| **Industry** | Mental health / consumer health | -| **Use case** | AI companion (Ebb) gating | -| **Scale** | 90M+ lives, 105M+ downloads, 6M+ Ebb messages | -| **Deployment** | Self-hosted | -| **Key features used** | BatchCheck, contextual tuples, graph design, Terraform-managed model | - -## Why OpenFGA - -Ebb is gated on a combination of business rules: who the member is contracted through, which country they are messaging from, which language their app is set to, and whether their employer has opted them out. A pure RBAC system could not express this without exploding into a role per combination, and a hand-rolled SQL check ran 10–15 seconds in the worst case — unacceptable for a chat experience. - -The Headspace team chose OpenFGA so the AI gating rules could live in a single relationship graph the platform team owned, with the same model evaluated from every service that fronts Ebb. - -## Architecture - -- **Wrapper API in front of OpenFGA.** Application services do not call the OpenFGA store directly. They call an internal authorization service that fans out **four parallel [BatchCheck](/docs/interacting/relationship-queries) requests** — assigned-to-Ebb, country-allowed, language-allowed, and not-blocked-by-org — and combines the results. -- **Inverted graph for performance.** The original model put the AI feature at the top with users below; a check meant traversing the entire user population. Flipping the direction so the user is the object and Ebb access is reached through unions of small relations dropped end-to-end latency from 10–15 seconds to 10–15 milliseconds. -- **Bidirectional tuple writes.** When a member-to-feature relationship is written, the inverse tuple is written at the same time, keeping reads cheap in either direction. -- **Terraform-managed model and static tuples.** The authorization model and the static enablement tuples (countries, languages, default org policies) ship through the Headspace [OpenFGA Terraform provider](https://github.com/openfga/terraform-provider-openfga), so model changes go through the same review pipeline as infrastructure. -- **Hidden model version.** The wrapper API does not expose the OpenFGA model ID to consumers; rolling forward to a new model version is a deploy of the wrapper, not a coordinated change across every caller. -- **SDK 1.10 conflict resolution.** The team adopted the conflict-resolution behavior shipped in SDK 1.10 to safely handle concurrent tuple writes during high-traffic enrollment events. - -## Outcomes - -- **End-to-end Ebb authorization in 10-15 ms**, down from 10-15 seconds. -- **Per-user blocking added without touching call sites** — a new relation in the model and a tuple write was enough; no service had to ship code. -- **Single source of truth** for AI gating rules, owned by the platform team and reviewed in Terraform. -- **Operational headroom** to extend Ebb gating (new languages, new contracts, new opt-out criteria) without rewriting application code. - -## Source - -This case study is based on a [presentation in the OpenFGA community meeting by Jeremy, principal engineer at Headspace](https://www.youtube.com/watch?v=xCu39aG7B1A). Supporting public material on Ebb is available at [headspace.com](https://www.headspace.com). diff --git a/docs/content/adopters/openlane.mdx b/docs/content/adopters/openlane.mdx deleted file mode 100644 index e54c98b879..0000000000 --- a/docs/content/adopters/openlane.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: OpenLane Case Study -description: How compliance-automation startup OpenLane wires OpenFGA into ent at the data-access layer, with overfetch + BatchCheck replacing slow ListObjects. -sidebar_position: 7 -slug: /adopters/openlane ---- - -# OpenLane: Authorization at the data-access layer for compliance automation - -[OpenLane](https://theopenlane.io) is an open-source compliance automation platform that helps teams achieve and maintain SOC 2, ISO 27001, and similar attestations. OpenFGA is wired into its data-access layer, so every GraphQL query and mutation is authorized without each resolver having to remember to ask. - -## At a glance - -| | | -| --- | --- | -| **Industry** | Compliance automation (GRC) | -| **Stack** | Go, GraphQL (gqlgen), [ent](https://entgo.io/) ORM, PostgreSQL, Kubernetes | -| **Deployment** | Self-hosted; separate Postgres databases for application data and OpenFGA | -| **Key features used** | BatchCheck, contextual tuples, object-owned cascading permissions, FGA-driven feature flags | - -## Why OpenFGA - -OpenLane builds the kind of platform whose customers will themselves be audited. Authorization had to be defensible end-to-end — every read, every write, every export — and could not live in a `if user.role == "admin"` switch sprinkled across resolvers. The team picked OpenFGA so authorization decisions were centralized, modeled as relationships, and could evolve without code changes in every service. - -A second motivation was packaging: OpenLane sells modules, and the same engine that grants access to a record can grant access to a feature. OpenFGA is the source of truth for **both**. - -## Architecture - -- **Authorization as ent middleware.** OpenLane uses [ent](https://entgo.io/) hooks to write tuples on every mutation and interceptors plus policies to evaluate every query. Resolvers do not call the OpenFGA SDK directly; the data-access layer does. -- **Object-owned mixin.** A reusable ent mixin attaches `owner` and parent relations to any record type, so cascading permissions ("an editor of the parent program can edit each control") are declared once and applied uniformly. -- **Overfetch + BatchCheck instead of ListObjects.** An early implementation used [ListObjects](/docs/interacting/relationship-queries) and saw ~8-second worst-case latency for large result sets. The team switched to overfetching candidates from Postgres (capped at 100 per page, up to 1,000 overfetched) and running [BatchCheck](/docs/interacting/relationship-queries) to filter in a single round trip. Total counts are computed via a separate query that short-circuits when the user is an admin. -- **In-house wrapper packages.** Three small Go packages — `FGX` (typed helpers around the OpenFGA SDK), `NFGA` (no-op fakes for unit tests), and `access-map` (declarative relation registration) — keep callers honest and make adding a new entity type a few lines of mixin configuration. -- **OpenFGA-as-feature-flags.** Module entitlements ("does this tenant have the policy module?") live in the same OpenFGA store as record-level permissions, so a check that returns `false` because the user is not an editor and a check that returns `false` because the tenant did not buy the module use the same call site. - -## Outcomes - -- **Authorization can't be forgotten.** Hooks and interceptors mean a new entity type inherits authorization automatically. -- **List-style endpoints went from ~8 s worst case to comfortably under a second** at expected page sizes, without changing the public API. -- **One store, two jobs.** Record permissions and feature entitlements live in OpenFGA, so packaging changes don't require a second policy system. -- **Auditable end-to-end.** Tuple writes happen in the same transaction boundary as the underlying data mutation, so the access graph and the data it protects don't drift. - -## Source - -This case study is based on a [presentation in the OpenFGA community meeting by Sarah Funkhouser, co-founder and head of engineering at OpenLane](https://www.youtube.com/watch?v=ZdlftEKQ0UA). The OpenLane platform itself is open source — the integration patterns above are visible in the [theopenlane GitHub organization](https://github.com/theopenlane). diff --git a/docs/content/adopters/overview.mdx b/docs/content/adopters/overview.mdx deleted file mode 100644 index a989e8577a..0000000000 --- a/docs/content/adopters/overview.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: OpenFGA Adopters and Case Studies -description: Production OpenFGA case studies from Agicap, Docker, Grafana Labs, Headspace, OpenLane, Read AI, Vitrolife, Zuplo and other adopters running fine-grained authorization at scale. -sidebar_position: 1 -slug: /adopters ---- - -# OpenFGA in production - -OpenFGA is deployed in production at fintechs, observability platforms, AI products, developer-tool companies, and API platforms. The case studies below are based on public [CNCF TOC adopter interviews](https://github.com/cncf/toc/tree/main/projects/openfga). - -## Featured case studies - -| Adopter | Industry | In production since | Scale | -| --- | --- | --- | --- | -| [Read AI](/docs/adopters/read-ai) | AI meeting intelligence | April 2023 | 5,200 RPS peak, 5.3B+ tuples | -| [Agicap](/docs/adopters/agicap) | Fintech | April 2023 | ~250 RPS, 8,000+ customers | -| [Zuplo](/docs/adopters/zuplo) | API management | 2024 | 500+ RPS spikes, multi-region edge | -| [Grafana Labs](/docs/adopters/grafana) | Observability | 2024 | Multi-tenant SaaS + embedded OSS | -| [Docker](/docs/adopters/docker) | Developer tools | March 2024 | 100–150 RPS | -| [Headspace](/docs/adopters/headspace) | Mental health & consumer | 2024 | 90M lives, 6M Ebb messages, 10-15 ms p99 | -| [OpenLane](/docs/adopters/openlane) | Compliance SaaS | 2024 | ent ORM hooks, BatchCheck overfetch (100/1000) | -| [Vitrolife Group](/docs/adopters/vitrolife) | Healthcare | 2025 | Hybrid Entra + OpenFGA, hourly differential sync | - -## What these adopters have in common - -- **Self-hosted, open source.** Every adopter cited the ability to run OpenFGA themselves as a key reason for choosing it over proprietary offerings. -- **PostgreSQL at scale.** Production deployments are running on Postgres, with billions of tuples in the largest case. -- **ReBAC over RBAC.** Each team chose relationship-based access control for the flexibility it gives over flat role models. See [authorization concepts](/docs/authorization-concepts) for a refresher. -- **CNCF governance** matters. Teams explicitly contrasted CNCF stewardship with the licensing risk of source-available alternatives. - -## Adopter list - -OpenFGA is also publicly used by organizations including [Twilio](https://www.twilio.com), [Italia.it (Italian Government)](https://www.pagopa.gov.it/), [Mercado Libre](https://www.mercadolibre.com), [Wolt](https://wolt.com), [Canonical](https://canonical.com), and many more. The full, machine-readable list is maintained in the [`openfga/community` repository](https://github.com/openfga/community/blob/main/ADOPTERS.md). - -## Add your story - -If your team runs OpenFGA in production and wants to share lessons learned, open a pull request against the [`openfga/community` ADOPTERS file](https://github.com/openfga/community/blob/main/ADOPTERS.md) or join the [CNCF Slack `#openfga` channel](https://cloud-native.slack.com/archives/C06G1NNH47N). diff --git a/docs/content/adopters/read-ai.mdx b/docs/content/adopters/read-ai.mdx deleted file mode 100644 index a4f802e4eb..0000000000 --- a/docs/content/adopters/read-ai.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Read AI Case Study -description: How Read AI runs OpenFGA at 5,200 requests per second with 20ms p99 latency over more than 5 billion relationship tuples. -sidebar_position: 5 -slug: /adopters/read-ai ---- - -# Read AI: 5 billion tuples, 20ms p99 latency - -[Read AI](https://www.read.ai) is the AI meeting notetaker and assistant trusted by more than 100,000 organizations and 75% of the Fortune 500, adding more than one million new customers every month. OpenFGA backs the authorization layer that lets Read AI safely share intelligence across meetings, messages, email, and documents. - -## At a glance - -| | | -| --- | --- | -| **Industry** | AI productivity / meeting intelligence | -| **In production since** | April 28, 2023 | -| **Peak load** | 5,200 RPS | -| **Latency** | 20ms p99 / 1.8ms average | -| **Tuple count** | 5,323,283,829 (and growing) | -| **Version** | v1.8.16 | -| **Storage** | PostgreSQL | - -## Why OpenFGA - -Read AI ran a proprietary, organically built authorization system that hit performance and scalability ceilings as the platform grew. The team evaluated alternatives such as Authzed before choosing OpenFGA, citing: - -- **Zanzibar foundations** that aligned with the sharing semantics the product needed. -- **Documentation clarity**, especially the practical examples and modeling guides. -- The ability to **self-host** with predictable cost. -- Approachable, responsive maintainers. - -## Production at scale - -The self-hosted OpenFGA service handles peak load of **5,200 requests per second** with a **20ms p99 latency** and **1.8ms average latency**. The data store holds more than **5.3 billion tuples** and grows daily. - -OpenFGA upgrades are folded into a monthly cadence. The OpenFGA release pace is faster than Read AI's, but upgrades have been smooth with no significant backward-compatibility issues. - -## Outcomes - -- Confidence in secure data authorization across the entire product surface. -- Adoption of ReBAC best practices improved internal design decisions. -- Compute and hosting costs dropped versus the prior solution. -- OpenFGA has not been the bottleneck even at peak. - -## Source - -This case study is based on the public CNCF TOC adopter interview with Andrew Powers, Software Engineering Manager at Read AI, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/vitrolife.mdx b/docs/content/adopters/vitrolife.mdx deleted file mode 100644 index 08ae367e81..0000000000 --- a/docs/content/adopters/vitrolife.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Vitrolife Group Case Study -description: How Vitrolife combines Microsoft Entra ID app roles with OpenFGA fine-grained ReBAC for a .NET 10 metadata platform serving IVF clinics worldwide. -sidebar_position: 8 -slug: /adopters/vitrolife ---- - -# Vitrolife Group: Hybrid Entra + OpenFGA authorization for a .NET healthcare platform - -The [Vitrolife Group](https://www.vitrolife.com) is a Swedish medical-device and software company serving IVF clinics worldwide. Its internal metadata platform — built on .NET 10 to organize landing zones, domains, platforms, and teams — uses a hybrid authorization design: Microsoft Entra ID app roles for coarse-grained access and OpenFGA for fine-grained, per-resource decisions. - -## At a glance - -| | | -| --- | --- | -| **Industry** | Healthcare / medical devices (IVF) | -| **Stack** | .NET 10, ASP.NET Core, Microsoft Entra ID, [Wolverine](https://wolverinefx.net/), Aspire | -| **Use case** | Internal metadata platform: landing zones, domains, platforms, teams | -| **Deployment** | Self-hosted alongside the .NET API | -| **Key features used** | Contextual tuples, intersections, conditions, full + differential sync from OpenFGA | - -## Why OpenFGA - -Entra ID gave Vitrolife a managed identity layer for both human users and service principals, but app roles alone could not express *per-resource* permissions ("can edit *this* landing zone, not all of them"). The platform team layered OpenFGA underneath to model ownership and group membership without inventing a parallel directory. - -Crucially, the team wanted OpenFGA — not Entra — to be the **source of truth** for access groups, so that the application's own model of "who has access to what" did not depend on a directory operation in a separate system. - -## Architecture - -- **App-role grammar.** Every Entra app role follows `...`. Capabilities are `viewer` / `editor` / `admin`. The qualifier is either `all` (the principal may act on every record of that resource) or `self` (the principal may act only on records they have an OpenFGA relationship with). `all` injects a contextual tuple at request time; `self` falls through to a normal [Check](/docs/interacting/relationship-queries) against the relationship graph. -- **Users and service principals are uniform.** Because both human users and service principals carry app roles, the application code never branches on principal type — the OpenFGA tuple set treats them identically. -- **Type-safe C# wrappers.** `FGAObjectId` enforces `:` shape at compile time. An `FGATuple` builder makes tuple construction explicit, and relations are modeled as `snake_case` enums to match the OpenFGA wire format without stringly-typed bugs. -- **Group memberships as contextual tuples.** Entra group memberships are surfaced into OpenFGA via contextual tuples on each request, intersected with FGA group definitions so a principal must satisfy *both* the Entra group and the FGA relationship to gain access. -- **Atomic writes via transactional outbox.** SQL writes and OpenFGA tuple writes are coordinated through a [Wolverine](https://wolverinefx.net/) outbox: the database row and the queued FGA mutation commit together; consumers process the outbox to make the tuple change visible. This is eventually consistent within a small, bounded window. -- **OpenFGA → Entra sync.** A scheduled job reads OpenFGA as the source of truth and reconciles Entra access groups: an hourly **full sync** plus a **differential sync** triggered by outbox events. If a tuple is removed in OpenFGA, the corresponding Entra membership is removed on the next pass. -- **Vertical slices and Aspire local dev.** Features are organized as vertical slices; .NET Aspire orchestrates a local environment with a mocked Microsoft Graph and a local OpenFGA, so the team can run the full authorization path end-to-end on a developer laptop. - -## Outcomes - -- **One mental model for authorization** that spans humans, service principals, and resources, with Entra handling identity and OpenFGA handling relationships. -- **Per-resource permissions** without a custom directory — the existing Entra investment stays in place. -- **OpenFGA as the durable source of truth** for access groups, with Entra reconciled on a schedule rather than the other way around. -- **Atomic SQL + tuple writes** through the Wolverine outbox, removing the class of bugs where the application data and the access graph drift apart. - -## Source - -This case study is based on a [presentation in the OpenFGA community meeting by Simon Gottschlag, CTO at Co-native, working with the Vitrolife Group on its platform](https://www.youtube.com/watch?v=nwu5SiiMpM8). diff --git a/docs/content/adopters/zuplo.mdx b/docs/content/adopters/zuplo.mdx deleted file mode 100644 index 72512d4819..0000000000 --- a/docs/content/adopters/zuplo.mdx +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Zuplo Case Study -description: How API management platform Zuplo runs OpenFGA across multiple data centers with PostgreSQL global replication for edge authorization. -sidebar_position: 6 -slug: /adopters/zuplo ---- - -# Zuplo: Edge authorization across multiple data centers - -[Zuplo](https://zuplo.com) is a developer-first API management platform that helps teams build, deploy, and scale APIs globally. Zuplo uses OpenFGA to enforce fine-grained authorization at the edge across every region the platform runs in. - -## At a glance - -| | | -| --- | --- | -| **Industry** | API management | -| **In production since** | ~2024 (12 months in production after 18 months total adoption) | -| **Scale** | Several hundred RPS, with spikes above 500 RPS | -| **Version** | v1.8.x | -| **Storage** | PostgreSQL with global replication | -| **Deployment** | Multi-region edge | - -## Why OpenFGA - -As Zuplo's customer base shifted toward larger enterprises, simple project-membership rules were no longer enough. The team evaluated: - -- Axiomatics (AuthZEN-based) -- Aserto (AuthZEN-based) -- Okta FGA -- Building a custom solution in-house - -They chose OpenFGA because it is **open source and self-hostable**, and because **PostgreSQL as a backend** let Zuplo replicate authorization data globally and run checks at the edge — exactly the topology API management requires. - -## Architecture - -- **One authorization model** governs the entire product, single-tenant. -- The same model handles **user access and API key access** to product features. -- OpenFGA is deployed in production across multiple data centers worldwide. -- Performance work for major upgrades uses k6-based end-to-end load tests. - -## Outcomes - -- Updating and versioning the authorization model independently of the application code accelerated development cycles. -- Roles and permissions can be tested and refined without code changes. -- Authorization is centralized across teams while keeping concerns separate. -- Caching introduced upstream replaced a homegrown cache layer Zuplo had built before OpenFGA shipped its own. - -## Outlook - -Zuplo continues to file feature requests upstream and has expressed interest in publicly sharing more details about how it implements authorization at the edge. - -## Source - -This case study is based on the public CNCF TOC adopter interview with Nate Totten, Co-founder & CTO of Zuplo, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/alternatives/overview.mdx b/docs/content/alternatives/overview.mdx deleted file mode 100644 index 3980c55e7c..0000000000 --- a/docs/content/alternatives/overview.mdx +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: OpenFGA Alternatives -description: An honest guide to OpenFGA alternatives — SpiceDB, Cerbos, Permit.io, Oso, Topaz, Permify, Ory Keto, OPA — and when each is the better fit. -sidebar_position: 1 -slug: /alternatives ---- - -# OpenFGA Alternatives - -If you're evaluating OpenFGA, you're almost certainly evaluating something else alongside it. This page is an honest hub: a short summary of each serious alternative, what makes it distinctive, and the cases where it is genuinely the better choice over OpenFGA. - -The authorization ecosystem in 2026 is crowded, and "best" depends on where the work lives — application code, infrastructure admission control, or a hosted control plane. Pick the engine whose centre of gravity matches your problem, not whichever has the loudest landing page. - -## Quick comparison - -| Alternative | Style | Hosted option | License | Best fit | -| --- | --- | --- | --- | --- | -| [SpiceDB](#spicedb) | Zanzibar / ReBAC | Authzed | Apache 2.0 | Want a Zanzibar engine with a commercial vendor behind it | -| [Cerbos](#cerbos) | PBAC (YAML policies) | Cerbos Hub | Apache 2.0 (PDP) | Stateless, attribute-driven decisions across many services | -| [Permit.io](#permit-io) | Control plane over OPA/Cedar | Hosted-first | Commercial | Want a SaaS UI on top of an OPA/Cedar engine | -| [Oso](#oso) | Polar DSL | Oso Cloud | Commercial (Cloud) | Want a managed engine with a single application-focused DSL | -| [Topaz](#topaz) | Zanzibar + Rego (hybrid) | Aserto | Apache 2.0 | Want ReBAC and Rego in one process, edge-deployed | -| [Permify](#permify) | Zanzibar / ReBAC | Permify Cloud | Apache 2.0 | Want a Zanzibar engine with a smaller, simpler surface | -| [Ory Keto](#ory-keto) | Zanzibar / ReBAC | Ory Network | Apache 2.0 + Enterprise | Already inside the Ory ecosystem (Kratos / Hydra) | -| [OPA](#opa) | General-purpose policy (Rego) | Self-hosted | Apache 2.0 (CNCF graduated) | Infrastructure / admission control, not application authorization | - -## When OpenFGA is the right pick - -Before the alternatives, the short version of where OpenFGA fits: - -- You want a **Zanzibar-style relationship engine** — typed model, tuple store, fast Check, reverse queries. -- You want it **CNCF-governed and self-hostable** on PostgreSQL, MySQL, or SQLite, with an in-memory mode for tests. -- You want **fine-grained, per-resource authorization** — document sharing, multi-tenant SaaS, AI agent / RAG access — rather than coarse role checks. -- You're comfortable owning the deployment (operator, Helm chart), or you're already using a hosted OpenFGA service such as **Auth0 FGA / Okta FGA** — managed OpenFGA from Auth0/Okta, the original creators who donated the project to the CNCF. - -If those bullets describe your problem, OpenFGA is purpose-built for it and you're in the right place. The rest of this page is for cases where the answer is genuinely something else. - -## SpiceDB - -[SpiceDB](https://authzed.com/spicedb) is the closest peer — a Zanzibar implementation with a typed schema language, multiple storage backends, and a Kubernetes operator. It's Apache 2.0 with a commercial vendor (Authzed) selling a managed and dedicated offering. - -**Pick SpiceDB over OpenFGA when:** - -- You specifically want a single commercial vendor on the support contract and roadmap, not a CNCF-governed project with multiple maintainers from different companies. -- You're already standardised on Authzed Cloud or Dedicated for hosting. -- Your team prefers SpiceDB's schema language ergonomics — both DSLs are expressive, and personal preference is a fine reason to choose. - -OpenFGA and SpiceDB will solve nearly the same problems. The decision usually comes down to governance model and which DSL the team finds easier to read. See the side-by-side: [OpenFGA vs SpiceDB](/docs/compare/spicedb). - -## Cerbos - -[Cerbos](https://www.cerbos.dev/) is a stateless policy decision point. Policies are YAML files, kept in Git, and the PDP evaluates them against attributes you pass at request time. There's no relationship database — that's a feature, not a gap. - -**Pick Cerbos over OpenFGA when:** - -- Authorization is **mostly attribute-driven**: claims from the JWT, resource metadata you already have at hand, request context. -- You don't need reverse queries (*"list every resource this user can read"*) — you only need yes/no on a known resource. -- You want decisions to scale horizontally without a shared database behind them. - -The honest framing: Cerbos and OpenFGA are answers to *different* questions. Cerbos shines on stateless ABAC across many services. OpenFGA shines when relationships change at write time and you need to query the graph. Many teams end up running both. See [OpenFGA vs Cerbos](/docs/compare/cerbos). - -## Permit.io - -[Permit.io](https://www.permit.io/) is a hosted control plane built on top of open-source engines (OPA / OPAL, Cedar). It bundles a UI, audit logs, low-code policy editing, and an MCP gateway. - -**Pick Permit.io over OpenFGA when:** - -- You want a **managed product with a UI** that non-engineers can edit policy in, not a library you embed in your stack. -- You want compliance certifications (SOC2/HIPAA/GDPR) included in the hosted product rather than something you assemble. -- You're happy delegating the engine choice to the vendor — Permit decides whether OPA or Cedar evaluates a given request. - -If self-hosting is the requirement, Permit isn't the right shape. If managed-with-a-UI is the requirement, OpenFGA isn't. See [OpenFGA vs Permit.io](/docs/compare/permit). - -## Oso - -[Oso](https://www.osohq.com/) ships **Oso Cloud** — a managed authorization service driven by the Polar DSL. The on-prem open-source library is in maintenance; the active product is the cloud service. - -**Pick Oso over OpenFGA when:** - -- You want a **single managed service** and don't want to operate an authorization engine. -- You like the Polar DSL specifically. Polar is expressive in a different way than the Zanzibar-style DSLs — closer to a logic language. -- Your scale targets match Oso Cloud's published numbers and you're comfortable with the commercial terms. - -The deployment-model trade-off is the main fork: OpenFGA is built to be self-hosted; Oso's current centre of gravity is the cloud. See [OpenFGA vs Oso](/docs/compare/oso). - -## Topaz - -[Topaz](https://www.topaz.sh/) is Aserto's open-source authorizer. Its distinctive choice is **combining a Zanzibar-style relationship store with Rego** — Topaz lets you write attribute rules in OPA's policy language alongside relationships in a directory. - -**Pick Topaz over OpenFGA when:** - -- You want both ReBAC and Rego in **one process** and don't want to wire two engines together yourself. -- You're deploying authorizers **at the edge** of each application and want the authorizer to embed cleanly there. -- You want to use existing Rego skills on policies you already maintain for OPA. - -OpenFGA covers most attribute-driven cases via [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples) without adding Rego. If you want Rego specifically, Topaz is the right tool for that. - -## Permify - -[Permify](https://permify.co/) is another open-source Zanzibar implementation with a smaller surface than OpenFGA or SpiceDB. It supports Postgres for storage and ships a self-hosted server plus a managed cloud. - -**Pick Permify over OpenFGA when:** - -- You prefer Permify's specific schema syntax or developer ergonomics after trying both. -- The smaller community and smaller feature surface is a feature, not a drawback, for your team. - -OpenFGA has CNCF governance, multiple production references documented in the [adopters file](https://github.com/openfga/community/blob/main/ADOPTERS.md), and a larger SDK matrix. For most teams that's the deciding factor; if it's not, Permify is a perfectly reasonable Zanzibar engine. - -## Ory Keto - -[Ory Keto](https://www.ory.com/keto/) is a Zanzibar implementation inside the Ory ecosystem — alongside Kratos (identity), Hydra (OAuth2), and Oathkeeper (proxy). Apache 2.0 plus an Enterprise edition and the hosted Ory Network. - -**Pick Ory Keto over OpenFGA when:** - -- You're **already running the Ory stack** (Kratos for users, Hydra for OAuth, Oathkeeper for proxying) and want one vendor across the identity-and-access surface. -- You want Ory Network as the managed option. - -If you're not already in the Ory ecosystem, OpenFGA's modeling tools, SDK matrix, and CNCF governance generally make it the easier standalone choice. See [OpenFGA vs Ory Keto](/docs/compare/keto). - -## OPA - -[Open Policy Agent](https://www.openpolicyagent.org/) (OPA) is a CNCF **graduated** general-purpose policy engine. Rego policies evaluate against any JSON input — Kubernetes admission, Terraform, service-mesh, application requests. - -**Pick OPA over OpenFGA when:** - -- You're solving **infrastructure or admission-control** problems — Kubernetes admission webhooks, Terraform plan checks, Envoy authorization filters. -- The same policy must apply across **many domains**, not just one application's resource model. -- You want a stateless engine where the data is supplied per request, not stored in a graph. - -OPA and OpenFGA solve different layers of the authorization stack — see [Policy Engines vs Relationship Engines](/docs/learn/policy-engine) for the longer version. Many teams run both: OPA at the platform layer, OpenFGA inside applications. The detailed comparison: [OpenFGA vs OPA](/docs/compare/opa). - -## How to actually choose - -Three questions cut through the marketing: - -1. **Where does the data live?** If your authorization data is mostly *relationships that change at write time* (group membership, sharing, ownership, hierarchy), pick a relationship engine — OpenFGA, SpiceDB, Permify, Keto, Topaz. If it's mostly *attributes you already have on the request*, pick a policy engine — Cerbos, OPA. -2. **Who operates it?** If you want a managed product with a UI, look at Permit.io and Oso Cloud first. If you want to self-host with open-source governance, look at OpenFGA, SpiceDB, Cerbos PDP, OPA. -3. **What ecosystem are you already in?** Already running Ory? Keto. Already running Authzed? SpiceDB. Already running Aserto? Topaz. The integration cost of a second vendor is real. - -If after that the answer is OpenFGA, the [Getting Started](/docs/getting-started) guide is twenty minutes of work. If it's something else, the linked comparison pages and the alternative's own docs will get you the rest of the way. - -## Related reading - -- [Compare overview](/docs/compare) — side-by-side detail on each engine -- [Policy Engines vs Relationship Engines](/docs/learn/policy-engine) -- [What is Fine-Grained Authorization?](/docs/learn/fine-grained-authorization) -- [Customer case studies](/docs/adopters) diff --git a/docs/content/compare/auth0-fga.mdx b/docs/content/compare/auth0-fga.mdx deleted file mode 100644 index 16ab897955..0000000000 --- a/docs/content/compare/auth0-fga.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: OpenFGA vs Auth0 FGA -description: Compare self-hosted OpenFGA with Auth0 FGA / Okta FGA, the managed OpenFGA service from the original creators. -sidebar_position: 9 -slug: /compare/auth0-fga ---- - -# OpenFGA vs Auth0 FGA - -[Auth0 FGA](https://docs.fga.dev/) is a managed authorization service built on OpenFGA. The engine, DSL, and API are the same — the difference is who operates the infrastructure, and what runs on top of it. - -## Side by side - -| | OpenFGA | Auth0 FGA | -| --- | --- | --- | -| Delivery | Self-hosted open source | Managed SaaS from Auth0/Okta | -| License | Apache 2.0 | Commercial | -| Engine & API | OpenFGA | OpenFGA | -| Availability | Self-managed | Multi-region, active-active | -| Upgrades & backups | Your responsibility | Handled by Auth0 | -| Dashboard | Local Playground | Hosted dashboard with SSO | -| Logging | You build the pipeline | Logging API with retention | -| Support | Community | 24/7 monitoring, enterprise support | - -## When to pick OpenFGA (self-hosted) - -You want the engine to run inside your own infrastructure — for data residency, air-gapped environments, or full control over database, version, and topology — and you're comfortable owning availability, backups, and upgrades. - -## When to pick Auth0 FGA / Okta FGA - -You want the same engine without operating it, or you're already on Auth0 / Okta for identity and want authorization in the same vendor relationship with enterprise support. - -## Migration shape - -Because Auth0 FGA is OpenFGA, moving in either direction is mostly an operational exercise — the model and tuples are portable, and SDK calls don't change. Most teams export the model and tuples and then cut over. - -> Auth0 FGA facts above are drawn from [docs.fga.dev](https://docs.fga.dev/openfga-vs-auth0-fga) as of May 2026. Verify current SLA, region availability, and pricing with Auth0/Okta before making a decision. diff --git a/docs/content/compare/cerbos.mdx b/docs/content/compare/cerbos.mdx index 94489024e2..a6202f081d 100644 --- a/docs/content/compare/cerbos.mdx +++ b/docs/content/compare/cerbos.mdx @@ -17,8 +17,9 @@ OpenFGA and [Cerbos](https://www.cerbos.dev) solve different shapes of the autho | Relationship storage | First-class — tuples are the source of truth | None — policies are stateless; data is passed in | | Language | OpenFGA DSL | YAML policies | | Storage | PostgreSQL, MySQL, SQLite | Stateless; policies on disk, in Git, or in Cerbos Hub | -| Reverse queries | `list-objects`, `list-users` | Not first-class (stateless) | -| Governance | CNCF Sandbox | Open source PDP, commercial Hub | +| Reverse queries | Engine-side `list-objects`, `list-users` over the tuple graph | `PlanResources` returns a query-plan AST you translate into a native DB query | +| Query-plan adapters | N/A — engine returns objects directly | Prisma, Drizzle, Mongoose, Convex, LangChain/ChromaDB (TS); SQLAlchemy (Python) | +| Governance | CNCF Incubation | Open source PDP, commercial Hub | ## When ReBAC + a tuple store is the right tool @@ -34,7 +35,11 @@ Real production deployments of this shape: - Authorization rules are mostly **attribute-based** (role, department, resource owner) and the data needed to decide is already on the request. - You want to keep authorization logic in Git as YAML and avoid running a database for permissions. -- You don't need reverse queries (*"list every document user X can read"*) — these inherently require a stored relationship graph. +- Reverse queries (*"list every document user X can read"*) can be answered by pushing a query plan into your existing database via Cerbos's [PlanResources API](https://docs.cerbos.dev/cerbos/latest/recipes/filtering-resources) and [query plan adapters](https://docs.cerbos.dev/cerbos/latest/recipes/query-plan-adapters/) (Prisma, Drizzle, Mongoose, Convex, LangChain/ChromaDB, SQLAlchemy) — a good fit when the data needed for the decision already lives in your application DB. + +## Two shapes of reverse query + +OpenFGA and Cerbos both let you answer *"which resources can this user access?"*, but the mechanics are different. OpenFGA's `ListObjects` and `ListUsers` traverse the tuple graph in the engine and return the objects directly — the relationship data is the source of truth and lives in OpenFGA's store. Cerbos's `PlanResources` returns a `CONDITIONAL` AST (or `ALWAYS_ALLOWED` / `ALWAYS_DENIED`) that your application or an adapter compiles into a `WHERE` clause against your own database. If the data needed to authorize lives in your app DB and you want a single filtered query, the Cerbos pushdown is ergonomic; if the relationships themselves are the source of truth and span hierarchies that don't map cleanly to your row model, the OpenFGA engine-side approach fits better. ## Combining both diff --git a/docs/content/compare/keto.mdx b/docs/content/compare/keto.mdx deleted file mode 100644 index 9f335a3055..0000000000 --- a/docs/content/compare/keto.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: OpenFGA vs Ory Keto -description: Compare OpenFGA and Ory Keto, two open-source Zanzibar implementations, on language, governance, and ecosystem. -sidebar_position: 6 -slug: /compare/keto ---- - -# OpenFGA vs Ory Keto - -Both OpenFGA and [Ory Keto](https://www.ory.com/keto/) implement Google's Zanzibar paper. They store relationship tuples in a relational database and answer permission checks against a typed schema. The differences are mostly in **governance, language, and ecosystem**. - -## Side by side - -| | OpenFGA | Ory Keto | -| --- | --- | --- | -| Origin | Auth0/Okta → CNCF | Ory | -| Governance | CNCF Sandbox | Vendor (Ory) | -| Language | OpenFGA DSL | Ory Permission Language (OPL), TypeScript-flavored | -| License | Apache 2.0 | Apache 2.0 (open source) plus Ory Enterprise / Ory Network | -| Storage | PostgreSQL, MySQL, SQLite | PostgreSQL, MySQL, CockroachDB | -| Hosted offering | Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Ory Network (managed) | -| Ecosystem | OpenFGA SDKs, Playground, modeling guides | Part of the Ory stack (Hydra, Kratos, Oathkeeper, Keto) | - -## When OpenFGA is the right pick - -- You want **CNCF governance** and a project not steered by a single vendor. Adopters explicitly cited this as a deciding factor over alternatives whose license could change later. -- You prefer the **OpenFGA DSL** — a narrow language tailored to relationship modeling — over OPL's TypeScript-flavored configuration. -- Your authorization story is **standalone** rather than integrated with identity (Hydra/Kratos). OpenFGA does one thing: relationship-based authorization. - -## When Ory Keto is the right pick - -- You already run the **Ory stack** (Hydra for OAuth/OIDC, Kratos for identity, Oathkeeper for proxy) and want everything from one vendor. -- You want a **vendor-supported managed offering** (Ory Network) without the operational work of self-hosting. - -## Both are valid Zanzibar implementations - -If your team's main question is *"which Zanzibar-style engine should we run?"* either project will get you there. The split usually comes down to ecosystem fit (Ory stack vs. CNCF) and modeling-language taste. - -> Comparison facts in this page were collected from [ory.com/keto](https://www.ory.com/keto/) on 2026-05-20. diff --git a/docs/content/compare/keycloak.mdx b/docs/content/compare/keycloak.mdx deleted file mode 100644 index 598609f79c..0000000000 --- a/docs/content/compare/keycloak.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: OpenFGA vs Keycloak -description: Compare OpenFGA's Zanzibar-style fine-grained authorization with Keycloak Authorization Services, the policy engine bundled with the Keycloak IdP. -sidebar_position: 9 -slug: /compare/keycloak ---- - -# OpenFGA vs Keycloak - -[Keycloak](https://www.keycloak.org/) is an open source identity and access management server: OIDC/SAML SSO, user federation, social login, and an admin console. Its **Authorization Services** module adds policy-based access control on top of that identity stack — resources, scopes, permissions, and policies (role, user, time, JS, aggregated) evaluated by the Keycloak server. - -OpenFGA solves a narrower problem: fine-grained, relationship-based authorization at scale. It does not issue tokens or manage users. The two are often used together — Keycloak as the IdP, OpenFGA as the authorization engine. - -## Side by side - -| | OpenFGA | Keycloak Authorization Services | -| --- | --- | --- | -| Primary role | Authorization engine (ReBAC) | IdP + policy engine bundled | -| Model | Zanzibar-style typed DSL, conditions (CEL) | Resources, scopes, permissions, policies (role/user/time/JS/aggregated) | -| Storage of permissions | Relationship tuples in Postgres / MySQL / SQLite | Policies + resources stored in Keycloak realm DB | -| Identity | Identity-agnostic — works with any IdP, including Keycloak | Tied to Keycloak realms and users | -| Query shape | `check`, `list-objects`, `list-users`, `expand` | Token introspection / UMA permission ticket / policy evaluation endpoint | -| Governance | CNCF Sandbox | CNCF Incubating (donated by Red Hat) | -| License | Apache 2.0 | Apache 2.0 | -| Production references | [Read AI](/docs/adopters/read-ai) (5,200 RPS, 5.3B+ tuples), [Agicap](/docs/adopters/agicap), [Grafana Labs](/docs/adopters/grafana), [Docker](/docs/adopters/docker) | Widely deployed as IdP; Authorization Services usage less commonly profiled | - -## Where OpenFGA tends to win - -- **Fine-grained relationships at scale.** OpenFGA is purpose-built for Zanzibar-style ReBAC: hierarchical objects, parent-child inheritance, user-to-user-group-to-resource paths, and `list-objects` queries that return every resource a user can access. Keycloak Authorization Services models resources and scopes with policies but is not designed around relationship graphs over millions of objects. -- **Identity independence.** OpenFGA does not care who issued the token. You can pair it with Keycloak, Auth0, Okta, Cognito, or any custom IdP. Keycloak Authorization Services is bound to Keycloak realms. -- **Versioned model as code.** The OpenFGA DSL plus the [Playground](https://play.fga.dev) and Terraform provider give you a reviewable, versioned authorization model. Keycloak policies live in the realm and are typically managed through the admin console or the admin REST API. -- **Horizontal scale and dedicated storage.** OpenFGA scales the authorization tier independently of identity, on its own database. Keycloak Authorization Services scales with the Keycloak server itself. - -## Where Keycloak tends to win - -- **Single deployment for IdP + authorization.** If you need an open source IdP and want coarse-to-medium-grained authorization without a second service, Keycloak handles both in one server. -- **UMA 2.0 and permission tickets.** Keycloak implements User-Managed Access flows out of the box. OpenFGA does not — you would build the UMA layer separately if you need it. -- **Operator familiarity.** Many teams already run Keycloak. Adding policies there is incremental rather than introducing a new service. - -## Using them together - -The most common pairing: Keycloak as the IdP issuing OIDC tokens, OpenFGA as the authorization engine. Your application validates the Keycloak token, extracts the subject and any relevant claims, then asks OpenFGA `check`, `list-objects`, or `list-users` for permission decisions. Keycloak roles and group memberships can be mirrored into OpenFGA tuples, or passed as [contextual tuples](/docs/modeling/token-claims-contextual-tuples) at query time. - -Community resources on this pairing: - -- [keycloak-openfga-event-publisher](https://github.com/embesozzi/keycloak-openfga-event-publisher) — a Keycloak SPI that listens to admin events (user/group/role changes) and syncs them into OpenFGA tuples automatically. -- [keycloak-openfga-workshop](https://github.com/embesozzi/keycloak-openfga-workshop) — hands-on workshop wiring Keycloak as the IdP to OpenFGA as the authorization engine end-to-end. -- [Keycloak integration with OpenFGA based on Zanzibar for fine-grained authorization at scale](https://embesozzi.medium.com/keycloak-integration-with-openfga-based-on-zanzibar-for-fine-grained-authorization-at-scale-d3376de00f9a) — walkthrough of the same pattern with architecture diagrams. - -## When to pick OpenFGA - -You need fine-grained, relationship-based authorization (documents, folders, tenants, agents, RAG chunks) decoupled from your identity provider, with a typed model you can version and a query API designed for `check` and `list-objects` at scale. - -## When to pick Keycloak Authorization Services - -You're already on Keycloak, your authorization needs are role- and scope-shaped rather than relationship-shaped, and you'd rather extend the existing IdP than introduce a separate authorization service. - -> Comparison facts about Keycloak Authorization Services above are drawn from the [Keycloak Authorization Services Guide](https://www.keycloak.org/docs/latest/authorization_services/) as of May 2026. Verify current scope and capabilities with the Keycloak documentation before making a decision. diff --git a/docs/content/compare/opa.mdx b/docs/content/compare/opa.mdx deleted file mode 100644 index bdcf91a4d6..0000000000 --- a/docs/content/compare/opa.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: OpenFGA vs Open Policy Agent (OPA) -description: Compare OpenFGA's Zanzibar-style relationship engine with Open Policy Agent (OPA), the CNCF general-purpose policy engine using Rego. -sidebar_position: 7 -slug: /compare/opa ---- - -# OpenFGA vs Open Policy Agent (OPA) - -OpenFGA and [Open Policy Agent](https://www.openpolicyagent.org) are both CNCF projects, but they solve different problems. OPA is a **general-purpose policy engine** that evaluates [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) policies against arbitrary JSON input — it is widely used for Kubernetes admission control, infrastructure policy, and service-mesh authorization. OpenFGA is a **relationship database** that answers "can user A do X on resource B?" against a stored, typed graph of tuples. - -## Side by side - -| | OpenFGA | OPA | -| --- | --- | --- | -| Primary use | Application authorization (ReBAC) | General-purpose policy (admission, infra, app) | -| Model | Typed schema + relationship tuples | Rego policies + arbitrary JSON data | -| State | Stores tuples — relationships are the source of truth | Stateless; data is supplied per-query or bundled | -| Reverse queries | `list-objects`, `list-users` first-class | Not first-class — Rego evaluates per request | -| Governance | CNCF Sandbox | CNCF Graduated | -| License | Apache 2.0 | Apache 2.0 | - -## When OpenFGA is the right pick - -- Your authorization rules involve **relationships that change at write time** — group membership, document sharing, folder hierarchies, multi-tenant ownership — and you want a database to store and query them. -- You need **reverse queries** like *"list every document this user can read"* for UI rendering or filtered listings. These require a stored graph. -- You want a **narrow, typed DSL** for relationship modeling rather than a general logic language. - -## When OPA is the right pick - -- You need **policy across many domains** — Kubernetes admission, Terraform plans, service-mesh request authorization, CI gates — not only application authorization. -- Your decisions are mostly **attribute-based** and the input is already on the request (claims, resource metadata, labels). -- You prefer Rego's general expressiveness and an established ecosystem of policy bundles. - -## Using both together - -Pairing the two is common: OPA at the infrastructure and request-admission layer, OpenFGA inside the application for relationship-heavy permissions. OpenFGA's [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples) cover most ABAC needs inside an application; OPA covers everything outside it. - -> Comparison facts in this page were collected from [openpolicyagent.org](https://www.openpolicyagent.org) on 2026-05-20. diff --git a/docs/content/compare/oso.mdx b/docs/content/compare/oso.mdx index 816592b535..a48d555769 100644 --- a/docs/content/compare/oso.mdx +++ b/docs/content/compare/oso.mdx @@ -15,7 +15,7 @@ OpenFGA is an open-source Zanzibar-style authorization engine governed by the CN | --- | --- | --- | | Delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA) | Oso Cloud (hosted) | | License | Apache 2.0 | Commercial (Cloud) | -| Governance | CNCF Sandbox | Single vendor | +| Governance | CNCF Incubation | Single vendor | | Language | OpenFGA DSL | Polar (general logic-programming-flavored DSL) | | Model styles | ReBAC first; RBAC and ABAC via relations and conditions | RBAC / ReBAC / ABAC / "AnyBAC" via Polar | | Data store | PostgreSQL, MySQL, SQLite (you run it) | Vendor-managed | diff --git a/docs/content/compare/overview.mdx b/docs/content/compare/overview.mdx deleted file mode 100644 index 02a2d18a50..0000000000 --- a/docs/content/compare/overview.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: OpenFGA vs Alternatives -description: Compare OpenFGA with SpiceDB, Cerbos, Permit.io, Oso, Ory Keto, and OPA across model, storage, deployment, language, and governance. -sidebar_position: 1 -slug: /compare ---- - -# OpenFGA vs alternatives - -These pages compare OpenFGA to other authorization systems based on each project's publicly documented features as of May 2026. They are written from OpenFGA's perspective but stick to verifiable facts — model, storage, deployment, language, governance — rather than benchmark numbers we cannot reproduce. - -| Compared system | Style | Hosted? | License | -| --- | --- | --- | --- | -| [SpiceDB](/docs/compare/spicedb) | Zanzibar / ReBAC | Authzed (managed) and self-hosted | Apache 2.0 | -| [Cerbos](/docs/compare/cerbos) | PBAC (RBAC/ABAC/ReBAC via YAML) | Cerbos Hub and self-hosted | Apache 2.0 (PDP) | -| [Permit.io](/docs/compare/permit) | Control plane over OPA/Cedar | Hosted-first | Commercial | -| [Oso](/docs/compare/oso) | Polar DSL | Oso Cloud (managed) | Commercial (Cloud) | -| [Ory Keto](/docs/compare/keto) | Zanzibar / ReBAC | Ory Network and self-hosted | Apache 2.0 + Enterprise | -| [OPA](/docs/compare/opa) | General-purpose policy (Rego) | Self-hosted | Apache 2.0 (CNCF graduated) | -| [WorkOS FGA](/docs/compare/workos) | Hosted FGA bundled with WorkOS identity | Hosted only | Commercial | -| [Keycloak](/docs/compare/keycloak) | IdP with bundled Authorization Services (policy-based) | Self-hosted | Apache 2.0 | -| [Auth0 FGA](/docs/compare/auth0-fga) | Managed OpenFGA from Auth0/Okta | Hosted only | Commercial (managed service) | - -## Why OpenFGA - -OpenFGA's specific niche is: - -- **Zanzibar-style ReBAC** with a typed DSL and a relationship tuple store — designed for fine-grained authorization, not generic policy. -- **CNCF Sandbox project** with public governance, an ADOPTERS file, and a Security Insights manifest. -- **Self-hostable** with PostgreSQL, MySQL, and SQLite backends and an in-memory mode for tests. A managed offering is also available as **Auth0 FGA / Okta FGA** from Auth0/Okta, the team that originally built OpenFGA and donated it to the CNCF. -- **Production references** at [Read AI](/docs/adopters/read-ai), [Agicap](/docs/adopters/agicap), [Grafana Labs](/docs/adopters/grafana), [Docker](/docs/adopters/docker), and [Zuplo](/docs/adopters/zuplo). - -If you want a hosted policy-as-a-service, several entries above will be a better fit — or you can use **Auth0 FGA / Okta FGA**, a managed OpenFGA service from the original creators. If you need a self-hostable Zanzibar-style engine with first-class modeling tools, OpenFGA is built for that case. diff --git a/docs/content/compare/permit.mdx b/docs/content/compare/permit.mdx index acae592e96..10c0312df5 100644 --- a/docs/content/compare/permit.mdx +++ b/docs/content/compare/permit.mdx @@ -7,7 +7,7 @@ slug: /compare/permit # OpenFGA vs Permit.io -OpenFGA is a self-hostable, CNCF-governed Zanzibar-style engine. [Permit.io](https://www.permit.io) is a commercial **hosted authorization control plane** that wraps existing policy engines (historically OPA / Cedar) with a UI, audit, MCP gateway, and SDKs. +OpenFGA is a self-hostable, CNCF-governed Zanzibar-style engine with official SDKs, a Playground UI, an [FGA CLI](https://github.com/openfga/cli), a [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest), a Helm chart, and a managed option via [Auth0 FGA](/docs/compare/auth0-fga). [Permit.io](https://www.permit.io) is a commercial **hosted authorization control plane** that wraps existing policy engines — historically OPA and Cedar, with OpenFGA as one of several supported engines — behind a unified UI, audit pipeline, MCP gateway, and SDKs. The two are not the same shape: OpenFGA is an engine plus tooling; Permit.io is a multi-engine control plane. ## Side by side @@ -15,10 +15,12 @@ OpenFGA is a self-hostable, CNCF-governed Zanzibar-style engine. [Permit.io](htt | --- | --- | --- | | Primary delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Hosted control plane (commercial) | | License | Apache 2.0 | Commercial | -| Governance | CNCF Sandbox | Single vendor | +| Governance | CNCF Incubation | Single vendor | | Model | Zanzibar-style ReBAC, conditions for ABAC | RBAC, ABAC, ReBAC over OPA-style engines | | Data residency | Wherever you run OpenFGA | Vendor-managed unless self-hosted PDP is purchased | -| First-party UI | OpenFGA Playground (web) | Full no-code/low-code admin UI | +| First-party UI | OpenFGA Playground (web); hosted dashboard via Auth0 FGA / Okta FGA | Full no-code/low-code admin UI | +| Official SDKs | Go, Java, .NET, Python, JavaScript/TypeScript | Multiple languages | +| IaC & CLI | FGA CLI, Terraform provider, Helm chart | Terraform provider, CLI | ## When OpenFGA is the right pick @@ -26,6 +28,7 @@ OpenFGA is a self-hostable, CNCF-governed Zanzibar-style engine. [Permit.io](htt - You need **regulatory data residency** — e.g. fintech (Agicap), healthcare, or air-gapped environments — that a hosted SaaS makes harder. - You want a **single, typed model file** versioned alongside code, not a vendor admin UI as the source of truth. The [FGA CLI](https://github.com/openfga/cli) and [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest) make the model-as-code workflow first-class — write the DSL, lint and test it locally, diff it in PRs, and deploy it like any other code artifact. + ## When Permit.io may be the right pick - You want a turn-key hosted authorization product with built-in admin UI, audit, and SOC 2 / HIPAA / GDPR / CCPA compliance attestations from the vendor. diff --git a/docs/content/compare/spicedb.mdx b/docs/content/compare/spicedb.mdx deleted file mode 100644 index 792722768e..0000000000 --- a/docs/content/compare/spicedb.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: OpenFGA vs SpiceDB -description: Compare OpenFGA and SpiceDB on Zanzibar foundations, schema language, storage backends, deployment, and governance. -sidebar_position: 2 -slug: /compare/spicedb ---- - -# OpenFGA vs SpiceDB - -OpenFGA and [SpiceDB](https://authzed.com/spicedb) are the two most prominent open-source implementations of Google's [Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/) paper. Both store relationship tuples and answer permission checks against a typed schema. The differences are in governance, modeling ergonomics, and storage strategy. - -## Side by side - -| | OpenFGA | SpiceDB | -| --- | --- | --- | -| Origin | Auth0/Okta, donated to CNCF | Authzed | -| Governance | CNCF Sandbox | Open core, Authzed-led | -| License | Apache 2.0 | Apache 2.0 | -| Model language | OpenFGA DSL (concise types + relations) | SpiceDB Schema | -| Storage | PostgreSQL, MySQL, SQLite, in-memory | PostgreSQL, MySQL, CockroachDB, Spanner, in-memory | -| Hosted offering | Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta, the original creators) | Authzed (managed) | -| Conditions / ABAC | Conditional tuples (CEL) | Caveats (CEL) | - -## Where OpenFGA tends to win - -- **CNCF governance.** Adopters such as [Grafana Labs](/docs/adopters/grafana) cited CNCF stewardship and an explicit governance policy as a reason to prefer OpenFGA over a vendor-led project that could re-license. -- **Modeling ergonomics.** The OpenFGA DSL aims to be the smallest readable expression of a relationship model; the [Playground](https://play.fga.dev) visualizes graphs and runs assertions in the browser. -- **SQLite + embedded.** [Grafana maintains the SQLite adapter](/docs/adopters/grafana) upstream so OpenFGA ships inside embedded Grafana OSS — a deployment shape SpiceDB does not target. - -## Where SpiceDB tends to win - -- **Wider storage matrix.** Out-of-the-box CockroachDB and Spanner adapters matter for teams that already standardize on those engines. -- **Authzed managed service.** A first-party SaaS removes operational work for teams that prefer to outsource the database tier. - -## When to pick OpenFGA - -You want a CNCF-governed, self-hosted Zanzibar-style engine, ideally on Postgres or MySQL, with a concise DSL and visual playground, and you value production references such as Read AI (5,200 RPS, 5.3B+ tuples), Agicap, Grafana, Docker, and Zuplo. See [the customer overview](/docs/adopters). - -## When to pick SpiceDB - -You need CockroachDB or Spanner storage, or you want a managed Zanzibar-style service from the same vendor that ships the engine. - -> Comparison facts in this page are drawn from each project's public site as of May 2026. License or feature claims may change — always confirm against the upstream project before making a decision. diff --git a/docs/content/compare/workos.mdx b/docs/content/compare/workos.mdx deleted file mode 100644 index e385f689c2..0000000000 --- a/docs/content/compare/workos.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: OpenFGA vs WorkOS FGA -description: Compare OpenFGA's self-hostable Zanzibar-style engine with WorkOS FGA, a hosted fine-grained authorization product bundled with WorkOS identity. -sidebar_position: 8 -slug: /compare/workos ---- - -# OpenFGA vs WorkOS FGA - -OpenFGA is a self-hostable, CNCF-governed Zanzibar-style authorization engine. [WorkOS FGA](https://workos.com/docs/fga) is a **hosted** fine-grained authorization product from WorkOS, designed to layer on top of WorkOS RBAC, SSO, Directory Sync, and AuthKit. It is sold and operated by WorkOS as part of its identity platform. - -## Side by side - -| | OpenFGA | WorkOS FGA | -| --- | --- | --- | -| Primary delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Hosted as part of WorkOS | -| License | Apache 2.0 | Commercial (vendor-hosted) | -| Governance | CNCF Sandbox | Single vendor (WorkOS) | -| Model | Zanzibar-style ReBAC, typed DSL, conditions (CEL) | Subjects, resources, roles, permissions, assignments — no schema DSL exposed | -| Storage | Postgres / MySQL / SQLite / in-memory (your infra) | Vendor-managed | -| Self-hosting | Yes | Not documented | -| Identity bundling | Identity-agnostic — works with any IdP | Tightly integrated with WorkOS RBAC, SSO, Directory Sync, AuthKit | - -## Where OpenFGA tends to win - -- **Self-hosting and data residency.** OpenFGA runs in your environment on your database, which matters for regulated workloads (fintech, healthcare) and air-gapped deployments. WorkOS FGA is documented as hosted. -- **Open governance.** OpenFGA is a CNCF Sandbox project with a public governance policy and ADOPTERS file. WorkOS FGA is a single-vendor commercial product. -- **Typed DSL and tooling.** A versioned authorization model in the OpenFGA DSL, the [Playground](https://play.fga.dev), and a Terraform provider give you the model as code. WorkOS FGA's public docs describe roles, permissions, and assignments rather than a typed schema language. -- **Production references at scale.** [Read AI](/docs/adopters/read-ai) (5,200 RPS, 5.3B+ tuples, 20 ms p99), [Headspace](/docs/adopters/headspace), [Agicap](/docs/adopters/agicap), [Grafana Labs](/docs/adopters/grafana), and [Docker](/docs/adopters/docker) all run OpenFGA in production. - -## Where WorkOS FGA tends to win - -- **Bundled with WorkOS identity.** If you are already on WorkOS for SSO, Directory Sync, or AuthKit, FGA plugs into the same dashboard and SDK surface — organization memberships are first-class subjects. The marketing page advertises **sub-50 ms p95 access checks** and incremental adoption alongside existing WorkOS RBAC roles. -- **No infrastructure to operate.** No database to provision, no engine to upgrade — WorkOS runs the service. - -## When to pick OpenFGA - -You want a self-hostable, CNCF-governed, Zanzibar-style engine with a typed DSL, a versionable model, and the option of either operating it yourself or using **Auth0 FGA / Okta FGA** — managed OpenFGA from Auth0/Okta, the original creators who donated the project to the CNCF. You value identity independence and have hard requirements on data residency, open governance, or compliance posture you control. - -## When to pick WorkOS FGA - -You're already on the WorkOS platform, you want fine-grained authorization bundled into the same vendor relationship as your identity stack, and you prefer a hosted offering over running an authorization service yourself. - -> Comparison facts about WorkOS FGA above are drawn from [workos.com/docs/fga](https://workos.com/docs/fga) as of May 2026. Verify current scope, pricing, and deployment options with WorkOS before making a decision. diff --git a/docs/content/industries/applicant-tracking-system.mdx b/docs/content/industries/applicant-tracking-system.mdx new file mode 100644 index 0000000000..25592a185f --- /dev/null +++ b/docs/content/industries/applicant-tracking-system.mdx @@ -0,0 +1,59 @@ +--- +title: Fine-Grained Authorization for ATS & Recruiting with FGA +description: Model jobs, candidates, applications, interviews, scorecards, and offer workflows in FGA for Greenhouse-style and Lever-style applicant tracking systems. +sidebar_position: 8 +slug: /industries/applicant-tracking-system +--- + +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# ATS Authorization with + +Applicant tracking systems — Greenhouse, Lever, Workday Recruiting, Ashby — handle data that is sensitive both ways: candidates expect their applications to stay private, and hiring teams need scoped access to only the roles they are working on. Recruiters coordinate across many jobs; hiring managers see only their own openings; interviewers see only the candidates they are interviewing; offer approval is reserved for admins. + +The full sample lives in [openfga/sample-stores/stores/applicant-tracking-system](https://github.com/openfga/sample-stores/tree/main/stores/applicant-tracking-system). + +## Core resources and relations + +- **organization** — the tenant (employer). Roles: `admin`, `recruiter`, `hiring_manager`. +- **department** and **office** — group jobs by team and location; department heads see scorecards for their org. +- **job** — has per-position `recruiter` and `hiring_manager` relations distinct from the org-wide roles. +- **candidate** — visible to recruiters and admins directly; hiring managers reach a candidate only through an `application` to one of their jobs. +- **application** — links a candidate to a job; inherits visibility from the parent job. +- **interview** — the `organizer` schedules; an `interviewer` sees only the interviews assigned to them. +- **scorecard** — visible to the authoring interviewer, the hiring manager, the department head, and admins. +- **offer** — anyone on the hiring team can draft and edit, but only an admin can approve. + +## What the model gets right + +**Per-job hiring teams, not org-wide visibility.** Recruiter and hiring manager are relations on each `job`, not just org roles. A hiring manager working on the *Senior Backend Engineer* req does not see candidates from the *Sales Director* req — even though both are "hiring managers" in the org. + +**Hiring manager access flows through applications.** A hiring manager doesn't have a direct relation to the candidate; they reach the candidate through an application to a job they own. When the application closes or moves jobs, visibility tracks the relationship automatically. + +**Interviewer scoping.** Interviewers see only the interviews they're assigned to and the scorecards they author. They don't see the rest of the candidate's pipeline, other interviewers' notes, or the offer details — a common compliance requirement so that interview feedback stays independent. + +**Offer approval separated from offer authorship.** Recruiters and hiring managers draft offers; only admins approve. Modeling `can_edit` and `can_approve` as distinct relations on `offer` keeps the four-eyes control without a separate workflow service. + +## Where this maps to features + +| ATS requirement | feature | +| --- | --- | +| Per-job hiring team | direct `recruiter` / `hiring_manager` relations on `job` | +| Hiring manager → candidate via application | userset traversal through `application` parent | +| Interviewer sees only assigned interviews | direct `interviewer` relation on `interview` | +| Scorecard visibility to dept head | role at `department`, inherited by scorecards | +| Offer draft vs. approve | distinct `can_edit` / `can_approve` relations on `offer` | +| Multi-tenant ATS SaaS | tenant-scoped types, see [multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) | +| Reassign a recruiter's reqs | bulk tuple writes against `recruiter` on `job` | + +## Common extensions + +- **Candidate self-service portal.** A `candidate#self` relation lets the candidate view their own application status without granting access to interviewer notes or scorecards. +- **Referrals.** A `referrer` relation on `application` lets the referring employee see the application status — but not the scorecards or offer terms. +- **External recruiters and agencies.** Add an `agency` type with scoped access to the jobs they're sourcing for, without giving them visibility into the rest of the pipeline. +- **EEO / sensitive demographic fields.** Gate via [conditions](/docs/modeling/conditions) or a separate `can_view_eeo` relation restricted to compliance staff, distinct from the general candidate viewer set. +- **Time-bound interviewer access.** Use [conditions](/docs/modeling/conditions) so an interviewer's access to a candidate expires after the debrief, instead of relying on a cleanup job. + +## Working sample + +Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/applicant-tracking-system](https://github.com/openfga/sample-stores/tree/main/stores/applicant-tracking-system). For the broader pattern of "role at the org, scoped relationships per record", see [Modeling Roles](/docs/best-practices/modeling-roles). diff --git a/docs/content/industries/banking.mdx b/docs/content/industries/banking.mdx index 4abaca19b5..73599e0a34 100644 --- a/docs/content/industries/banking.mdx +++ b/docs/content/industries/banking.mdx @@ -1,11 +1,13 @@ --- -title: Banking Authorization with OpenFGA -description: Model account managers, account owners, per-transaction limits, and delegation in OpenFGA for banking and fintech applications. +title: Fine-Grained Authorization for Banking & Fintech with FGA +description: Model account managers, account owners, per-transaction limits, and delegation in FGA for banking, fintech, and PCI-DSS-regulated applications. sidebar_position: 3 slug: /industries/banking --- -# Banking Authorization with OpenFGA +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# Banking Authorization with Banking and fintech authorization isn't just "who can read this record?" — it's "who can move *how much* money out of *which* account, and who approved that delegation?". A relationship-based model captures the answer without scattering the rules across application code. @@ -31,11 +33,11 @@ A typical "manager", "owner", "delegate" RBAC scheme answers *what action is all - Audit can't answer "who could have wired more than $50k from account A on date D" without joining application code with role tables. - Delegation expiry, per-transaction overrides, and dual-control approvals each become bespoke features. -OpenFGA expresses the limit *as part of the relationship* using [conditions](/docs/modeling/conditions), so the check `can_initiate_transfer` takes the transaction amount as a contextual parameter and returns a single allow/deny. + expresses the limit *as part of the relationship* using [conditions](/docs/modeling/conditions), so the check `can_initiate_transfer` takes the transaction amount as a contextual parameter and returns a single allow/deny. -## Where this maps to OpenFGA features +## Where this maps to features -| Banking requirement | OpenFGA feature | +| Banking requirement | feature | | --- | --- | | Per-actor transaction limits | [conditions](/docs/modeling/conditions) on `can_initiate_transfer` | | Owner / co-owner / delegate roles | direct relations on `account` | diff --git a/docs/content/industries/crm.mdx b/docs/content/industries/crm.mdx index 3d04d138ba..75efb6d05a 100644 --- a/docs/content/industries/crm.mdx +++ b/docs/content/industries/crm.mdx @@ -1,11 +1,13 @@ --- -title: CRM Authorization with OpenFGA -description: Model accounts, contacts, leads, opportunities, and territory-based access in OpenFGA for Salesforce-style and HubSpot-style CRM systems. +title: Fine-Grained Authorization for CRM with FGA +description: Model accounts, contacts, leads, opportunities, and territory-based access in FGA for Salesforce-style and HubSpot-style CRM platforms. sidebar_position: 6 slug: /industries/crm --- -# CRM Authorization with OpenFGA +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# CRM Authorization with CRM platforms — Salesforce, HubSpot, Pipedrive, Zoho — share an authorization shape that pure RBAC handles badly: a sales rep owns *their* accounts and deals, a manager sees *the team's* pipeline, and admins see everything. Account ownership is per-record and changes constantly as deals are reassigned. @@ -30,9 +32,9 @@ The full sample is in [openfga/sample-stores/stores/crm](https://github.com/open **Close-deal as its own permission.** "Can edit the opportunity" and "can close the deal" are different relations. Reps edit their own deals but only owners or sales managers can mark won/lost. -## Where this maps to OpenFGA features +## Where this maps to features -| CRM requirement | OpenFGA feature | +| CRM requirement | feature | | --- | --- | | Per-rep account ownership | direct `owner` relation on `account` | | Manager sees the team's pipeline | org-level `sales_manager` role | diff --git a/docs/content/industries/ecommerce.mdx b/docs/content/industries/ecommerce.mdx index b789b10ee0..20b46005e7 100644 --- a/docs/content/industries/ecommerce.mdx +++ b/docs/content/industries/ecommerce.mdx @@ -1,15 +1,17 @@ --- -title: E-commerce Authorization with OpenFGA -description: Model multi-store organizations, staff roles, products, orders, and refunds in OpenFGA for Shopify-style and BigCommerce-style platforms. +title: Fine-Grained Authorization for E-commerce with FGA +description: Model multi-store organizations, staff roles, products, orders, and refunds in FGA for Shopify-style and BigCommerce-style e-commerce platforms. sidebar_position: 4 slug: /industries/ecommerce --- -# E-commerce Authorization with OpenFGA +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# E-commerce Authorization with E-commerce platforms — Shopify-style multi-store SaaS, BigCommerce, Etsy-like marketplaces — share an authorization shape that role-only systems struggle with: a merchant has *one organization* but *many stores*, and staff often have different permissions in different stores. A category manager might be a `manager` in the apparel store and just a `staff` member in the home-goods store. -OpenFGA models the org/store split natively. The full sample lives in [openfga/sample-stores/stores/ecommerce](https://github.com/openfga/sample-stores/tree/main/stores/ecommerce). + models the org/store split natively. The full sample lives in [openfga/sample-stores/stores/ecommerce](https://github.com/openfga/sample-stores/tree/main/stores/ecommerce). ## Core resources and relations @@ -37,9 +39,9 @@ Object types: **Refund vs. delete.** Refund is a manager-level permission; delete is admin only. Two separate relations means audits can answer "who could have refunded this order" independently of "who could have deleted it". -## Where this maps to OpenFGA features +## Where this maps to features -| E-commerce requirement | OpenFGA feature | +| E-commerce requirement | feature | | --- | --- | | Multi-store organizations | [parent-child relationships](/docs/modeling/parent-child) | | Per-store roles | direct relations on `store` | @@ -57,4 +59,4 @@ Object types: ## Working sample -Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/ecommerce](https://github.com/openfga/sample-stores/tree/main/stores/ecommerce). For the broader multi-tenant question (one OpenFGA store across many merchants), see [Multi-Tenant SaaS](/docs/use-cases/multi-tenant-saas). +Schema, sample tuples, and assertions are in [openfga/sample-stores/stores/ecommerce](https://github.com/openfga/sample-stores/tree/main/stores/ecommerce). For the broader multi-tenant question (one store across many merchants), see [Multi-Tenant SaaS](/docs/use-cases/multi-tenant-saas). diff --git a/docs/content/industries/healthcare.mdx b/docs/content/industries/healthcare.mdx index 7d05689f13..70f8423048 100644 --- a/docs/content/industries/healthcare.mdx +++ b/docs/content/industries/healthcare.mdx @@ -1,11 +1,13 @@ --- -title: Healthcare Authorization with OpenFGA -description: Model patients, providers, encounters, and PHI access in OpenFGA. Care teams, facility hierarchy, and sensitive-field permissions. +title: Fine-Grained Authorization for Healthcare — HIPAA, PHI, EHR +description: Model patients, providers, encounters, and HIPAA-regulated PHI access in OpenFGA. Care teams, facility hierarchy, and sensitive-field permissions for EHR. sidebar_position: 2 slug: /industries/healthcare --- -# Healthcare Authorization with OpenFGA +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# Healthcare Authorization with Healthcare applications — EHR/EMR systems like Epic, Cerner, or Athenahealth, plus patient portals, telehealth, and clinical research tools — share a few hard authorization problems: @@ -35,11 +37,11 @@ The healthcare sample defines these object types: **Record inheritance.** Diagnoses and treatments use object-to-object relationships ([parent-child](/docs/modeling/parent-child)) so the care team inherits access through the encounter and patient. No separate ACL maintenance on every clinical note. -**Facility hierarchy.** Facility directors are scoped to facility-level data (staff lists, equipment, schedules) — they do *not* inherit `can_view_sensitive` on patients seen at their facility. That separation is hard in role-only systems and easy in OpenFGA. +**Facility hierarchy.** Facility directors are scoped to facility-level data (staff lists, equipment, schedules) — they do *not* inherit `can_view_sensitive` on patients seen at their facility. That separation is hard in role-only systems and easy in . -## Where this maps to OpenFGA features +## Where this maps to features -| Healthcare requirement | OpenFGA feature | +| Healthcare requirement | feature | | --- | --- | | Care-team membership per patient | direct tuples on `patient` | | PHI vs. non-PHI fields | two relations: `can_view_record`, `can_view_sensitive` | @@ -50,13 +52,13 @@ The healthcare sample defines these object types: ## Compliance posture -OpenFGA itself isn't a HIPAA product — compliance is a property of how you run it. What OpenFGA does give you: + itself isn't a HIPAA product — compliance is a property of how you run it. What does give you: - A single, queryable source of truth for *who can see which patient record* at any moment, which audit programs typically require. - Append-only tuple change history via the Read Changes API. - Separation between checks (read-only) and writes (admin-only) at the API level. -For the operational side — encryption at rest, logging, deployment in a HIPAA boundary — see [Running OpenFGA in Production](/docs/best-practices/running-in-production). +For the operational side — encryption at rest, logging, deployment in a HIPAA boundary — see [Running in Production](/docs/best-practices/running-in-production). ## Working sample diff --git a/docs/content/industries/human-resources.mdx b/docs/content/industries/human-resources.mdx index 4284a5a05b..159bf3b3f6 100644 --- a/docs/content/industries/human-resources.mdx +++ b/docs/content/industries/human-resources.mdx @@ -1,11 +1,13 @@ --- -title: HR & HRIS Authorization with OpenFGA -description: Model employee records, manager hierarchies, payroll, benefits, and PII isolation in OpenFGA for Workday-style HRIS and directory systems. +title: Fine-Grained Authorization for HR & HRIS with FGA +description: Model employee records, manager hierarchies, payroll, benefits, and PII isolation in FGA for Workday-style HRIS, HR, and directory systems. sidebar_position: 5 slug: /industries/human-resources --- -# HR & HRIS Authorization with OpenFGA +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# HR & HRIS Authorization with HRIS platforms — Workday, BambooHR, Rippling, plus internal directory systems — have to answer authorization questions that role-only systems struggle with: *the employee* always sees their own record, *direct managers* see their reports but not all employees, *HR* sees everything, and *PII* (SSN, date of birth, home address) is gated separately from the rest of the profile. @@ -29,9 +31,9 @@ The full sample model is in [openfga/sample-stores/stores/human-resources](https **Time-off approvals routed by relationship.** The approver is whoever the `manager` relation points at on the day of the request — no separate "approval routing" table. -## Where this maps to OpenFGA features +## Where this maps to features -| HR requirement | OpenFGA feature | +| HR requirement | feature | | --- | --- | | Employee self-service on own record | direct `self` relation on `employee` | | Manager-of-direct-reports view | `manager` relation, evaluated per-employee | diff --git a/docs/content/industries/lms.mdx b/docs/content/industries/lms.mdx index ecd56805ba..b31df046d2 100644 --- a/docs/content/industries/lms.mdx +++ b/docs/content/industries/lms.mdx @@ -1,11 +1,13 @@ --- -title: LMS Authorization with OpenFGA -description: Model courses, classes, enrollments, content authorship, and grading workflows in OpenFGA for Canvas-style and Moodle-style learning platforms. +title: Fine-Grained Authorization for LMS & EdTech with FGA +description: Model courses, classes, enrollments, content authorship, and grading workflows in FGA for Canvas-style and Moodle-style learning management systems. sidebar_position: 7 slug: /industries/lms --- -# LMS Authorization with OpenFGA +import { ProductName, ProductNameFormat } from '@components/Docs'; + +# LMS Authorization with Learning management systems — Canvas, Moodle, Blackboard, plus internal corporate training platforms — combine curriculum hierarchy with per-user enrollment. A student enrolled in *one* class section sees *that* class's materials but not the rest of the course catalog; an instructor authoring a quiz can edit it even when their general role is "instructor, not owner". @@ -30,9 +32,9 @@ The full sample lives in [openfga/sample-stores/stores/lms](https://github.com/o **Publish gated to owners.** A course can be edited by enrolled instructors but only published by the `owner` or an admin. This separates "draft work" from "goes live to students", which curriculum review processes depend on. -## Where this maps to OpenFGA features +## Where this maps to features -| LMS requirement | OpenFGA feature | +| LMS requirement | feature | | --- | --- | | Per-class enrollment | direct relations on `class`, evaluated per-section | | Course → class hierarchy | [parent-child relationships](/docs/modeling/parent-child) | diff --git a/docs/content/industries/overview.mdx b/docs/content/industries/overview.mdx index 1798c32253..bd11739e2a 100644 --- a/docs/content/industries/overview.mdx +++ b/docs/content/industries/overview.mdx @@ -1,13 +1,15 @@ --- -title: OpenFGA by Industry -description: How healthcare, banking, e-commerce, HR, CRM, and LMS teams model fine-grained authorization with OpenFGA. +title: Fine-Grained Authorization by Industry +description: How healthcare, banking, e-commerce, HR, CRM, and LMS teams model fine-grained authorization with FGA. sidebar_position: 1 slug: /industries --- -# OpenFGA by Industry +import { ProductName, ProductNameFormat } from '@components/Docs'; -OpenFGA's [modeling language](/docs/configuration-language) is general-purpose, but the *shape* of an authorization model differs by industry. Healthcare worries about who can see PHI on a given encounter; banking worries about per-transaction limits and delegation; e-commerce worries about which staff member can refund an order in which store. The pages below walk through how each domain is typically modeled, with links to working samples in [openfga/sample-stores](https://github.com/openfga/sample-stores#industry-examples). +# by Industry + +'s [modeling language](/docs/configuration-language) is general-purpose, but the *shape* of an authorization model differs by industry. Healthcare worries about who can see PHI on a given encounter; banking worries about per-transaction limits and delegation; e-commerce worries about which staff member can refund an order in which store. The pages below walk through how each domain is typically modeled, with links to working samples in [openfga/sample-stores](https://github.com/openfga/sample-stores#industry-examples). ## Industries @@ -17,9 +19,10 @@ OpenFGA's [modeling language](/docs/configuration-language) is general-purpose, - **[Human Resources](/docs/industries/human-resources)** — employee records, manager hierarchies, and per-field sensitivity (compensation, performance reviews) gated by HRBP role and reporting line. - **[CRM](/docs/industries/crm)** — accounts, opportunities, and territories with owner / team / read-only-collaborator relations and per-field visibility for pipeline and forecast data. - **[Learning Management](/docs/industries/lms)** — courses, cohorts, and enrollments with instructor / TA / learner roles and assignment-level grading permissions. +- **[Applicant Tracking](/docs/industries/applicant-tracking-system)** — jobs, candidates, applications, interviews, and offer approvals with per-job hiring teams and interviewer scoping. ## Why industry-specific models matter -The OpenFGA team has published [23+ sample stores](https://github.com/openfga/sample-stores#industry-examples) covering accounting, ads, applicant tracking, calendars, call centers, chat, CRM, developer portals, expenses, file storage, hospitality, HR, IoT, issue tracking, knowledge bases, KMS, LMS, manufacturing, payments, and real estate. The pages above are the industries where OpenFGA adopters most often ask "is this how others do it?" — and the answer is meaningful enough to be worth writing down. +The team has published [23+ sample stores](https://github.com/openfga/sample-stores#industry-examples) covering accounting, ads, applicant tracking, calendars, call centers, chat, CRM, developer portals, expenses, file storage, hospitality, HR, issue tracking, knowledge bases, KMS, LMS, manufacturing, payments, and real estate. The pages above are the industries where adopters most often ask "is this how others do it?" — and the answer is meaningful enough to be worth writing down. If your industry isn't here, the pattern pages under [Use Cases](/docs/use-cases) (multi-tenant SaaS, microservices, AI agents, RAG, MCP) are domain-neutral and apply across verticals. diff --git a/docs/content/intro.mdx b/docs/content/intro.mdx index ca4b7f1f98..4ea7e9a8d9 100644 --- a/docs/content/intro.mdx +++ b/docs/content/intro.mdx @@ -64,12 +64,6 @@ Inspired by [Google’s Zanzibar](https://zanzibar.academy), Google’s internal link: './modeling/getting-started', id: './modeling/getting-started', }, - { - title: 'Adopters', - description: 'Production case studies from Agicap, Docker, Grafana Labs, Headspace, OpenLane, Read AI, Vitrolife, and Zuplo.', - link: './customers', - id: './customers', - }, { title: 'Use Cases', description: 'Patterns for AI agents, RAG, MCP servers, multi-tenant SaaS, and microservices.', @@ -88,11 +82,5 @@ Inspired by [Google’s Zanzibar](https://zanzibar.academy), Google’s internal link: './learn', id: './learn', }, - { - title: 'Compare', - description: 'OpenFGA versus SpiceDB, Cerbos, Permit.io, Oso, Ory Keto, OPA, and other engines.', - link: './compare', - id: './compare', - } ]} /> diff --git a/docs/content/learn/abac-vs-rebac.mdx b/docs/content/learn/abac-vs-rebac.mdx index aa5156d723..02337f4c2b 100644 --- a/docs/content/learn/abac-vs-rebac.mdx +++ b/docs/content/learn/abac-vs-rebac.mdx @@ -5,6 +5,8 @@ sidebar_position: 5 slug: /learn/abac-vs-rebac --- +import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; + # ABAC vs. ReBAC **Attribute-Based Access Control (ABAC)** decides based on attributes of the principal, resource, and request — *"role = engineer AND region = EU"*. **Relationship-Based Access Control (ReBAC)** decides based on relationships in a stored graph — *"user is editor of doc, which is in folder shared with team"*. @@ -15,7 +17,7 @@ slug: /learn/abac-vs-rebac | Ergonomics for hierarchy | Needs explicit attribute lookups | Native | | Ergonomics for attribute checks | Native | Needs conditions/contextual data | | Reverse queries | Hard — must enumerate resources | First-class | -| Common engines | OPA (Rego), Cedar, AWS IAM | OpenFGA, SpiceDB, Ory Keto | +| Common engines | OPA (Rego), Cedar, AWS IAM | , SpiceDB, Ory Keto | ## When ABAC fits @@ -29,16 +31,36 @@ slug: /learn/abac-vs-rebac - You need **reverse queries** for UI rendering or filtered listings. - Permissions need to traverse multi-hop graphs (team → project → folder → document). -## OpenFGA does both +## does both -OpenFGA is a ReBAC engine, but it covers ABAC needs through [conditions](/docs/modeling/conditions) (CEL expressions evaluated at check time) and [contextual tuples](/docs/interacting/contextual-tuples) (request-time data passed into the check). For most applications that's enough — you don't need a second engine. Agicap, for example, runs [conditional ReBAC](/docs/adopters/agicap) for attribute-driven rules inside a Zanzibar model. + is a ReBAC engine, but it covers ABAC needs through [conditions](/docs/modeling/conditions) (CEL expressions evaluated at check time) and [contextual tuples](/docs/interacting/contextual-tuples) (request-time data passed into the check). For most applications that's enough — you don't need a second engine. ## When you might want both -If you also need policy outside the application — Kubernetes admission, Terraform validation, service-mesh request rules — pair OpenFGA with a [policy engine](/docs/learn/policy-engine) like OPA. OpenFGA inside the app, OPA at the infrastructure layer. +If you also need policy outside the application — Kubernetes admission, Terraform validation, service-mesh request rules — pair with a [policy engine](/docs/learn/policy-engine) like OPA. inside the app, OPA at the infrastructure layer. ## Related reading -- [ReBAC overview](/docs/learn/rebac) -- [RBAC vs. ReBAC](/docs/learn/rbac-vs-rebac) -- [Conditions](/docs/modeling/conditions) + diff --git a/docs/content/learn/fine-grained-authorization.mdx b/docs/content/learn/fine-grained-authorization.mdx index b40e0d349f..cffabe0795 100644 --- a/docs/content/learn/fine-grained-authorization.mdx +++ b/docs/content/learn/fine-grained-authorization.mdx @@ -5,6 +5,8 @@ sidebar_position: 6 slug: /learn/fine-grained-authorization --- +import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; + # What is Fine-Grained Authorization? **Fine-grained authorization (FGA)** means deciding access at the level of the individual resource and action, rather than at the role or coarse-scope level. *"Alice can edit document-42"* is fine-grained; *"Alice is an editor"* is not. @@ -18,7 +20,7 @@ slug: /learn/fine-grained-authorization Coarse-grained models can simulate these with enough effort, but the authorization layer ends up duplicating a graph database in roles tables. Fine-grained engines store the graph directly. -## How OpenFGA implements FGA +## How implements FGA - A typed [model](/docs/configuration-language) defines resource types and the relations between them. - [Tuples](/docs/concepts) record specific relationships between specific principals and specific resources. @@ -33,6 +35,20 @@ Coarse-grained models can simulate these with enough effort, but the authorizati ## Related reading -- [Authorization Concepts](/docs/authorization-concepts) -- [ReBAC overview](/docs/learn/rebac) -- [Customer case studies](/docs/adopters) + diff --git a/docs/content/learn/overview.mdx b/docs/content/learn/overview.mdx index c4a5945384..139a5e356a 100644 --- a/docs/content/learn/overview.mdx +++ b/docs/content/learn/overview.mdx @@ -1,17 +1,19 @@ --- title: Learn Authorization -description: Authorization concepts explained — Zanzibar, ReBAC, RBAC, ABAC, fine-grained authorization, and policy engines — with OpenFGA examples. +description: Authorization concepts explained — Zanzibar, ReBAC, RBAC, ABAC, fine-grained authorization, and policy engines — with FGA examples. sidebar_position: 1 slug: /learn --- +import { ProductName, ProductNameFormat } from '@components/Docs'; + # Learn Authorization -A short reference for the concepts behind OpenFGA. Each page is a focused explainer with an OpenFGA example so you can map the theory to the model language. +A short reference for the concepts behind . Each page is a focused explainer with an example so you can map the theory to the model language. - **[Google Zanzibar](/docs/learn/zanzibar)** — the paper that started the ReBAC wave. - **[ReBAC: Relationship-Based Access Control](/docs/learn/rebac)** — what it is, when to use it. - **[RBAC vs. ReBAC](/docs/learn/rbac-vs-rebac)** — when roles run out. - **[ABAC vs. ReBAC](/docs/learn/abac-vs-rebac)** — attributes versus relationships. - **[Fine-Grained Authorization](/docs/learn/fine-grained-authorization)** — what "fine-grained" actually buys you. -- **[Policy Engines vs. Relationship Engines](/docs/learn/policy-engine)** — Rego, Cedar, OPL, and where OpenFGA fits. +- **[Policy Engines vs. Relationship Engines](/docs/learn/policy-engine)** — Rego, Cedar, OPL, and where fits. diff --git a/docs/content/learn/policy-engine.mdx b/docs/content/learn/policy-engine.mdx index b705efbc8f..96a9c58649 100644 --- a/docs/content/learn/policy-engine.mdx +++ b/docs/content/learn/policy-engine.mdx @@ -5,12 +5,14 @@ sidebar_position: 7 slug: /learn/policy-engine --- +import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; + # Policy Engines vs. Relationship Engines The authorization toolbox has two broad shapes: - **Policy engines** — OPA (Rego), Cedar, Ory's OPL, Oso's Polar — evaluate policies expressed in a DSL against input data passed on each request. The engine is **stateless**; you supply the data. -- **Relationship engines** — OpenFGA, SpiceDB, Ory Keto, all in the [Zanzibar](/docs/learn/zanzibar) tradition — store relationship tuples in a database and answer queries against the stored graph. The engine **is the database**. +- **Relationship engines** — , SpiceDB, Ory Keto, all in the [Zanzibar](/docs/learn/zanzibar) tradition — store relationship tuples in a database and answer queries against the stored graph. The engine **is the database**. ## When a policy engine fits @@ -28,10 +30,18 @@ OPA's [graduated CNCF status](https://www.cncf.io/projects/open-policy-agent-opa ## You can use both -Pairing them is common: a relationship engine for application authorization, a policy engine at the infrastructure or admission layer. Inside the application, OpenFGA covers most attribute-driven rules with [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples), so a second engine isn't always needed. +Pairing them is common: a relationship engine for application authorization, a policy engine at the infrastructure or admission layer. Inside the application, covers most attribute-driven rules with [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples), so a second engine isn't always needed. ## Related reading -- [OpenFGA vs. OPA](/docs/compare/opa) -- [OpenFGA vs. Cerbos](/docs/compare/cerbos) -- [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac) + diff --git a/docs/content/learn/rbac-vs-rebac.mdx b/docs/content/learn/rbac-vs-rebac.mdx index 5e042cfcbb..5f25416776 100644 --- a/docs/content/learn/rbac-vs-rebac.mdx +++ b/docs/content/learn/rbac-vs-rebac.mdx @@ -5,6 +5,8 @@ sidebar_position: 4 slug: /learn/rbac-vs-rebac --- +import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; + # RBAC vs. ReBAC **Role-Based Access Control (RBAC)** assigns named roles to users; the role implies a set of permissions. **Relationship-Based Access Control (ReBAC)** models permissions as relationships between specific users and specific resources. @@ -28,12 +30,32 @@ Sharing breaks RBAC for the same reason: "viewer of doc-42" is not a useful role - "User X is a member of team Y, which owns project Z, which contains document D" — four tuples, one check, no role explosion. - Cross-tenant sharing — a user in one org can be an `editor` of a single document in another org without granting them anything else. -## Doing both in OpenFGA +## Doing both in -OpenFGA is a [ReBAC](/docs/learn/rebac) engine that **also models RBAC cleanly**: a role is a relation on a tenant, and the model lets the same user be a different role in different tenants without any duplication. The [Modeling Roles](/docs/best-practices/modeling-roles) guide walks through the pattern. + is a [ReBAC](/docs/learn/rebac) engine that **also models RBAC cleanly**: a role is a relation on a tenant, and the model lets the same user be a different role in different tenants without any duplication. The [Modeling Roles](/docs/best-practices/modeling-roles) guide walks through the pattern. ## Related reading -- [ReBAC overview](/docs/learn/rebac) -- [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac) -- [Modeling roles](/docs/best-practices/modeling-roles) + diff --git a/docs/content/learn/rebac.mdx b/docs/content/learn/rebac.mdx index 1bf971404b..f0630c47d2 100644 --- a/docs/content/learn/rebac.mdx +++ b/docs/content/learn/rebac.mdx @@ -5,6 +5,8 @@ sidebar_position: 3 slug: /learn/rebac --- +import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; + # What is ReBAC? **Relationship-Based Access Control (ReBAC)** models permissions as relationships between users and resources, rather than as roles assigned to users or attributes attached to objects. *"Alice is an editor of doc-42"* and *"doc-42 is in folder-7"* and *"folder-7 is in workspace-A"* are all relationships; whether Alice can read doc-42 falls out of traversing those relationships. @@ -19,9 +21,9 @@ Most real authorization questions are graph questions: Roles can model the simple cases but blow up combinatorially as soon as hierarchy or sharing enters the picture. ReBAC stores the edges directly and queries them. -## ReBAC in OpenFGA +## ReBAC in -OpenFGA is a ReBAC engine in the [Zanzibar](/docs/learn/zanzibar) tradition. You define types and relations in a typed [DSL](/docs/configuration-language), write tuples like `(document:42, editor, user:alice)`, and call [check](/docs/interacting/relationship-queries) or `list-objects`. + is a ReBAC engine in the [Zanzibar](/docs/learn/zanzibar) tradition. You define types and relations in a typed [DSL](/docs/configuration-language), write tuples like `(document:42, editor, user:alice)`, and call [check](/docs/interacting/relationship-queries) or `list-objects`. ## When ReBAC is the right tool @@ -31,11 +33,31 @@ OpenFGA is a ReBAC engine in the [Zanzibar](/docs/learn/zanzibar) tradition. You ## When something else fits better -- Pure attribute checks (department equals "engineering", region equals "EU") are better served by attributes — see [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac). OpenFGA covers these with [conditions](/docs/modeling/conditions). +- Pure attribute checks (department equals "engineering", region equals "EU") are better served by attributes — see [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac). covers these with [conditions](/docs/modeling/conditions). - Infrastructure or admission policy across many domains — see [policy engines](/docs/learn/policy-engine). ## Related reading -- [RBAC vs. ReBAC](/docs/learn/rbac-vs-rebac) -- [ABAC vs. ReBAC](/docs/learn/abac-vs-rebac) -- [Authorization Concepts](/docs/authorization-concepts) + diff --git a/docs/content/learn/zanzibar.mdx b/docs/content/learn/zanzibar.mdx index d11728aa2b..5d1863692b 100644 --- a/docs/content/learn/zanzibar.mdx +++ b/docs/content/learn/zanzibar.mdx @@ -5,9 +5,11 @@ sidebar_position: 2 slug: /learn/zanzibar --- +import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs'; + # What is Google Zanzibar? -[Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/) is a 2019 paper from Google describing the authorization system used by Google Drive, YouTube, Calendar, Cloud, and most other Google products. It is the source of the design pattern OpenFGA implements. +[Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/) is a 2019 paper from Google describing the authorization system used by Google Drive, YouTube, Calendar, Cloud, and most other Google products. It is the source of the design pattern implements. ## What Zanzibar actually is @@ -25,19 +27,46 @@ Two ideas make it work: Before Zanzibar, every Google product had its own authorization layer. Cross-product features ("share a Drive doc to a Calendar event guest") meant authorization logic had to be duplicated and kept in sync. Zanzibar gave Google one model, one store, one decision surface. -## How OpenFGA maps to Zanzibar +## How maps to Zanzibar -OpenFGA implements the same model: + implements the core Zanzibar operations — `Write`, `Read`, `Check`, `Expand`, and `Watch` — and extends them with capabilities that aren't in the paper: -- **Schema** — written in the [OpenFGA DSL](/docs/configuration-language). +- **Schema** — written in the [ DSL](/docs/configuration-language). - **Tuples** — stored in PostgreSQL, MySQL, or SQLite. -- **Check** and **List-objects** — exposed via the [API](/docs/interacting/relationship-queries). -- **Conditions** (CEL) — OpenFGA's equivalent of caveats for attribute-based decisions. +- **Check** and **Expand** — exposed via the [API](/docs/interacting/relationship-queries). +- **ListObjects** and **ListUsers** — reverse queries that aren't in the Zanzibar paper, for answering *"what can this user see?"* and *"who has access to this object?"*. +- **Conditions** (CEL) — also not in the paper; 's mechanism for attribute-based decisions, similar in spirit to caveats. -OpenFGA does not replicate Zanzibar's globally distributed Spanner-backed architecture; it is designed to run on your existing databases. For most applications, that's the point — Zanzibar's *model* is what's valuable, not its operational scale. + does not replicate Zanzibar's globally distributed Spanner-backed architecture; it is designed to run on your existing databases. For most applications, that's the point — Zanzibar's *model* is what's valuable, not its operational scale. ## Related reading -- [ReBAC: Relationship-Based Access Control](/docs/learn/rebac) -- [Fine-Grained Authorization](/docs/learn/fine-grained-authorization) -- [Authorization Concepts](/docs/authorization-concepts) + diff --git a/docs/content/use-cases/ai-agent-authorization.mdx b/docs/content/use-cases/ai-agent-authorization.mdx index b07c45534e..8264377261 100644 --- a/docs/content/use-cases/ai-agent-authorization.mdx +++ b/docs/content/use-cases/ai-agent-authorization.mdx @@ -5,11 +5,13 @@ sidebar_position: 2 slug: /use-cases/ai-agent-authorization --- +import { ProductName, ProductNameFormat } from '@components/Docs'; + # AI Agent Authorization Autonomous and copilot agents act on behalf of users — but "on behalf of" is not the same as "as." A well-modeled agent has its **own identity**, inherits **only the permissions it actually needs**, and can be revoked independently of the user it serves. -OpenFGA models this directly: + models this directly: - **Agents are first-class principals.** They appear on the left side of tuples like users do, so checks, list-objects, and list-users all work on agent identities. - **Permissions are delegated, not copied.** Relations like `can_act_on_behalf_of` make the delegation explicit and revocable. @@ -24,11 +26,12 @@ The modeling guides below cover the building blocks: - [RAG authorization](/docs/use-cases/rag-authorization) — filter retrieved context by the user's permissions before the model sees it. - [MCP server authorization](/docs/use-cases/mcp-server-authorization) — protect tools and resources exposed via Model Context Protocol. + ## Why ReBAC fits agent workflows -Agent workflows are graph-shaped: a user grants an agent access to a workspace; the workspace contains documents; some documents are shared from other workspaces. Roles can't model that without exploding into per-resource role definitions. Relationships do it natively — and OpenFGA's [reverse queries](/docs/interacting/relationship-queries) (`list-objects`, `list-users`) let the agent ask *"what can I see?"* without scanning everything. +Agent workflows are graph-shaped: a user grants an agent access to a workspace; the workspace contains documents; some documents are shared from other workspaces. Roles can't model that without exploding into per-resource role definitions. Relationships do it natively — and 's [reverse queries](/docs/interacting/relationship-queries) (`list-objects`, `list-users`) let the agent ask *"what can I see?"* without scanning everything. ## Related reading - [Authorization for AI Agents](/docs/modeling/agents) — the full modeling guide, with examples. -- [OpenFGA vs. policy engines for agents](/docs/compare) — when relationship modeling is the right tool for agent workflows. +- [Policy vs Relationship Engines](/docs/learn/policy-engine) — when relationship modeling is the right tool for agent workflows. diff --git a/docs/content/use-cases/mcp-server-authorization.mdx b/docs/content/use-cases/mcp-server-authorization.mdx index 5eeb25c4ad..b5c040fbe1 100644 --- a/docs/content/use-cases/mcp-server-authorization.mdx +++ b/docs/content/use-cases/mcp-server-authorization.mdx @@ -5,29 +5,54 @@ sidebar_position: 4 slug: /use-cases/mcp-server-authorization --- +import { ProductName, ProductNameFormat } from '@components/Docs'; + # MCP Server Authorization -A Model Context Protocol server exposes tools and resources to an AI client. That makes it an authorization surface: every tool call and every resource read should be checked against the calling user (and the calling agent, if they're separate principals). +A Model Context Protocol server exposes tools to a client. That makes it an authorization surface: every tool call should be checked against the calling user. ## The pattern -Model the MCP server's surface in OpenFGA: +Model the MCP server's surface in : -- `tool` and `resource` types, each with relations like `can_invoke` or `can_read`. -- `user` and `agent` types as principals. -- Relationship tuples that grant specific users or agents access to specific tools and resources. +- A `tool` type with a `can_call` relation. +- `user`, `group`, and `role` types so permissions can be granted directly, by group membership, or by role assignment. +- Relationship tuples that grant specific users — or everyone via `user:*` — access to specific tools. On every MCP request: -1. Identify the caller (user, agent, or both via [agents as principals](/docs/modeling/agents/agents-as-principals)). -2. Call [check](/docs/interacting/relationship-queries) against the requested tool or resource. +1. Identify the calling user. +2. Call [check](/docs/interacting/relationship-queries) against the requested tool. 3. Allow or deny. -For listing tools and resources to the client, use [list-objects](/docs/interacting/relationship-queries) so the client only ever sees what it could actually invoke. +For listing tools to the client, use [list-objects](/docs/interacting/relationship-queries) so the client only ever sees what it could actually invoke. + +## Simplified model + +```dsl.openfga +model + schema 1.1 + +type user + +type group + relations + define member: [user] + +type role + relations + define assignee: [user, group#member] + +type tool + relations + define can_call: [user:*, user, role#assignee] +``` + +`can_call` accepts a public grant (`user:*`), a direct user, or any user assigned to a role — including users who are role assignees by way of group membership. The MCP server checks `tool:search#can_call` on every request, with the calling user as the subject. -## Bounding agent scope +## Going further -A common pitfall is granting agents the same broad role as the user they serve. Instead, model the delegation explicitly: the user grants the agent access to *this workspace* or *this set of tools* for *this session*, and revoke when done. The [task-based authorization](/docs/modeling/agents/task-based-authorization) guide walks through this. +When modeling MCP servers, the caller is always an agent. If you want your API to have different permissions for specific agents, model the agent as its own principal so you can scope and revoke its access independently. See the [MCP authorization modeling guide](/docs/modeling/agents/mcp-authorization) for time-bounded grants via conditions, and [agents as principals](/docs/modeling/agents/agents-as-principals) for delegation patterns. ## Related reading diff --git a/docs/content/use-cases/microservices-authorization.mdx b/docs/content/use-cases/microservices-authorization.mdx index 4ce085e229..0f104c7fbf 100644 --- a/docs/content/use-cases/microservices-authorization.mdx +++ b/docs/content/use-cases/microservices-authorization.mdx @@ -5,31 +5,56 @@ sidebar_position: 6 slug: /use-cases/microservices-authorization --- +import { ProductName, ProductNameFormat } from '@components/Docs'; + # Microservices Authorization -Microservices repeat the same pattern: each service ends up with its own roles table, its own permission checks, and its own subtle drift. OpenFGA centralizes that into a single authorization service that every microservice consults. +Microservices repeat the same pattern: each service ends up with its own roles table, its own permission checks, and its own subtle drift. centralizes that into a single authorization service that every microservice consults. ## The pattern -- **One OpenFGA store** holds the model and tuples for the whole system. -- **Each microservice** calls `check`, `list-objects`, or `list-users` over the OpenFGA API rather than implementing its own permission logic. +- **One store** holds the model and tuples for the whole system. +- **Each microservice** calls `check`, `list-objects`, or `list-users` over the API rather than implementing its own permission logic. - **Writes go through the service that owns the relationship** (e.g. the membership service writes `member` tuples; the document service writes `can_share` tuples). ## Why this beats per-service authorization - **One model to reason about.** Cross-service questions (*"can this user read this document via their team's project access?"*) become a single graph traversal instead of a coordination dance. -- **No more drifted roles tables.** A relation defined once in the OpenFGA model is the same relation in every service. +- **No more drifted roles tables.** A relation defined once in the model is the same relation in every service. - **Reverse queries work across services.** `list-objects` returns every resource the user can access, regardless of which service owns it. +## Simplified model + +```dsl +type user + +type team + relations + define member: [user] + +type project + relations + define team: [team] + define can_view: member from team + define can_edit: member from team + +type document + relations + define project: [project] + define can_view: can_view from project + define can_edit: can_edit from project +``` + +The membership service writes `team#member` tuples; the project service writes `project#team`; the document service writes `document#project`. Any service — including ones that only read documents — answers permission questions with a single `check` against the same store, instead of reimplementing team/project/document role logic locally. + ## Operational shape -- **Latency.** Run OpenFGA close to the calling services. Read AI runs [5,200 RPS at 20 ms p99](/docs/adopters/read-ai) on PostgreSQL — a useful reference for sizing. -- **Caching.** OpenFGA supports check caching; pair with short cache TTLs in clients for hot paths. +- **Latency.** Run close to the calling services. Sizing depends on workload, but production deployments routinely hit thousands of RPS at single-digit-to-low-double-digit ms p99 on PostgreSQL. +- **Caching.** supports check caching; pair with short cache TTLs in clients for hot paths. - **Observability.** Treat authorization decisions as a first-class metric. Track check rate, latency, and authorization failures per service. ## Related reading -- [Running OpenFGA in production](/docs/best-practices/running-in-production) +- [Running in production](/docs/best-practices/running-in-production) - [Source of truth](/docs/best-practices/source-of-truth) - [Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas) -- [Customer case studies](/docs/adopters) diff --git a/docs/content/use-cases/multi-tenant-saas.mdx b/docs/content/use-cases/multi-tenant-saas.mdx index 0c1c289d37..2e0a6c2164 100644 --- a/docs/content/use-cases/multi-tenant-saas.mdx +++ b/docs/content/use-cases/multi-tenant-saas.mdx @@ -5,9 +5,11 @@ sidebar_position: 5 slug: /use-cases/multi-tenant-saas --- +import { ProductName, ProductNameFormat } from '@components/Docs'; + # Multi-Tenant SaaS Authorization -Multi-tenant SaaS is one of OpenFGA's most common production shapes. The same binary serves every tenant; tenant boundaries are enforced inside the authorization model rather than at the database or service layer. +Multi-tenant SaaS is one of 's most common production shapes. The same binary serves every tenant; tenant boundaries are enforced inside the authorization model rather than at the database or service layer. ## The pattern @@ -23,11 +25,44 @@ This gives you: - **Cross-tenant sharing when you want it.** A document can also have individually granted users from other organizations — same model, no special case. - **A single store for all tenants.** Operationally cheaper than per-tenant databases; the tuple count grows linearly with usage rather than fan-out. -## Production references +## Per-tenant custom roles + +Most SaaS products eventually need tenant-defined roles — *"Acme wants a `billing-manager` role; Globex wants a `compliance-reviewer` role"* — without redeploying the model. The pattern is to make `role` a first-class type whose `assignee` relation can be a user, another role, or a group, and then bind those roles to organization-level permissions: + +```dsl +type user + +type group + relations + define member: [user, group#member] + +type role + relations + define assignee: [user, role#assignee, group#member] + +type organization + relations + define admin: [user, role#assignee] + define billing_manager: [role#assignee] or admin + define document_manager: [role#assignee] or admin + + define can_invite_user: admin + define can_edit_billing: billing_manager + define can_create_document: document_manager + +type document + relations + define organization: [organization] + define editor: document_manager from organization + define viewer: document_manager from organization + + define can_view: viewer or editor + define can_edit: editor +``` + +Each tenant creates its own `role:acme-billing-manager`, `role:acme-document-management`, etc., and binds them to the predefined organization-level relations (`billing_manager`, `document_manager`, `admin`). Users or groups become assignees of those roles, and document-level permissions inherit through the organization. The model itself stays static; tenants get full flexibility on which roles exist and who is in them. -- [Grafana Labs](/docs/adopters/grafana) — single platform serving multi-tenant Grafana Cloud and embedded OSS. -- [Agicap](/docs/adopters/agicap) — 8,000+ customer organizations on a single OpenFGA store with conditional ReBAC. -- [Read AI](/docs/adopters/read-ai) — 5.3B+ tuples across many tenants on PostgreSQL. +The full working example — including tuples, group nesting (`group:acme-data-engineering#member` ∈ `group:engineering#member`), and `list_users` assertions — is in the [multitenant-rbac sample store](https://github.com/openfga/sample-stores/tree/main/stores/multitenant-rbac). You can run it locally with the [FGA CLI](/docs/getting-started/cli) to see the checks pass. ## Modeling building blocks @@ -37,5 +72,4 @@ This gives you: ## Related reading -- [Customer case studies](/docs/adopters) - [Microservices authorization](/docs/use-cases/microservices-authorization) diff --git a/docs/content/use-cases/overview.mdx b/docs/content/use-cases/overview.mdx index 4b8736e7eb..d270f9b436 100644 --- a/docs/content/use-cases/overview.mdx +++ b/docs/content/use-cases/overview.mdx @@ -5,9 +5,11 @@ sidebar_position: 1 slug: /use-cases --- -# OpenFGA Use Cases +import { ProductName, ProductNameFormat } from '@components/Docs'; -OpenFGA is a Zanzibar-style relationship engine. The patterns below are the ones that show up most often in production — each links to the modeling guide and, where available, a customer reference that runs the pattern at scale. +# Use Cases + + is a Zanzibar-style relationship engine. The patterns below are the ones that show up most often in production — each links to the modeling guide and, where available, an adopter reference that runs the pattern at scale. ## AI and agent authorization @@ -17,9 +19,6 @@ OpenFGA is a Zanzibar-style relationship engine. The patterns below are the ones ## Application authorization -- **[Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas)** — one OpenFGA store, many tenants, with strict isolation. Used by Grafana Labs, Agicap, and others. +- **[Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas)** — one store, many tenants, with strict isolation. Used by Grafana Labs, Agicap, and others. - **[Microservices authorization](/docs/use-cases/microservices-authorization)** — a central authorization service that every microservice consults, instead of each service rolling its own roles table. -## Customer references - -The [adopters section](/docs/adopters) documents how Read AI, Agicap, Grafana, Docker, and Zuplo run OpenFGA in production — including the patterns above at scale. diff --git a/docs/content/use-cases/rag-authorization.mdx b/docs/content/use-cases/rag-authorization.mdx index e41b0d7334..af500c2a2a 100644 --- a/docs/content/use-cases/rag-authorization.mdx +++ b/docs/content/use-cases/rag-authorization.mdx @@ -5,22 +5,44 @@ sidebar_position: 3 slug: /use-cases/rag-authorization --- +import { ProductName, ProductNameFormat } from '@components/Docs'; + # RAG Authorization -Retrieval-augmented generation pipelines retrieve documents from a corpus and hand them to the model as context. If the corpus contains anything the asking user shouldn't see, the model will happily summarize it back to them. The fix is to **filter retrieval results by the user's permissions** before the model ever sees them. +Retrieval-augmented generation pipelines retrieve documents from a data set and hand them to the model as context. If the data set contains anything the asking user shouldn't see, the model will happily summarize it back to them. The fix is to **filter retrieval results by the user's permissions** before the model ever sees them. ## The pattern 1. The user asks the agent a question. 2. The retriever pulls candidate documents (vector search, keyword, hybrid). -3. **OpenFGA filters** the candidates: for each candidate `doc:X`, check whether `user:Y` has `can_view`. Or call [list-objects](/docs/interacting/relationship-queries) once to get the full set of documents this user can read, then intersect. +3. ** filters** the candidates: for each candidate `doc:X`, check whether `user:Y` has `can_view`. Or call [list-objects](/docs/interacting/relationship-queries) once to get the full set of documents this user can read, then intersect. 4. Only the surviving documents are passed to the model as context. The same model and prompt now produce different — and correct — answers per user, because the context they see is scoped to what they're allowed to see. +## Simplified model + +```dsl +type user + +type folder + relations + define parent: [folder] + define viewer: [user] + define can_view: viewer or can_view from parent + +type document + relations + define parent: [folder] + define viewer: [user] + define can_view: viewer or can_view from parent +``` + +Folders nest arbitrarily deep — a viewer on a top-level folder inherits `can_view` on every descendant folder and document. Before retrieval reaches the model, call `list-objects(user:alice, can_view, document)` to get every document Alice can read, then intersect with the retriever's candidate set. + ## Why list-objects matters here -For small corpora, per-document checks are fine. For larger ones, `list-objects` is dramatically cheaper: one call returns the full set of documents the user can read, and you intersect that with the retriever's candidates. This is exactly the case OpenFGA's reverse queries are designed for. +For small set of documents, per-document checks are fine. For larger ones, `list-objects` is dramatically cheaper: one call returns the full set of documents the user can read, and you intersect that with the retriever's candidates. This is exactly the case 's reverse queries are designed for. ## Conditions and contextual data diff --git a/docs/sidebars.js b/docs/sidebars.js index b21149b255..0b48d9d8bc 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -470,23 +470,6 @@ const sidebars = { } ], }, - { - type: 'category', - collapsible: true, - collapsed: true, - label: 'Adopters', - link: { type: 'doc', id: 'content/adopters/overview' }, - items: [ - { type: 'doc', label: 'Agicap', id: 'content/adopters/agicap' }, - { type: 'doc', label: 'Docker', id: 'content/adopters/docker' }, - { type: 'doc', label: 'Grafana Labs', id: 'content/adopters/grafana' }, - { type: 'doc', label: 'Read AI', id: 'content/adopters/read-ai' }, - { type: 'doc', label: 'Zuplo', id: 'content/adopters/zuplo' }, - { type: 'doc', label: 'Headspace', id: 'content/adopters/headspace' }, - { type: 'doc', label: 'OpenLane', id: 'content/adopters/openlane' }, - { type: 'doc', label: 'Vitrolife Group', id: 'content/adopters/vitrolife' }, - ], - }, { type: 'category', collapsible: true, @@ -500,6 +483,7 @@ const sidebars = { { type: 'doc', label: 'Human Resources', id: 'content/industries/human-resources' }, { type: 'doc', label: 'CRM', id: 'content/industries/crm' }, { type: 'doc', label: 'Learning Management', id: 'content/industries/lms' }, + { type: 'doc', label: 'Applicant Tracking', id: 'content/industries/applicant-tracking-system' }, ], }, { @@ -531,29 +515,6 @@ const sidebars = { { type: 'doc', label: 'Policy vs Relationship Engines', id: 'content/learn/policy-engine' }, ], }, - { - type: 'doc', - label: 'Alternatives', - id: 'content/alternatives/overview', - }, - { - type: 'category', - collapsible: true, - collapsed: true, - label: 'Compare', - link: { type: 'doc', id: 'content/compare/overview' }, - items: [ - { type: 'doc', label: 'OpenFGA vs SpiceDB', id: 'content/compare/spicedb' }, - { type: 'doc', label: 'OpenFGA vs Cerbos', id: 'content/compare/cerbos' }, - { type: 'doc', label: 'OpenFGA vs Permit.io', id: 'content/compare/permit' }, - { type: 'doc', label: 'OpenFGA vs Oso', id: 'content/compare/oso' }, - { type: 'doc', label: 'OpenFGA vs Ory Keto', id: 'content/compare/keto' }, - { type: 'doc', label: 'OpenFGA vs OPA', id: 'content/compare/opa' }, - { type: 'doc', label: 'OpenFGA vs WorkOS FGA', id: 'content/compare/workos' }, - { type: 'doc', label: 'OpenFGA vs Keycloak', id: 'content/compare/keycloak' }, - { type: 'doc', label: 'OpenFGA vs Auth0 FGA', id: 'content/compare/auth0-fga' }, - ], - }, ], }; diff --git a/static/llms.txt b/static/llms.txt index affe5ec453..841b69d790 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -95,6 +95,13 @@ OpenFGA implements authorization through: - [Migrating Models](https://openfga.dev/docs/modeling/migrating/migrating-models) +#### Authorization for Agents +- [Overview](https://openfga.dev/docs/modeling/agents/overview) - Authorization for Agents overview + - [Modeling Agents as Principals](https://openfga.dev/docs/modeling/agents/agents-as-principals) + - [RAG Authorization](https://openfga.dev/docs/modeling/agents/rag-authorization) + - [Authorization for MCP Servers](https://openfga.dev/docs/modeling/agents/mcp-authorization) + - [Task-Based Authorization](https://openfga.dev/docs/modeling/agents/task-based-authorization) + #### Interacting with the API - [Overview](https://openfga.dev/docs/interacting/overview) - Interacting with the API overview - [Manage User Access](https://openfga.dev/docs/interacting/managing-user-access) @@ -106,15 +113,44 @@ OpenFGA implements authorization through: - [Relationship Queries](https://openfga.dev/docs/interacting/relationship-queries) - [Get Tuple Changes](https://openfga.dev/docs/interacting/read-tuple-changes) - [Search with Permissions](https://openfga.dev/docs/interacting/search-with-permissions) + - [AuthZEN API](https://openfga.dev/docs/interacting/authzen) #### Best Practices - [Overview](https://openfga.dev/docs/best-practices/overview) - Best Practices overview - [Adoption Patterns](https://openfga.dev/docs/best-practices/adoption-patterns) - [Authorization Model Design Principles](https://openfga.dev/docs/best-practices/modeling-design-principles) + - [Modeling ABAC](https://openfga.dev/docs/best-practices/modeling-abac) - [Modeling Roles](https://openfga.dev/docs/best-practices/modeling-roles) - [Source of Truth](https://openfga.dev/docs/best-practices/source-of-truth) - [Running OpenFGA in Production](https://openfga.dev/docs/best-practices/running-in-production) +#### Industries +- [Overview](https://openfga.dev/docs/industries/overview) - Industries overview + - [Healthcare](https://openfga.dev/docs/industries/healthcare) + - [Banking](https://openfga.dev/docs/industries/banking) + - [E-commerce](https://openfga.dev/docs/industries/ecommerce) + - [Human Resources](https://openfga.dev/docs/industries/human-resources) + - [CRM](https://openfga.dev/docs/industries/crm) + - [Learning Management](https://openfga.dev/docs/industries/lms) + - [Applicant Tracking](https://openfga.dev/docs/industries/applicant-tracking-system) + +#### Use Cases +- [Overview](https://openfga.dev/docs/use-cases/overview) - Use Cases overview + - [AI Agent Authorization](https://openfga.dev/docs/use-cases/ai-agent-authorization) + - [RAG Authorization](https://openfga.dev/docs/use-cases/rag-authorization) + - [MCP Server Authorization](https://openfga.dev/docs/use-cases/mcp-server-authorization) + - [Multi-Tenant SaaS](https://openfga.dev/docs/use-cases/multi-tenant-saas) + - [Microservices Authorization](https://openfga.dev/docs/use-cases/microservices-authorization) + +#### Learn +- [Overview](https://openfga.dev/docs/learn/overview) - Learn overview + - [Zanzibar](https://openfga.dev/docs/learn/zanzibar) + - [What is ReBAC?](https://openfga.dev/docs/learn/rebac) + - [RBAC vs ReBAC](https://openfga.dev/docs/learn/rbac-vs-rebac) + - [ABAC vs ReBAC](https://openfga.dev/docs/learn/abac-vs-rebac) + - [Fine-Grained Authorization](https://openfga.dev/docs/learn/fine-grained-authorization) + - [Policy vs Relationship Engines](https://openfga.dev/docs/learn/policy-engine) + ## Supported Features From 250d5eae38eb27d2cb6b02d3b918170ab1fbd4b9 Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 17:17:23 -0300 Subject: [PATCH 04/13] fix: removed duplicated content that is now in /learn without breaking existing URLS --- docs/content/authorization-concepts.mdx | 31 +++++------- docs/content/compare/cerbos.mdx | 48 ------------------- docs/content/compare/oso.mdx | 42 ---------------- docs/content/compare/permit.mdx | 45 ----------------- .../learn/fine-grained-authorization.mdx | 9 ++++ docs/content/learn/policy-engine.mdx | 11 +++-- docs/content/learn/rbac-vs-rebac.mdx | 4 ++ docs/content/learn/rebac.mdx | 4 ++ docs/content/use-cases/overview.mdx | 2 +- 9 files changed, 37 insertions(+), 159 deletions(-) delete mode 100644 docs/content/compare/cerbos.mdx delete mode 100644 docs/content/compare/oso.mdx delete mode 100644 docs/content/compare/permit.mdx diff --git a/docs/content/authorization-concepts.mdx b/docs/content/authorization-concepts.mdx index d570722d19..c1c271a786 100644 --- a/docs/content/authorization-concepts.mdx +++ b/docs/content/authorization-concepts.mdx @@ -85,46 +85,37 @@ For example, when you log in to Google, Authentication is the process of verifyi ## What is Fine-Grained Authorization? -Fine-Grained Authorization (FGA) implies the ability to grant specific users permission to perform certain actions in specific resources. +Fine-Grained Authorization (FGA) means deciding access at the level of the individual resource and action — *"Alice can edit document-42"*, not just *"Alice is an editor"*. Well-designed FGA systems handle millions of objects and users with permissions that change rapidly, like Google Drive's per-document and per-folder sharing. -Well-designed FGA systems allow you to manage permissions for millions of objects and users. These permissions can change rapidly as a system continually adds objects and updates access permissions for its users. - -A notable example of FGA is Google Drive: access can be granted either to documents or to folders, as well as to individual users or users as a group, and access rights regularly change as new documents are created and shared with specific users or groups. +See [Fine-Grained Authorization](./learn/fine-grained-authorization.mdx) for the full explanation. ## What is Role-Based Access Control? -In [Role-Based Access Control](https://en.wikipedia.org/wiki/Role-based_access_control) (RBAC), permissions are assigned to users based on their role in a system. For example, a user needs the `editor` role to edit content. +In [Role-Based Access Control](https://en.wikipedia.org/wiki/Role-based_access_control) (RBAC), permissions are assigned to users based on roles like `editor` or `admin`. RBAC fits flat, single-tenant access models but breaks down with hierarchy, sharing, or multi-tenancy. -RBAC systems enable you to define users, groups, roles, and permissions, then store them in a centralized location. Applications access that information to make authorization decisions. +See [RBAC vs. ReBAC](./learn/rbac-vs-rebac.mdx) for when roles run out and how models RBAC cleanly. ## What is Attribute-Based Access Control? -In [Attribute-Based Access Control](https://en.wikipedia.org/wiki/Attribute-based_access_control) (ABAC), permissions are granted based on a set of attributes that a user or resource possesses. For example, a user assigned both `marketing` and `manager` attributes is entitled to publish and delete posts that have a `marketing` attribute. +In [Attribute-Based Access Control](https://en.wikipedia.org/wiki/Attribute-based_access_control) (ABAC), permissions are granted based on attributes of the user, resource, or request — for example, a user with `marketing` and `manager` attributes can publish marketing posts. ABAC implementations typically pull attributes from multiple sources at decision time. -Applications implementing ABAC need to retrieve information stored in multiple data sources - like RBAC services, user directories, and application-specific data sources - to make authorization decisions. +See [ABAC vs. ReBAC](./learn/abac-vs-rebac.mdx) for how the two combine. ## What is Policy-Based Access Control? -Policy-Based Access Control (PBAC) is the ability to manage authorization policies in a centralized way that’s external to the application code. Most implementations of ABAC are also PBAC. +Policy-Based Access Control (PBAC) manages authorization policies centrally, external to application code. Most ABAC implementations are also PBAC. 's [model DSL](./configuration-language.mdx) is itself a policy: committed to Git, reviewed via PR, deployed like any other code — see [Policy Engines vs. Relationship Engines](./learn/policy-engine.mdx). ## What is Relationship-Based Access Control? -[Relationship-Based Access Control](https://en.wikipedia.org/wiki/Relationship-based_access_control) (ReBAC) enables user access rules to be conditional on relations that a given user has with a given object _and_ that object's relationship with other objects. For example, a given user can view a given document if the user has access to the document's parent folder. - -ReBAC is a superset of RBAC: you can fully implement RBAC with ReBAC. -ReBAC also lets you natively solve for ABAC when attributes can be expressed in the form of relationships. For example ‘a user’s manager’, ‘the parent folder’, ‘the owner of a document’, ‘the user’s department’ can be defined as relationships. +[Relationship-Based Access Control](https://en.wikipedia.org/wiki/Relationship-based_access_control) (ReBAC) makes access rules conditional on relationships between users and objects, and between objects themselves — *"a user can view a document if they have access to its parent folder"*. ReBAC is a superset of RBAC and natively covers ABAC scenarios when attributes are expressed as relationships. extends ReBAC with [Conditions](./modeling/conditions.mdx) and [Contextual Tuples](./modeling/token-claims-contextual-tuples.mdx) for the remaining attribute-driven cases. - extends ReBAC by making it simpler to express additional ABAC scenarios using [Conditions](./modeling/conditions.mdx) or [Contextual Tuples](./modeling/token-claims-contextual-tuples.mdx). - -ReBAC can also be considered PBAC, as authorization policies are centralized. +See [What is ReBAC?](./learn/rebac.mdx) for the full picture. ## What is Zanzibar? -[Zanzibar](https://research.google/pubs/pub48190/) is Google's global authorization system across Google's product suite. It’s based on ReBAC and uses object-relation-user tuples to store relationship data, then checks those relations for a match between a user and an object. For more information, see [Zanzibar Academy](https://zanzibar.academy). - -ReBAC systems based on Zanzibar store the data necessary to make authorization decisions in a centralized database. Applications only need to call an API to make authorization decisions. +[Zanzibar](https://research.google/pubs/pub48190/) is Google's global authorization system, used by Drive, YouTube, Calendar, and Cloud. It stores object-relation-user tuples and answers checks and reverse queries against the resulting graph. implements the Zanzibar model on your existing databases. - is an example of a Zanzibar-based authorization system. +See [What is Zanzibar?](./learn/zanzibar.mdx) for what the paper introduced and how maps to it. Comparison facts in this page are drawn from each project's public site as of May 2026. diff --git a/docs/content/compare/oso.mdx b/docs/content/compare/oso.mdx deleted file mode 100644 index a48d555769..0000000000 --- a/docs/content/compare/oso.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: OpenFGA vs Oso -description: Compare OpenFGA's open-source Zanzibar-style engine with Oso Cloud's hosted authorization service and the Polar policy language. -sidebar_position: 5 -slug: /compare/oso ---- - -# OpenFGA vs Oso - -OpenFGA is an open-source Zanzibar-style authorization engine governed by the CNCF. [Oso](https://www.osohq.com) ships **Oso Cloud**, a managed authorization service whose policies are written in **Polar**, Oso's domain-specific language. - -## Side by side - -| | OpenFGA | Oso | -| --- | --- | --- | -| Delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA) | Oso Cloud (hosted) | -| License | Apache 2.0 | Commercial (Cloud) | -| Governance | CNCF Incubation | Single vendor | -| Language | OpenFGA DSL | Polar (general logic-programming-flavored DSL) | -| Model styles | ReBAC first; RBAC and ABAC via relations and conditions | RBAC / ReBAC / ABAC / "AnyBAC" via Polar | -| Data store | PostgreSQL, MySQL, SQLite (you run it) | Vendor-managed | - -## When OpenFGA is the right pick - -- You want a **self-hosted, open-source** authorization service with an Apache 2.0 license and CNCF governance — the consistent decision factor in every published [adopter interview](/docs/adopters). -- You want a **typed, narrow DSL** specifically for relationship modeling rather than a general policy language. The [FGA CLI](https://github.com/openfga/cli) makes the model-as-code workflow first-class — lint, test, and deploy the DSL alongside your application code, with a [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest) for infrastructure-as-code rollouts. -- You need to keep the **relationship data inside your own infrastructure**, for compliance or latency reasons (Zuplo's [edge deployment](/docs/adopters/zuplo) is a good example). - -## When Oso may be the right pick - -- You want a **fully managed** authorization service and prefer to express rules in Polar rather than a relationship DSL. -- Polar's general expressiveness fits your team's mental model better than a Zanzibar-style typed model would. - -## Architectural trade-off: dedicated tuple store - -OpenFGA stores relationship tuples in its own database (PostgreSQL, MySQL, or SQLite). Oso Cloud's pitch is the opposite — it queries your existing application database so you don't duplicate data. That's a real trade-off worth naming: a dedicated tuple store costs you a write path and storage, but in return you get a typed relationship model, predictable fan-out performance, multi-tenant isolation, and a consistency model that doesn't depend on your application schema. Read AI runs **5.3B+ tuples at 20 ms p99** on PostgreSQL ([case study](/docs/adopters/read-ai)) — the dedicated-store model scales. - -## Performance claims - -Oso publishes very high throughput numbers on its homepage. We don't reproduce vendor-published benchmarks here — but for reference, the largest publicly documented OpenFGA deployment ([Read AI](/docs/adopters/read-ai)) runs **5,200 RPS at 20 ms p99 over 5.3B+ tuples** on PostgreSQL, end to end in production traffic. - -> Comparison facts in this page were collected from [osohq.com](https://www.osohq.com) on 2026-05-20. diff --git a/docs/content/compare/permit.mdx b/docs/content/compare/permit.mdx deleted file mode 100644 index 10c0312df5..0000000000 --- a/docs/content/compare/permit.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: OpenFGA vs Permit.io -description: Compare OpenFGA's self-hosted Zanzibar-style engine with Permit.io's hosted authorization control plane. -sidebar_position: 4 -slug: /compare/permit ---- - -# OpenFGA vs Permit.io - -OpenFGA is a self-hostable, CNCF-governed Zanzibar-style engine with official SDKs, a Playground UI, an [FGA CLI](https://github.com/openfga/cli), a [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest), a Helm chart, and a managed option via [Auth0 FGA](/docs/compare/auth0-fga). [Permit.io](https://www.permit.io) is a commercial **hosted authorization control plane** that wraps existing policy engines — historically OPA and Cedar, with OpenFGA as one of several supported engines — behind a unified UI, audit pipeline, MCP gateway, and SDKs. The two are not the same shape: OpenFGA is an engine plus tooling; Permit.io is a multi-engine control plane. - -## Side by side - -| | OpenFGA | Permit.io | -| --- | --- | --- | -| Primary delivery | Self-hosted open source, or Auth0 FGA / Okta FGA (managed OpenFGA from Auth0/Okta) | Hosted control plane (commercial) | -| License | Apache 2.0 | Commercial | -| Governance | CNCF Incubation | Single vendor | -| Model | Zanzibar-style ReBAC, conditions for ABAC | RBAC, ABAC, ReBAC over OPA-style engines | -| Data residency | Wherever you run OpenFGA | Vendor-managed unless self-hosted PDP is purchased | -| First-party UI | OpenFGA Playground (web); hosted dashboard via Auth0 FGA / Okta FGA | Full no-code/low-code admin UI | -| Official SDKs | Go, Java, .NET, Python, JavaScript/TypeScript | Multiple languages | -| IaC & CLI | FGA CLI, Terraform provider, Helm chart | Terraform provider, CLI | - -## When OpenFGA is the right pick - -- You want **self-hosted, open source, and CNCF-governed**. Every adopter we document picked OpenFGA partly to avoid licensing risk and vendor lock-in (see [customer case studies](/docs/adopters)). -- You need **regulatory data residency** — e.g. fintech (Agicap), healthcare, or air-gapped environments — that a hosted SaaS makes harder. -- You want a **single, typed model file** versioned alongside code, not a vendor admin UI as the source of truth. The [FGA CLI](https://github.com/openfga/cli) and [Terraform provider](https://registry.terraform.io/providers/openfga/openfga/latest) make the model-as-code workflow first-class — write the DSL, lint and test it locally, diff it in PRs, and deploy it like any other code artifact. - - -## When Permit.io may be the right pick - -- You want a turn-key hosted authorization product with built-in admin UI, audit, and SOC 2 / HIPAA / GDPR / CCPA compliance attestations from the vendor. -- You don't want to operate the authorization data store yourself and are willing to pay for managed infrastructure. - -## Migration shape - -Teams that move from a hosted control plane to OpenFGA typically: - -1. Re-express their current RBAC/ABAC rules in the OpenFGA DSL — relations replace roles, conditions cover attribute checks. -2. Run [parallel checks](/docs/best-practices) against both engines for a period, comparing decisions. Docker [used exactly this pattern](/docs/adopters/docker). -3. Cut over per-feature once both systems agree. - -> Permit.io product details above were collected from [permit.io](https://www.permit.io) on 2026-05-20. Verify current product scope with the vendor before making a decision. diff --git a/docs/content/learn/fine-grained-authorization.mdx b/docs/content/learn/fine-grained-authorization.mdx index cffabe0795..5676f7a915 100644 --- a/docs/content/learn/fine-grained-authorization.mdx +++ b/docs/content/learn/fine-grained-authorization.mdx @@ -33,6 +33,15 @@ Coarse-grained models can simulate these with enough effort, but the authorizati - **Multi-tenant SaaS** with external sharing. - **AI agents and RAG**, where each user must only see their slice of the corpus — covered in [AI agent authorization](/docs/use-cases/ai-agent-authorization). +## Choosing the right model + +A short decision path: + +- **Flat access, a handful of roles, single tenant** — [RBAC](/docs/learn/rbac-vs-rebac) is enough. +- **Decisions driven mostly by request attributes** (region, department, time-of-day) — start with [ABAC](/docs/learn/abac-vs-rebac) or a [policy engine](/docs/learn/policy-engine). +- **Hierarchy, sharing, multi-tenancy, or reverse queries** — you want a relationship engine. handles attribute checks too via [conditions](/docs/modeling/conditions), so you usually don't need a second engine. +- **Mixed infrastructure + application policy** — a policy engine at the admission layer plus for the application is the common pairing. + ## Related reading , SpiceDB, Ory Keto, all in the [Zanzibar](/docs/learn/zanzibar) tradition — store relationship tuples in a database and answer queries against the stored graph. The engine **is the database**. ## When a policy engine fits - Decisions are mostly **attribute-driven** — claims, resource metadata, request context. -- The same policy needs to apply across **multiple domains** — Kubernetes admission, Terraform, service-mesh, application requests. -- You want policy in **Git as code**, reviewed via PR, deployed as bundles. +- All the **data needed for the decision is in hand at request time** — claims in the token, fields on the resource, request context — so the engine doesn't need to fetch anything to answer. +- You **already run one** for infrastructure or admission policy — extending it to cover application rules avoids a second decision surface. OPA's [graduated CNCF status](https://www.cncf.io/projects/open-policy-agent-opa/) and broad ecosystem make it the default choice in this category. ## When a relationship engine fits - Decisions depend on **relationships that change at write time** — group membership, document sharing, folder hierarchy, multi-tenant ownership. +- The data behind the decision is **too large or too dynamic to ship on every request** — millions of memberships, deeply nested hierarchies — so the engine needs to own the store. - You need **reverse queries** — *"list every resource this user can read"* — which inherently require a stored graph. - Permissions are **per-resource and per-user**, not just per-attribute. +## Policy as code in + +The "policy in Git, reviewed via PR" workflow isn't unique to policy engines. The [model DSL](/docs/configuration-language) is the policy: types, relations, and [conditions](/docs/modeling/conditions) live in a text file you commit, review, and deploy like any other code. The same model backs authorization decisions across services, languages, and domains — one source of truth instead of policy logic re-implemented per service. + ## You can use both Pairing them is common: a relationship engine for application authorization, a policy engine at the infrastructure or admission layer. Inside the application, covers most attribute-driven rules with [conditions](/docs/modeling/conditions) and [contextual tuples](/docs/interacting/contextual-tuples), so a second engine isn't always needed. diff --git a/docs/content/learn/rbac-vs-rebac.mdx b/docs/content/learn/rbac-vs-rebac.mdx index 5f25416776..845c307975 100644 --- a/docs/content/learn/rbac-vs-rebac.mdx +++ b/docs/content/learn/rbac-vs-rebac.mdx @@ -19,6 +19,10 @@ import { ProductName, ProductNameFormat, RelatedSection } from '@components/Docs | Reverse queries | Needs a separate index | First-class (`list-objects`) | | Best fit | Small teams, flat access models | Hierarchies, sharing, multi-tenancy | +## When RBAC is enough + +RBAC is the right tool when access is flat and uniform: a small number of roles (admin, editor, viewer), a single tenant, no per-resource sharing, and no need to answer *"what can this user see?"* across millions of objects. Most internal tools, admin consoles, and early-stage SaaS apps fit this shape, and reaching for a relationship engine before you need one is overkill. + ## Where RBAC runs out The classic break is multi-tenancy. With RBAC, "admin of organization 42" becomes a distinct role from "admin of organization 43" — multiplied by every tenant. Tools paper over this with `org:admin` *scopes*, but at that point you've reinvented relationships, badly. diff --git a/docs/content/learn/rebac.mdx b/docs/content/learn/rebac.mdx index f0630c47d2..7eea7d2886 100644 --- a/docs/content/learn/rebac.mdx +++ b/docs/content/learn/rebac.mdx @@ -21,6 +21,10 @@ Most real authorization questions are graph questions: Roles can model the simple cases but blow up combinatorially as soon as hierarchy or sharing enters the picture. ReBAC stores the edges directly and queries them. +## ReBAC, ACLs, and roles + +ReBAC is the descendant of **access control lists (ACLs)** — the original Unix-style model where each resource carries a list of who can access it. ACLs handle per-resource sharing well but have no notion of inheritance, types, or groups. RBAC fixed the grouping problem by introducing roles but lost per-resource granularity. ReBAC keeps both: tuples are per-resource like ACLs, and the typed schema lets relations compose like roles do — including across hierarchies. + ## ReBAC in is a ReBAC engine in the [Zanzibar](/docs/learn/zanzibar) tradition. You define types and relations in a typed [DSL](/docs/configuration-language), write tuples like `(document:42, editor, user:alice)`, and call [check](/docs/interacting/relationship-queries) or `list-objects`. diff --git a/docs/content/use-cases/overview.mdx b/docs/content/use-cases/overview.mdx index d270f9b436..927e3d0916 100644 --- a/docs/content/use-cases/overview.mdx +++ b/docs/content/use-cases/overview.mdx @@ -19,6 +19,6 @@ import { ProductName, ProductNameFormat } from '@components/Docs'; ## Application authorization -- **[Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas)** — one store, many tenants, with strict isolation. Used by Grafana Labs, Agicap, and others. +- **[Multi-tenant SaaS](/docs/use-cases/multi-tenant-saas)** — one store, many tenants, with strict isolation. - **[Microservices authorization](/docs/use-cases/microservices-authorization)** — a central authorization service that every microservice consults, instead of each service rolling its own roles table. From 23a2bcefb9080adab1d548461592df941b890d4d Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 17:25:26 -0300 Subject: [PATCH 05/13] fix: small seo fixes --- blog/fine-grained-news-2023-12.md | 1 + blog/fine-grained-news-2024-01.md | 1 + blog/fine-grained-news-2024-02.md | 1 + blog/fine-grained-news-2024-03.md | 1 + blog/fine-grained-news-2024-04.md | 1 + blog/fine-grained-news-2024-05.md | 1 + blog/fine-grained-news-2024-06.md | 1 + blog/fine-grained-news-2024-07.md | 1 + blog/fine-grained-news-2024-08.md | 1 + blog/fine-grained-news-2024-09.md | 1 + blog/fine-grained-news-2024-10.md | 3 ++- blog/fine-grained-news-2024-11.md | 1 + blog/fine-grained-news-2025-01.md | 1 + blog/fine-grained-news-2025-02.md | 1 + blog/fine-grained-news-2025-09.md | 1 + blog/fine-grained-news-2025-10.md | 1 + docs/content/authorization-concepts.mdx | 12 ++++++------ 17 files changed, 23 insertions(+), 7 deletions(-) diff --git a/blog/fine-grained-news-2023-12.md b/blog/fine-grained-news-2023-12.md index f80cf78670..4dc3a6c55d 100644 --- a/blog/fine-grained-news-2023-12.md +++ b/blog/fine-grained-news-2023-12.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-01.md b/blog/fine-grained-news-2024-01.md index 04785cb680..ba2fe09cb0 100644 --- a/blog/fine-grained-news-2024-01.md +++ b/blog/fine-grained-news-2024-01.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-02.md b/blog/fine-grained-news-2024-02.md index 4da5ed47c8..bd340b5696 100644 --- a/blog/fine-grained-news-2024-02.md +++ b/blog/fine-grained-news-2024-02.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-03.md b/blog/fine-grained-news-2024-03.md index 33157ec5dc..b09e3e8880 100644 --- a/blog/fine-grained-news-2024-03.md +++ b/blog/fine-grained-news-2024-03.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-04.md b/blog/fine-grained-news-2024-04.md index 41c1d68b6e..54da7fe836 100644 --- a/blog/fine-grained-news-2024-04.md +++ b/blog/fine-grained-news-2024-04.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-05.md b/blog/fine-grained-news-2024-05.md index 4e709a7157..f5be897e06 100644 --- a/blog/fine-grained-news-2024-05.md +++ b/blog/fine-grained-news-2024-05.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-06.md b/blog/fine-grained-news-2024-06.md index 93f2888802..83a8466a94 100644 --- a/blog/fine-grained-news-2024-06.md +++ b/blog/fine-grained-news-2024-06.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-07.md b/blog/fine-grained-news-2024-07.md index cbd15a5a24..50018704b6 100644 --- a/blog/fine-grained-news-2024-07.md +++ b/blog/fine-grained-news-2024-07.md @@ -7,6 +7,7 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-08.md b/blog/fine-grained-news-2024-08.md index e785d51999..caaaf706ba 100644 --- a/blog/fine-grained-news-2024-08.md +++ b/blog/fine-grained-news-2024-08.md @@ -7,6 +7,7 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-09.md b/blog/fine-grained-news-2024-09.md index ed999f6961..541ed4cd00 100644 --- a/blog/fine-grained-news-2024-09.md +++ b/blog/fine-grained-news-2024-09.md @@ -7,6 +7,7 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News Welcome to the September edition of Fine-Grained News! As we transition into the fall season, we’re excited to bring you the latest updates on the progress of OpenFGA. diff --git a/blog/fine-grained-news-2024-10.md b/blog/fine-grained-news-2024-10.md index c89d94a242..e642c08595 100644 --- a/blog/fine-grained-news-2024-10.md +++ b/blog/fine-grained-news-2024-10.md @@ -7,6 +7,7 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News - October 2024 @@ -62,4 +63,4 @@ Check out our roadmap to see what’s in the works. Feature requests and ideas c ## See you Next Month -Fine-Grained News is published every month. If you have any feedback, want to share your OpenFGA story, or have a noteworthy update, please let us know on any of our [community channels](https://openfga.dev/community) or at [community@openfga.dev](mailto:community@openfga.dev). \ No newline at end of file +Fine-Grained News is published every month. If you have any feedback, want to share your OpenFGA story, or have a noteworthy update, please let us know on any of our [community channels](https://openfga.dev/community) or at [community@openfga.dev](mailto:community@openfga.dev). diff --git a/blog/fine-grained-news-2024-11.md b/blog/fine-grained-news-2024-11.md index b3c4df1631..83925bbccc 100644 --- a/blog/fine-grained-news-2024-11.md +++ b/blog/fine-grained-news-2024-11.md @@ -7,6 +7,7 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News - November 2024 diff --git a/blog/fine-grained-news-2025-01.md b/blog/fine-grained-news-2025-01.md index 90586fe565..7ac991e87a 100644 --- a/blog/fine-grained-news-2025-01.md +++ b/blog/fine-grained-news-2025-01.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News - January 2025 diff --git a/blog/fine-grained-news-2025-02.md b/blog/fine-grained-news-2025-02.md index 6c625e5db9..99762cf2c4 100644 --- a/blog/fine-grained-news-2025-02.md +++ b/blog/fine-grained-news-2025-02.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News - February 2025 diff --git a/blog/fine-grained-news-2025-09.md b/blog/fine-grained-news-2025-09.md index acd3639e7d..bfc83d913f 100644 --- a/blog/fine-grained-news-2025-09.md +++ b/blog/fine-grained-news-2025-09.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine Grained News - September 2025 diff --git a/blog/fine-grained-news-2025-10.md b/blog/fine-grained-news-2025-10.md index af428b8425..fa6b6dc61d 100644 --- a/blog/fine-grained-news-2025-10.md +++ b/blog/fine-grained-news-2025-10.md @@ -7,6 +7,7 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false +unlisted: true --- # Fine-Grained News - October 2025 diff --git a/docs/content/authorization-concepts.mdx b/docs/content/authorization-concepts.mdx index c1c271a786..9b3c344376 100644 --- a/docs/content/authorization-concepts.mdx +++ b/docs/content/authorization-concepts.mdx @@ -25,7 +25,7 @@ export const faqJsonLd = { name: 'What is Fine-Grained Authorization?', acceptedAnswer: { '@type': 'Answer', - text: 'Fine-Grained Authorization (FGA) implies the ability to grant specific users permission to perform certain actions in specific resources. Well-designed FGA systems allow you to manage permissions for millions of objects and users, with permissions that can change rapidly. A notable example is Google Drive, where access can be granted to documents or folders, to individual users or groups.', + text: 'Fine-Grained Authorization (FGA) means deciding access at the level of the individual resource and action — "Alice can edit document-42", not just "Alice is an editor". Well-designed FGA systems handle millions of objects and users with permissions that change rapidly, like Google Drive\'s per-document and per-folder sharing.', }, }, { @@ -33,7 +33,7 @@ export const faqJsonLd = { name: 'What is Role-Based Access Control?', acceptedAnswer: { '@type': 'Answer', - text: 'In Role-Based Access Control (RBAC), permissions are assigned to users based on their role in a system. For example, a user needs the editor role to edit content. RBAC systems enable you to define users, groups, roles, and permissions, then store them in a centralized location that applications access to make authorization decisions.', + text: 'In Role-Based Access Control (RBAC), permissions are assigned to users based on roles like editor or admin. RBAC fits flat, single-tenant access models but breaks down with hierarchy, sharing, or multi-tenancy.', }, }, { @@ -41,7 +41,7 @@ export const faqJsonLd = { name: 'What is Attribute-Based Access Control?', acceptedAnswer: { '@type': 'Answer', - text: 'In Attribute-Based Access Control (ABAC), permissions are granted based on a set of attributes that a user or resource possesses. For example, a user assigned both marketing and manager attributes is entitled to publish and delete posts that have a marketing attribute. Applications implementing ABAC retrieve information from multiple data sources to make authorization decisions.', + text: 'In Attribute-Based Access Control (ABAC), permissions are granted based on attributes of the user, resource, or request — for example, a user with marketing and manager attributes can publish marketing posts. ABAC implementations typically pull attributes from multiple sources at decision time.', }, }, { @@ -49,7 +49,7 @@ export const faqJsonLd = { name: 'What is Policy-Based Access Control?', acceptedAnswer: { '@type': 'Answer', - text: "Policy-Based Access Control (PBAC) is the ability to manage authorization policies in a centralized way that's external to the application code. Most implementations of ABAC are also PBAC.", + text: "Policy-Based Access Control (PBAC) manages authorization policies centrally, external to application code. Most ABAC implementations are also PBAC. OpenFGA's model DSL is itself a policy: committed to Git, reviewed via PR, deployed like any other code.", }, }, { @@ -57,7 +57,7 @@ export const faqJsonLd = { name: 'What is Relationship-Based Access Control?', acceptedAnswer: { '@type': 'Answer', - text: "Relationship-Based Access Control (ReBAC) enables user access rules to be conditional on relations that a given user has with a given object and that object's relationship with other objects. For example, a user can view a document if the user has access to the document's parent folder. ReBAC is a superset of RBAC and can natively solve for ABAC when attributes are expressed as relationships.", + text: 'Relationship-Based Access Control (ReBAC) makes access rules conditional on relationships between users and objects, and between objects themselves — "a user can view a document if they have access to its parent folder". ReBAC is a superset of RBAC and natively covers ABAC scenarios when attributes are expressed as relationships.', }, }, { @@ -65,7 +65,7 @@ export const faqJsonLd = { name: 'What is Zanzibar?', acceptedAnswer: { '@type': 'Answer', - text: "Zanzibar is Google's global authorization system across Google's product suite. It's based on ReBAC and uses object-relation-user tuples to store relationship data, then checks those relations for a match between a user and an object. ReBAC systems based on Zanzibar store authorization data in a centralized database that applications query via API. OpenFGA is an example of a Zanzibar-based authorization system.", + text: "Zanzibar is Google's global authorization system, used by Drive, YouTube, Calendar, and Cloud. It stores object-relation-user tuples and answers checks and reverse queries against the resulting graph. OpenFGA implements the Zanzibar model on your existing databases.", }, }, ], From ca5fc5823c15dedc699112a78a39fc00076812d7 Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 17:37:01 -0300 Subject: [PATCH 06/13] feat: added Sitewide BreadcrumbList JSON-LD --- src/theme/Root.tsx | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index f0d9ca6c7c..a3e2efd453 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -1,18 +1,108 @@ import React from 'react'; import Head from '@docusaurus/Head'; +import { useLocation } from '@docusaurus/router'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; interface RootProps { children: React.ReactNode; } +const SLUG_OVERRIDES: Record = { + docs: 'Docs', + learn: 'Learn', + rebac: 'ReBAC', + rbac: 'RBAC', + abac: 'ABAC', + pbac: 'PBAC', + acl: 'ACL', + acls: 'ACLs', + fga: 'FGA', + api: 'API', + apis: 'APIs', + cli: 'CLI', + sdk: 'SDK', + sdks: 'SDKs', + faq: 'FAQ', + url: 'URL', + urls: 'URLs', + http: 'HTTP', + https: 'HTTPS', + json: 'JSON', + yaml: 'YAML', + dsl: 'DSL', + oss: 'OSS', + oidc: 'OIDC', + oauth: 'OAuth', + ai: 'AI', + llm: 'LLM', + rag: 'RAG', + mcp: 'MCP', + os: 'OS', + 'rbac-vs-rebac': 'RBAC vs. ReBAC', + 'abac-vs-rebac': 'ABAC vs. ReBAC', + 'fine-grained-authorization': 'Fine-Grained Authorization', + 'policy-engine': 'Policy Engines vs. Relationship Engines', + zanzibar: 'Zanzibar', + openfga: 'OpenFGA', + 'authorization-concepts': 'Authorization Concepts', + 'getting-started': 'Getting Started', + 'configuration-language': 'Configuration Language', + modeling: 'Modeling', + concepts: 'Concepts', + 'best-practices': 'Best Practices', + 'use-cases': 'Use Cases', + interacting: 'Interacting', + 'ai-agent-authorization': 'AI Agent Authorization', + 'agents-as-principals': 'Agents as Principals', + 'rag-authorization': 'RAG Authorization', + 'mcp-authorization': 'MCP Authorization', +}; + +const SMALL_WORDS = new Set(['a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'in', 'of', 'on', 'or', 'the', 'to', 'vs']); + +function titleCase(segment: string): string { + const lower = segment.toLowerCase(); + if (SLUG_OVERRIDES[lower]) return SLUG_OVERRIDES[lower]; + const words = lower.replace(/[_]+/g, ' ').split('-').filter(Boolean); + return words + .map((w, i) => { + if (SLUG_OVERRIDES[w]) return SLUG_OVERRIDES[w]; + if (i > 0 && SMALL_WORDS.has(w)) return w; + return w[0].toUpperCase() + w.slice(1); + }) + .join(' '); +} + +function buildBreadcrumbJsonLd(pathname: string, siteUrl: string): string | null { + const clean = pathname.replace(/\/+$/, ''); + if (!clean || clean === '') return null; + const segments = clean.split('/').filter(Boolean); + if (segments.length < 2) return null; + const itemListElement = segments.map((seg, i) => ({ + '@type': 'ListItem', + position: i + 1, + name: titleCase(seg), + item: `${siteUrl}/${segments.slice(0, i + 1).join('/')}`, + })); + return JSON.stringify({ + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement, + }); +} + export default function Root({ children }: RootProps): JSX.Element { const { siteConfig } = useDocusaurusContext(); const { contentSecurityPolicy } = siteConfig.customFields; + const { pathname } = useLocation(); + const breadcrumbJsonLd = buildBreadcrumbJsonLd(pathname, siteConfig.url); return (

+ {breadcrumbJsonLd && ( + + )} {children}
From 50c810320ecb3e59bf5473a113fc6319fc39abee Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 17:50:08 -0300 Subject: [PATCH 07/13] fix: added blog pages back to the index --- blog/fine-grained-news-2023-12.md | 1 - blog/fine-grained-news-2024-01.md | 1 - blog/fine-grained-news-2024-02.md | 1 - blog/fine-grained-news-2024-03.md | 1 - blog/fine-grained-news-2024-04.md | 1 - blog/fine-grained-news-2024-05.md | 1 - blog/fine-grained-news-2024-06.md | 1 - blog/fine-grained-news-2024-07.md | 1 - blog/fine-grained-news-2024-08.md | 1 - blog/fine-grained-news-2024-09.md | 1 - blog/fine-grained-news-2024-10.md | 1 - blog/fine-grained-news-2024-11.md | 1 - blog/fine-grained-news-2025-01.md | 1 - blog/fine-grained-news-2025-02.md | 1 - blog/fine-grained-news-2025-09.md | 1 - blog/fine-grained-news-2025-10.md | 1 - src/theme/Root.tsx | 4 ++++ 17 files changed, 4 insertions(+), 16 deletions(-) diff --git a/blog/fine-grained-news-2023-12.md b/blog/fine-grained-news-2023-12.md index 4dc3a6c55d..f80cf78670 100644 --- a/blog/fine-grained-news-2023-12.md +++ b/blog/fine-grained-news-2023-12.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-01.md b/blog/fine-grained-news-2024-01.md index ba2fe09cb0..04785cb680 100644 --- a/blog/fine-grained-news-2024-01.md +++ b/blog/fine-grained-news-2024-01.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-02.md b/blog/fine-grained-news-2024-02.md index bd340b5696..4da5ed47c8 100644 --- a/blog/fine-grained-news-2024-02.md +++ b/blog/fine-grained-news-2024-02.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-03.md b/blog/fine-grained-news-2024-03.md index b09e3e8880..33157ec5dc 100644 --- a/blog/fine-grained-news-2024-03.md +++ b/blog/fine-grained-news-2024-03.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-04.md b/blog/fine-grained-news-2024-04.md index 54da7fe836..41c1d68b6e 100644 --- a/blog/fine-grained-news-2024-04.md +++ b/blog/fine-grained-news-2024-04.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-05.md b/blog/fine-grained-news-2024-05.md index f5be897e06..4e709a7157 100644 --- a/blog/fine-grained-news-2024-05.md +++ b/blog/fine-grained-news-2024-05.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-06.md b/blog/fine-grained-news-2024-06.md index 83a8466a94..93f2888802 100644 --- a/blog/fine-grained-news-2024-06.md +++ b/blog/fine-grained-news-2024-06.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-07.md b/blog/fine-grained-news-2024-07.md index 50018704b6..cbd15a5a24 100644 --- a/blog/fine-grained-news-2024-07.md +++ b/blog/fine-grained-news-2024-07.md @@ -7,7 +7,6 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-08.md b/blog/fine-grained-news-2024-08.md index caaaf706ba..e785d51999 100644 --- a/blog/fine-grained-news-2024-08.md +++ b/blog/fine-grained-news-2024-08.md @@ -7,7 +7,6 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News diff --git a/blog/fine-grained-news-2024-09.md b/blog/fine-grained-news-2024-09.md index 541ed4cd00..ed999f6961 100644 --- a/blog/fine-grained-news-2024-09.md +++ b/blog/fine-grained-news-2024-09.md @@ -7,7 +7,6 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News Welcome to the September edition of Fine-Grained News! As we transition into the fall season, we’re excited to bring you the latest updates on the progress of OpenFGA. diff --git a/blog/fine-grained-news-2024-10.md b/blog/fine-grained-news-2024-10.md index e642c08595..ce9e85b6f8 100644 --- a/blog/fine-grained-news-2024-10.md +++ b/blog/fine-grained-news-2024-10.md @@ -7,7 +7,6 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News - October 2024 diff --git a/blog/fine-grained-news-2024-11.md b/blog/fine-grained-news-2024-11.md index 83925bbccc..b3c4df1631 100644 --- a/blog/fine-grained-news-2024-11.md +++ b/blog/fine-grained-news-2024-11.md @@ -7,7 +7,6 @@ authors: hello-caleb tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News - November 2024 diff --git a/blog/fine-grained-news-2025-01.md b/blog/fine-grained-news-2025-01.md index 7ac991e87a..90586fe565 100644 --- a/blog/fine-grained-news-2025-01.md +++ b/blog/fine-grained-news-2025-01.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News - January 2025 diff --git a/blog/fine-grained-news-2025-02.md b/blog/fine-grained-news-2025-02.md index 99762cf2c4..6c625e5db9 100644 --- a/blog/fine-grained-news-2025-02.md +++ b/blog/fine-grained-news-2025-02.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News - February 2025 diff --git a/blog/fine-grained-news-2025-09.md b/blog/fine-grained-news-2025-09.md index bfc83d913f..acd3639e7d 100644 --- a/blog/fine-grained-news-2025-09.md +++ b/blog/fine-grained-news-2025-09.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine Grained News - September 2025 diff --git a/blog/fine-grained-news-2025-10.md b/blog/fine-grained-news-2025-10.md index fa6b6dc61d..af428b8425 100644 --- a/blog/fine-grained-news-2025-10.md +++ b/blog/fine-grained-news-2025-10.md @@ -7,7 +7,6 @@ authors: aaguiar tags: [newsletter] image: https://openfga.dev/img/og-rich-embed.png hide_table_of_contents: false -unlisted: true --- # Fine-Grained News - October 2025 diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index a3e2efd453..165123cb8b 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -96,10 +96,14 @@ export default function Root({ children }: RootProps): JSX.Element { const { contentSecurityPolicy } = siteConfig.customFields; const { pathname } = useLocation(); const breadcrumbJsonLd = buildBreadcrumbJsonLd(pathname, siteConfig.url); + // Fine-Grained News digests are time-sensitive newsletters — keep them visible in the blog + // index/RSS but noindex them so they don't compete with evergreen pages in search results. + const noindex = /^\/blog\/fine-grained-news-/.test(pathname); return (
+ {noindex && } {breadcrumbJsonLd && ( )} From 03ca37442e74e1759bf7c73c680cbfdf8e7da30b Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 18:04:17 -0300 Subject: [PATCH 08/13] fix: removed LGPL dependency and addressed copilot comments --- .../use-cases/microservices-authorization.mdx | 2 +- docs/content/use-cases/multi-tenant-saas.mdx | 2 +- docs/content/use-cases/rag-authorization.mdx | 2 +- docusaurus.config.js | 53 --- package-lock.json | 377 +----------------- package.json | 1 - src/theme/Root.tsx | 45 ++- 7 files changed, 49 insertions(+), 433 deletions(-) diff --git a/docs/content/use-cases/microservices-authorization.mdx b/docs/content/use-cases/microservices-authorization.mdx index 0f104c7fbf..d930caa915 100644 --- a/docs/content/use-cases/microservices-authorization.mdx +++ b/docs/content/use-cases/microservices-authorization.mdx @@ -25,7 +25,7 @@ Microservices repeat the same pattern: each service ends up with its own roles t ## Simplified model -```dsl +```dsl.openfga type user type team diff --git a/docs/content/use-cases/multi-tenant-saas.mdx b/docs/content/use-cases/multi-tenant-saas.mdx index 2e0a6c2164..2ac48531b6 100644 --- a/docs/content/use-cases/multi-tenant-saas.mdx +++ b/docs/content/use-cases/multi-tenant-saas.mdx @@ -29,7 +29,7 @@ This gives you: Most SaaS products eventually need tenant-defined roles — *"Acme wants a `billing-manager` role; Globex wants a `compliance-reviewer` role"* — without redeploying the model. The pattern is to make `role` a first-class type whose `assignee` relation can be a user, another role, or a group, and then bind those roles to organization-level permissions: -```dsl +```dsl.openfga type user type group diff --git a/docs/content/use-cases/rag-authorization.mdx b/docs/content/use-cases/rag-authorization.mdx index af500c2a2a..5f1cb0f5c0 100644 --- a/docs/content/use-cases/rag-authorization.mdx +++ b/docs/content/use-cases/rag-authorization.mdx @@ -22,7 +22,7 @@ The same model and prompt now produce different — and correct — answers per ## Simplified model -```dsl +```dsl.openfga type user type folder diff --git a/docusaurus.config.js b/docusaurus.config.js index b1b147c09f..1b49edaa86 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -177,7 +177,6 @@ import dev.openfga.sdk.api.configuration.ClientConfiguration;`, }, }, ], - '@stackql/docusaurus-plugin-structured-data', ], presets: [ @@ -222,58 +221,6 @@ import dev.openfga.sdk.api.configuration.ClientConfiguration;`, { name: 'twitter:site', content: '@openfga' }, { name: 'twitter:image', content: 'https://openfga.dev/img/og-rich-embed.png' }, ], - structuredData: { - excludedRoutes: ['/blog/authors', '/blog/archive', '/blog/tags', '/api/service'], - verbose: false, - featuredImageDimensions: { width: 1200, height: 627 }, - authors: { - aaguiar: { authorId: 'aaguiar', url: 'https://github.com/aaguiarz', imageUrl: 'https://openfga.dev/img/blog/authors/andres.jpg', sameAs: [] }, - eharris: { authorId: 'eharris', url: 'https://github.com/ewanharris', imageUrl: 'https://openfga.dev/img/blog/authors/ewan.jpg', sameAs: [] }, - jakub: { authorId: 'jakub', url: 'https://github.com/curfew-marathon', imageUrl: 'https://openfga.dev/img/blog/authors/jakub.jpg', sameAs: [] }, - miparnisari: { authorId: 'miparnisari', url: 'https://github.com/miparnisari', imageUrl: 'https://openfga.dev/img/blog/authors/miparnisari.jpg', sameAs: [] }, - 'hello-caleb': { authorId: 'hello-caleb', url: 'https://github.com/hello-caleb', imageUrl: 'https://openfga.dev/img/blog/authors/caleb.jpg', sameAs: [] }, - tylernix: { authorId: 'tylernix', url: 'https://github.com/tylernix', imageUrl: 'https://openfga.dev/img/blog/authors/tyler.jpg', sameAs: [] }, - }, - organization: { - name: 'OpenFGA', - url: 'https://openfga.dev', - logo: 'https://openfga.dev/img/openfga_logo.png', - sameAs: [ - 'https://github.com/openfga', - 'https://twitter.com/openfga', - 'https://hachyderm.io/@openfga', - 'https://openfga.dev/community', - ], - parentOrganization: { - '@type': 'Organization', - name: 'Cloud Native Computing Foundation', - url: 'https://www.cncf.io', - parentOrganization: { - '@type': 'Organization', - name: 'The Linux Foundation', - url: 'https://www.linuxfoundation.org', - }, - }, - }, - website: { - inLanguage: 'en-US', - potentialAction: { - '@type': 'SearchAction', - target: { - '@type': 'EntryPoint', - urlTemplate: 'https://openfga.dev/search?q={search_term_string}', - }, - 'query-input': 'required name=search_term_string', - }, - }, - webpage: { - inLanguage: 'en-US', - datePublished: '2022-09-13', - }, - breadcrumbLabelMap: { - docs: 'Docs', - }, - }, colorMode: { defaultMode: 'dark', disableSwitch: true, diff --git a/package-lock.json b/package-lock.json index f2c8f26f56..5ec8844b5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "@openfga/frontend-utils": "^0.2.0-beta.11", "@openfga/sdk": "^0.9.5", "@openfga/syntax-transformer": "^0.2.1", - "@stackql/docusaurus-plugin-structured-data": "^1.3.2", "assert-never": "1.4.0", "clsx": "2.1.1", "path-browserify": "1.0.1", @@ -5449,15 +5448,6 @@ "micromark-util-symbol": "^1.0.1" } }, - "node_modules/@stackql/docusaurus-plugin-structured-data": { - "version": "1.3.2", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/@stackql/docusaurus-plugin-structured-data/-/docusaurus-plugin-structured-data-1.3.2.tgz", - "integrity": "sha512-79wy4X8Kt4ZKJ1jlldyslWPdntiXv8r8pFZWOmUAYVa+Ix1QA2toOrOBBy1YUGwRSROxV2OyJQs4w90FgQNzzQ==", - "license": "MIT", - "dependencies": { - "jsdom": "^21.0.0" - } - }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -6386,15 +6376,6 @@ "node": ">=14.16" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/@tootallnate/once/-/once-2.0.1.tgz", - "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -7270,13 +7251,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "license": "BSD-3-Clause" - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7311,16 +7285,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "license": "MIT", - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, "node_modules/acorn-import-phases": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", @@ -9523,18 +9487,6 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, - "node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", - "license": "MIT", - "dependencies": { - "rrweb-cssom": "^0.6.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -9551,29 +9503,6 @@ "node": ">= 14" } }, - "node_modules/data-urls": { - "version": "4.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/data-urls/-/data-urls-4.0.0.tgz", - "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", - "license": "MIT", - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/data-urls/node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -9651,12 +9580,6 @@ } } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, "node_modules/decode-named-character-reference": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", @@ -9962,19 +9885,6 @@ ], "license": "BSD-2-Clause" }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "license": "MIT", - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", @@ -10432,6 +10342,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", @@ -10453,6 +10364,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "license": "BSD-3-Clause", "optional": true, "engines": { @@ -12268,31 +12180,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-encoding-sniffer/node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -13254,12 +13141,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -13609,133 +13490,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdom": { - "version": "21.1.2", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/jsdom/-/jsdom-21.1.2.tgz", - "integrity": "sha512-sCpFmK2jv+1sjff4u7fzft+pUh2KSUbUrEHYHyfSIbGTIcmnjyp83qg6qLwdJ/I3LpTXx33ACxeRL7Lsyc6lGQ==", - "license": "MIT", - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.2", - "acorn-globals": "^7.0.0", - "cssstyle": "^3.0.0", - "data-urls": "^4.0.0", - "decimal.js": "^10.4.3", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.4", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.1", - "ws": "^8.13.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/jsdom/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jsdom/node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.20.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -16920,12 +16674,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -19306,18 +19054,6 @@ "dev": true, "license": "MIT" }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -20473,12 +20209,6 @@ "node": ">=0.10.0" } }, - "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", - "license": "MIT" - }, "node_modules/rtlcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", @@ -20681,18 +20411,6 @@ "node": ">=11.0.0" } }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -21889,12 +21607,6 @@ "react-dom": ">=16.8.0 <20" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "license": "MIT" - }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -22148,42 +21860,6 @@ "node": ">=6" } }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", @@ -22984,18 +22660,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "license": "MIT", - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", @@ -23044,15 +22708,6 @@ "license": "MIT", "optional": true }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, "node_modules/webpack": { "version": "5.105.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", @@ -23419,19 +23074,6 @@ "node": ">=18" } }, - "node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", - "license": "MIT", - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -23731,15 +23373,6 @@ "xml-js": "bin/cli.js" } }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, "node_modules/xmlbuilder2": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.0.tgz", @@ -23756,12 +23389,6 @@ "node": ">=20.0" } }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://a0us.jfrog.io/artifactory/api/npm/npm/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "license": "MIT" - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index bac79fe112..ed699d90a9 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@openfga/frontend-utils": "^0.2.0-beta.11", "@openfga/sdk": "^0.9.5", "@openfga/syntax-transformer": "^0.2.1", - "@stackql/docusaurus-plugin-structured-data": "^1.3.2", "assert-never": "1.4.0", "clsx": "2.1.1", "path-browserify": "1.0.1", diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index 165123cb8b..52a7a474eb 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -88,9 +88,49 @@ function buildBreadcrumbJsonLd(pathname: string, siteUrl: string): string | null '@context': 'https://schema.org', '@type': 'BreadcrumbList', itemListElement, - }); + }).replace(/ {noindex && } + {isHome && } + {isHome && } {breadcrumbJsonLd && ( )} From 64f78c4bddd3badd379c0b626b7d7bd3757cfcca Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 18:13:45 -0300 Subject: [PATCH 09/13] fix: prettier warning --- src/theme/Root.tsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index 52a7a474eb..bb9c1e180a 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -58,7 +58,23 @@ const SLUG_OVERRIDES: Record = { 'mcp-authorization': 'MCP Authorization', }; -const SMALL_WORDS = new Set(['a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'in', 'of', 'on', 'or', 'the', 'to', 'vs']); +const SMALL_WORDS = new Set([ + 'a', + 'an', + 'and', + 'as', + 'at', + 'but', + 'by', + 'for', + 'in', + 'of', + 'on', + 'or', + 'the', + 'to', + 'vs', +]); function titleCase(segment: string): string { const lower = segment.toLowerCase(); @@ -147,9 +163,7 @@ export default function Root({ children }: RootProps): JSX.Element { {noindex && } {isHome && } {isHome && } - {breadcrumbJsonLd && ( - - )} + {breadcrumbJsonLd && } {children}
From 2aad0d61c561416c223b4965d850e4a872e88b35 Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 18:55:19 -0300 Subject: [PATCH 10/13] fix: fixed openfga logo URL and llms.txt generation --- scripts/generate-llms-txt.mjs | 87 +++++++++++++++++++++++++++++++---- src/theme/Root.tsx | 2 +- static/llms.txt | 28 +++++------ 3 files changed, 92 insertions(+), 25 deletions(-) diff --git a/scripts/generate-llms-txt.mjs b/scripts/generate-llms-txt.mjs index 31b8dd1f7c..2e8f603588 100644 --- a/scripts/generate-llms-txt.mjs +++ b/scripts/generate-llms-txt.mjs @@ -6,6 +6,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const SIDEBARS_FILE = path.join(__dirname, '../docs/sidebars.js'); +const DOCS_CONTENT_DIR = path.join(__dirname, '../docs/content'); const OUTPUT_FILE = path.join(__dirname, '../static/llms.txt'); const BASE_URL = 'https://openfga.dev/docs'; @@ -27,14 +28,77 @@ async function loadSidebars() { return sandbox.module.exports; } +/** + * Recursively lists markdown documents in a directory. + * @param {string} dir + * @returns {Promise} + */ +async function listDocFiles(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }); + const files = await Promise.all(entries.map((entry) => { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + return listDocFiles(fullPath); + } + if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { + return [fullPath]; + } + return []; + })); + + return files.flat(); +} + +/** + * Reads the frontmatter slug from a markdown document. + * @param {string} content + * @returns {string | null} + */ +function readSlug(content) { + const frontmatter = /^---\n([\s\S]*?)\n---/.exec(content); + if (!frontmatter) { + return null; + } + + const slug = /^slug:\s*(.+)$/m.exec(frontmatter[1]); + if (!slug) { + return null; + } + + return slug[1].trim().replace(/^['"]|['"]$/g, ''); +} + +/** + * Creates a map from Docusaurus doc IDs to published URL paths. + * @returns {Promise>} + */ +async function loadDocUrlMap() { + const docFiles = await listDocFiles(DOCS_CONTENT_DIR); + const urls = new Map(); + + for (const file of docFiles) { + const relativePath = path.relative(path.join(__dirname, '../docs'), file); + const id = relativePath.replace(/\.(md|mdx)$/, '').split(path.sep).join('/'); + const content = await fs.readFile(file, 'utf8'); + const slug = readSlug(content); + + if (slug) { + urls.set(id, slug.startsWith('/') ? slug : `/${slug}`); + } + } + + return urls; +} + /** * Converts a doc ID to a URL path * @param {string} id - Document ID like 'content/intro' + * @param {Map} docUrlMap * @returns {string} - URL path like '/fga' */ -function docIdToUrl(id) { - if (id === 'content/intro') { - return '/fga'; +function docIdToUrl(id, docUrlMap) { + if (docUrlMap.has(id)) { + return docUrlMap.get(id); } return '/' + id.replace('content/', ''); } @@ -42,16 +106,17 @@ function docIdToUrl(id) { /** * Processes sidebar items recursively and generates markdown content * @param {Array} items - Sidebar items + * @param {Map} docUrlMap * @param {number} depth - Current nesting depth * @returns {string} - Markdown content */ -function processItems(items, depth = 0) { +function processItems(items, docUrlMap, depth = 0) { let content = ''; const indent = ' '.repeat(depth); for (const item of items) { if (item.type === 'doc') { - const url = `${BASE_URL}${docIdToUrl(item.id)}`; + const url = `${BASE_URL}${docIdToUrl(item.id, docUrlMap)}`; content += `${indent}- [${item.label}](${url})`; if (item.description) { content += ` - ${item.description}`; @@ -60,11 +125,11 @@ function processItems(items, depth = 0) { } else if (item.type === 'category') { content += `${indent}#### ${item.label}\n`; if (item.link && item.link.type === 'doc') { - const url = `${BASE_URL}${docIdToUrl(item.link.id)}`; + const url = `${BASE_URL}${docIdToUrl(item.link.id, docUrlMap)}`; content += `${indent}- [Overview](${url}) - ${item.label} overview\n`; } if (item.items && item.items.length > 0) { - content += processItems(item.items, depth + 1); + content += processItems(item.items, docUrlMap, depth + 1); } content += '\n'; } @@ -76,9 +141,10 @@ function processItems(items, depth = 0) { /** * Generates the complete llms.txt content * @param {Object} sidebars - Parsed sidebars configuration + * @param {Map} docUrlMap * @returns {string} - Complete llms.txt content */ -function generateLlmsTxtContent(sidebars) { +function generateLlmsTxtContent(sidebars, docUrlMap) { const header = `# OpenFGA Documentation ## Project Overview @@ -111,7 +177,7 @@ OpenFGA implements authorization through: let content = header; if (sidebars.docs) { - content += processItems(sidebars.docs); + content += processItems(sidebars.docs, docUrlMap); } const footer = ` @@ -151,9 +217,10 @@ async function generateLlmsTxt() { try { console.log('Loading sidebars configuration...'); const sidebars = await loadSidebars(); + const docUrlMap = await loadDocUrlMap(); console.log('Generating llms.txt content...'); - const content = generateLlmsTxtContent(sidebars); + const content = generateLlmsTxtContent(sidebars, docUrlMap); console.log('Writing llms.txt file...'); await fs.writeFile(OUTPUT_FILE, content, 'utf8'); diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index bb9c1e180a..885f154e87 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -112,7 +112,7 @@ const ORGANIZATION_JSON_LD = JSON.stringify({ '@type': 'Organization', name: 'OpenFGA', url: 'https://openfga.dev', - logo: 'https://openfga.dev/img/openfga_logo.png', + logo: 'https://openfga.dev/img/openfga_logo.svg', sameAs: [ 'https://github.com/openfga', 'https://twitter.com/openfga', diff --git a/static/llms.txt b/static/llms.txt index 841b69d790..9056c79008 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -31,13 +31,13 @@ OpenFGA implements authorization through: - [Configuration Language](https://openfga.dev/docs/configuration-language) - [Community](https://openfga.dev/docs/community) #### Getting Started -- [Overview](https://openfga.dev/docs/getting-started/overview) - Getting Started overview +- [Overview](https://openfga.dev/docs/getting-started) - Getting Started overview #### Setup OpenFGA - [Overview](https://openfga.dev/docs/getting-started/setup-openfga/overview) - Setup OpenFGA overview - [Configure OpenFGA](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga) - [Configuration Options](https://openfga.dev/docs/getting-started/setup-openfga/configuration) - - [🐳 Docker](https://openfga.dev/docs/getting-started/setup-openfga/docker-setup) - - [☸️ Kubernetes](https://openfga.dev/docs/getting-started/setup-openfga/kubernetes-setup) + - [🐳 Docker](https://openfga.dev/docs/getting-started/setup-openfga/docker) + - [☸️ Kubernetes](https://openfga.dev/docs/getting-started/setup-openfga/kubernetes) - [🛡️Access Control](https://openfga.dev/docs/getting-started/setup-openfga/access-control) - [Playground](https://openfga.dev/docs/getting-started/setup-openfga/playground) - [Reporting Runtime Issues](https://openfga.dev/docs/getting-started/setup-openfga/reporting-runtime-issues) @@ -57,7 +57,7 @@ OpenFGA implements authorization through: - [Configure SDK Client Telemetry](https://openfga.dev/docs/getting-started/configure-telemetry) #### Modeling Guides -- [Overview](https://openfga.dev/docs/modeling/overview) - Modeling Guides overview +- [Overview](https://openfga.dev/docs/modeling) - Modeling Guides overview - [Get Started with Modeling](https://openfga.dev/docs/modeling/getting-started) - [Direct Access](https://openfga.dev/docs/modeling/direct-access) - [User Groups](https://openfga.dev/docs/modeling/user-groups) @@ -71,18 +71,18 @@ OpenFGA implements authorization through: - [Token claims as Contextual Tuples](https://openfga.dev/docs/modeling/token-claims-contextual-tuples) - [Contextual and Time-Based Authorization](https://openfga.dev/docs/modeling/contextual-time-based-authorization) - [Authorization Through Organization Context](https://openfga.dev/docs/modeling/organization-context-authorization) - - [Testing Models](https://openfga.dev/docs/modeling/testing-models) + - [Testing Models](https://openfga.dev/docs/modeling/testing) - [Store File Format](https://openfga.dev/docs/modeling/store-file-format) - [Modular Models](https://openfga.dev/docs/modeling/modular-models) #### Building Blocks - - [Overview](https://openfga.dev/docs/modeling/building-blocks/overview) - Building Blocks overview + - [Overview](https://openfga.dev/docs/modeling/building-blocks) - Building Blocks overview - [Direct Relationships](https://openfga.dev/docs/modeling/building-blocks/direct-relationships) - [Concentric Relationships](https://openfga.dev/docs/modeling/building-blocks/concentric-relationships) - [Object to Object Relationships](https://openfga.dev/docs/modeling/building-blocks/object-to-object-relationships) - [Usersets](https://openfga.dev/docs/modeling/building-blocks/usersets) #### Advanced Use-Cases - - [Overview](https://openfga.dev/docs/modeling/advanced/overview) - Advanced Use-Cases overview + - [Overview](https://openfga.dev/docs/modeling/advanced) - Advanced Use-Cases overview - [Google Drive](https://openfga.dev/docs/modeling/advanced/gdrive) - [GitHub](https://openfga.dev/docs/modeling/advanced/github) - [Slack](https://openfga.dev/docs/modeling/advanced/slack) @@ -90,20 +90,20 @@ OpenFGA implements authorization through: - [Entitlements](https://openfga.dev/docs/modeling/advanced/entitlements) #### Migrations - - [Overview](https://openfga.dev/docs/modeling/migrating/overview) - Migrations overview + - [Overview](https://openfga.dev/docs/modeling/migrating) - Migrations overview - [Migrating Relations](https://openfga.dev/docs/modeling/migrating/migrating-relations) - [Migrating Models](https://openfga.dev/docs/modeling/migrating/migrating-models) #### Authorization for Agents -- [Overview](https://openfga.dev/docs/modeling/agents/overview) - Authorization for Agents overview +- [Overview](https://openfga.dev/docs/modeling/agents) - Authorization for Agents overview - [Modeling Agents as Principals](https://openfga.dev/docs/modeling/agents/agents-as-principals) - [RAG Authorization](https://openfga.dev/docs/modeling/agents/rag-authorization) - [Authorization for MCP Servers](https://openfga.dev/docs/modeling/agents/mcp-authorization) - [Task-Based Authorization](https://openfga.dev/docs/modeling/agents/task-based-authorization) #### Interacting with the API -- [Overview](https://openfga.dev/docs/interacting/overview) - Interacting with the API overview +- [Overview](https://openfga.dev/docs/interacting) - Interacting with the API overview - [Manage User Access](https://openfga.dev/docs/interacting/managing-user-access) - [Manage Group Access](https://openfga.dev/docs/interacting/managing-group-access) - [Manage Group Membership](https://openfga.dev/docs/interacting/managing-group-membership) @@ -116,7 +116,7 @@ OpenFGA implements authorization through: - [AuthZEN API](https://openfga.dev/docs/interacting/authzen) #### Best Practices -- [Overview](https://openfga.dev/docs/best-practices/overview) - Best Practices overview +- [Overview](https://openfga.dev/docs/best-practices) - Best Practices overview - [Adoption Patterns](https://openfga.dev/docs/best-practices/adoption-patterns) - [Authorization Model Design Principles](https://openfga.dev/docs/best-practices/modeling-design-principles) - [Modeling ABAC](https://openfga.dev/docs/best-practices/modeling-abac) @@ -125,7 +125,7 @@ OpenFGA implements authorization through: - [Running OpenFGA in Production](https://openfga.dev/docs/best-practices/running-in-production) #### Industries -- [Overview](https://openfga.dev/docs/industries/overview) - Industries overview +- [Overview](https://openfga.dev/docs/industries) - Industries overview - [Healthcare](https://openfga.dev/docs/industries/healthcare) - [Banking](https://openfga.dev/docs/industries/banking) - [E-commerce](https://openfga.dev/docs/industries/ecommerce) @@ -135,7 +135,7 @@ OpenFGA implements authorization through: - [Applicant Tracking](https://openfga.dev/docs/industries/applicant-tracking-system) #### Use Cases -- [Overview](https://openfga.dev/docs/use-cases/overview) - Use Cases overview +- [Overview](https://openfga.dev/docs/use-cases) - Use Cases overview - [AI Agent Authorization](https://openfga.dev/docs/use-cases/ai-agent-authorization) - [RAG Authorization](https://openfga.dev/docs/use-cases/rag-authorization) - [MCP Server Authorization](https://openfga.dev/docs/use-cases/mcp-server-authorization) @@ -143,7 +143,7 @@ OpenFGA implements authorization through: - [Microservices Authorization](https://openfga.dev/docs/use-cases/microservices-authorization) #### Learn -- [Overview](https://openfga.dev/docs/learn/overview) - Learn overview +- [Overview](https://openfga.dev/docs/learn) - Learn overview - [Zanzibar](https://openfga.dev/docs/learn/zanzibar) - [What is ReBAC?](https://openfga.dev/docs/learn/rebac) - [RBAC vs ReBAC](https://openfga.dev/docs/learn/rbac-vs-rebac) From b477abff5bb2a9cd9a1b8e755abcfb6c6dd5c045 Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 25 May 2026 19:31:53 -0300 Subject: [PATCH 11/13] fix: improvements in llms.txt and robots.txt --- docusaurus.config.js | 1 + scripts/generate-llms-txt.mjs | 17 ++++++++++++++++- static/llms.txt | 17 ++++++++++++++++- static/robots.txt | 12 ++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index 1b49edaa86..db1818a948 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -190,6 +190,7 @@ import dev.openfga.sdk.api.configuration.ClientConfiguration;`, routeBasePath: '/docs', exclude: ['**/README.md'], showLastUpdateAuthor: false, + showLastUpdateTime: true, editUrl: 'https://github.com/openfga/openfga.dev/edit/main/', }, blog: { diff --git a/scripts/generate-llms-txt.mjs b/scripts/generate-llms-txt.mjs index 2e8f603588..f63e3b9107 100644 --- a/scripts/generate-llms-txt.mjs +++ b/scripts/generate-llms-txt.mjs @@ -147,6 +147,8 @@ function processItems(items, docUrlMap, depth = 0) { function generateLlmsTxtContent(sidebars, docUrlMap) { const header = `# OpenFGA Documentation +> OpenFGA is a CNCF open source authorization system for fine-grained, relationship-based access control in modern applications. + ## Project Overview OpenFGA (Fine-Grained Authorization) is an open source authorization system based on Google's Zanzibar. It's a Cloud Native Computing Foundation (CNCF) project that provides scalable, fine-grained authorization for applications using Relationship-Based Access Control (ReBAC). @@ -170,7 +172,7 @@ OpenFGA implements authorization through: 4. **Stores** - Isolated authorization environments 5. **Conditional Tuples** - Attribute-based access control capabilities -## Complete Documentation Structure +## Docs `; @@ -181,6 +183,19 @@ OpenFGA implements authorization through: } const footer = ` +## API + +- [OpenFGA API Reference](https://openfga.dev/api/service) - HTTP API documentation for OpenFGA operations +- [Install SDK Client](https://openfga.dev/docs/getting-started/install-sdk) - Client SDK setup for supported languages +- [Use the FGA CLI](https://openfga.dev/docs/getting-started/cli) - Command line interface documentation + +## Optional + +- [Advanced Modeling](https://openfga.dev/docs/modeling/advanced) - Production-style modeling examples for common applications +- [Authorization for Agents](https://openfga.dev/docs/modeling/agents) - Modeling patterns for AI agents, RAG, and MCP servers +- [Use Cases](https://openfga.dev/docs/use-cases) - High-level authorization use cases and solution patterns +- [Best Practices](https://openfga.dev/docs/best-practices) - Guidance for adopting and operating OpenFGA + ## Supported Features - Multiple database backends (PostgreSQL, MySQL, SQLite) diff --git a/static/llms.txt b/static/llms.txt index 9056c79008..92ab0ccbc9 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -1,5 +1,7 @@ # OpenFGA Documentation +> OpenFGA is a CNCF open source authorization system for fine-grained, relationship-based access control in modern applications. + ## Project Overview OpenFGA (Fine-Grained Authorization) is an open source authorization system based on Google's Zanzibar. It's a Cloud Native Computing Foundation (CNCF) project that provides scalable, fine-grained authorization for applications using Relationship-Based Access Control (ReBAC). @@ -23,7 +25,7 @@ OpenFGA implements authorization through: 4. **Stores** - Isolated authorization environments 5. **Conditional Tuples** - Attribute-based access control capabilities -## Complete Documentation Structure +## Docs - [What is OpenFGA](https://openfga.dev/docs/fga) - [Authorization Concepts](https://openfga.dev/docs/authorization-concepts) @@ -152,6 +154,19 @@ OpenFGA implements authorization through: - [Policy vs Relationship Engines](https://openfga.dev/docs/learn/policy-engine) +## API + +- [OpenFGA API Reference](https://openfga.dev/api/service) - HTTP API documentation for OpenFGA operations +- [Install SDK Client](https://openfga.dev/docs/getting-started/install-sdk) - Client SDK setup for supported languages +- [Use the FGA CLI](https://openfga.dev/docs/getting-started/cli) - Command line interface documentation + +## Optional + +- [Advanced Modeling](https://openfga.dev/docs/modeling/advanced) - Production-style modeling examples for common applications +- [Authorization for Agents](https://openfga.dev/docs/modeling/agents) - Modeling patterns for AI agents, RAG, and MCP servers +- [Use Cases](https://openfga.dev/docs/use-cases) - High-level authorization use cases and solution patterns +- [Best Practices](https://openfga.dev/docs/best-practices) - Guidance for adopting and operating OpenFGA + ## Supported Features - Multiple database backends (PostgreSQL, MySQL, SQLite) diff --git a/static/robots.txt b/static/robots.txt index 5346c6ca6d..0bedf7da5b 100644 --- a/static/robots.txt +++ b/static/robots.txt @@ -1,4 +1,16 @@ User-agent: * Allow: / +User-agent: GPTBot +Allow: / + +User-agent: Google-Extended +Allow: / + +User-agent: PerplexityBot +Allow: / + +User-agent: anthropic-ai +Allow: / + Sitemap: https://openfga.dev/sitemap.xml From 0ed4f51895a15d2a9d5f87ade63027ab6d29a98d Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Fri, 29 May 2026 02:32:17 -0300 Subject: [PATCH 12/13] fix: addressed feedback and improved schema.org format --- docs/content/authorization-concepts.mdx | 4 +- src/pages/index.tsx | 48 +++++++++++---- src/theme/Root.tsx | 82 ++++++++++++++----------- 3 files changed, 84 insertions(+), 50 deletions(-) diff --git a/docs/content/authorization-concepts.mdx b/docs/content/authorization-concepts.mdx index 9b3c344376..5b84c7fd41 100644 --- a/docs/content/authorization-concepts.mdx +++ b/docs/content/authorization-concepts.mdx @@ -49,7 +49,7 @@ export const faqJsonLd = { name: 'What is Policy-Based Access Control?', acceptedAnswer: { '@type': 'Answer', - text: "Policy-Based Access Control (PBAC) manages authorization policies centrally, external to application code. Most ABAC implementations are also PBAC. OpenFGA's model DSL is itself a policy: committed to Git, reviewed via PR, deployed like any other code.", + text: "Policy-Based Access Control (PBAC) manages authorization policies centrally, external to application code. Most ABAC implementations are also PBAC. OpenFGA's authorization model DSL is functionally similar to a policy: it can be committed, PR reviewed and CI/CD deployed.", }, }, { @@ -65,7 +65,7 @@ export const faqJsonLd = { name: 'What is Zanzibar?', acceptedAnswer: { '@type': 'Answer', - text: "Zanzibar is Google's global authorization system, used by Drive, YouTube, Calendar, and Cloud. It stores object-relation-user tuples and answers checks and reverse queries against the resulting graph. OpenFGA implements the Zanzibar model on your existing databases.", + text: "Zanzibar is Google's global authorization system, used by Drive, YouTube, Calendar, and Cloud. It stores object-relation-user tuples and answers checks and reverse queries against the resulting graph. OpenFGA is an authorization service, similar to Zanzibar, and is open source and can be used with your data and services.", }, }, ], diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 35082e3aa6..3747431761 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -5,18 +5,42 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import { QuickStartSection, FeaturesSection, ResourcesSection, HeroHeader } from '@features/LandingPage'; -const softwareApplicationJsonLd = { +const softwareJsonLd = { '@context': 'https://schema.org', - '@type': 'SoftwareApplication', - name: 'OpenFGA', - url: 'https://openfga.dev', - applicationCategory: 'SecurityApplication', - operatingSystem: 'Linux, macOS, Windows', - description: - 'OpenFGA is an open-source, CNCF-incubating fine-grained authorization engine inspired by Google Zanzibar.', - programmingLanguage: ['Go', 'JavaScript', 'Java', '.NET', 'Python'], - codeRepository: 'https://github.com/openfga/openfga', - license: 'https://www.apache.org/licenses/LICENSE-2.0', + '@graph': [ + { + '@type': 'SoftwareApplication', + '@id': 'https://openfga.dev/#software', + name: 'OpenFGA', + url: 'https://openfga.dev', + applicationCategory: 'SecurityApplication', + applicationSubCategory: 'Authorization Service', + operatingSystem: 'Linux, macOS, Windows', + owner: { '@id': 'https://www.cncf.io/#organization' }, + releaseNotes: 'https://github.com/openfga/openfga/blob/main/CHANGELOG.md', + isAccessibleForFree: true, + softwareHelp: { + '@type': 'WebPage', + name: 'OpenFGA Discussions', + discussionUrl: 'https://github.com/orgs/openfga/discussions', + }, + keywords: + 'open source, security, permissions, authorization, rbac, entitlements, abac, fga, pbac, fine grained access control, zanzibar, fine grained authorization, rebac, openfga', + description: + 'OpenFGA is an open-source, CNCF-incubating fine-grained authorization engine inspired by Google Zanzibar.', + license: 'https://www.apache.org/licenses/LICENSE-2.0', + subjectOf: { '@id': 'https://openfga.dev/#source-code' }, + }, + { + '@type': 'SoftwareSourceCode', + '@id': 'https://openfga.dev/#source-code', + name: 'OpenFGA source code', + codeRepository: 'https://github.com/openfga/openfga', + programmingLanguage: ['Go', 'JavaScript', 'Java', '.NET', 'Python'], + license: 'https://www.apache.org/licenses/LICENSE-2.0', + targetProduct: { '@id': 'https://openfga.dev/#software' }, + }, + ], }; export default function Home(): JSX.Element { @@ -28,7 +52,7 @@ export default function Home(): JSX.Element { description="OpenFGA is a CNCF Incubating open-source fine-grained authorization engine inspired by Google Zanzibar. Used by Grafana, Docker, and Canonical." > - +
diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index 885f154e87..c74d710201 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -107,45 +107,56 @@ function buildBreadcrumbJsonLd(pathname: string, siteUrl: string): string | null }).replace(/ {noindex && } - {isHome && } - {isHome && } + {isHome && } {breadcrumbJsonLd && } {children} From 1f4f731b94238cab5f6807a6834b2e7419b131ba Mon Sep 17 00:00:00 2001 From: Andres Aguiar Date: Mon, 8 Jun 2026 16:51:05 -0500 Subject: [PATCH 13/13] feat: adding adopter stories --- docs/content/adopters/agicap.mdx | 53 ++++++++++++++++++++++++++++ docs/content/adopters/docker.mdx | 46 ++++++++++++++++++++++++ docs/content/adopters/grafana.mdx | 54 +++++++++++++++++++++++++++++ docs/content/adopters/headspace.mdx | 46 ++++++++++++++++++++++++ docs/content/adopters/openlane.mdx | 44 +++++++++++++++++++++++ docs/content/adopters/overview.mdx | 38 ++++++++++++++++++++ docs/content/adopters/read-ai.mdx | 48 +++++++++++++++++++++++++ docs/content/adopters/vitrolife.mdx | 47 +++++++++++++++++++++++++ docs/content/adopters/zuplo.mdx | 54 +++++++++++++++++++++++++++++ docs/sidebars.js | 17 +++++++++ 10 files changed, 447 insertions(+) create mode 100644 docs/content/adopters/agicap.mdx create mode 100644 docs/content/adopters/docker.mdx create mode 100644 docs/content/adopters/grafana.mdx create mode 100644 docs/content/adopters/headspace.mdx create mode 100644 docs/content/adopters/openlane.mdx create mode 100644 docs/content/adopters/overview.mdx create mode 100644 docs/content/adopters/read-ai.mdx create mode 100644 docs/content/adopters/vitrolife.mdx create mode 100644 docs/content/adopters/zuplo.mdx diff --git a/docs/content/adopters/agicap.mdx b/docs/content/adopters/agicap.mdx new file mode 100644 index 0000000000..2335c6cdc8 --- /dev/null +++ b/docs/content/adopters/agicap.mdx @@ -0,0 +1,53 @@ +--- +title: Agicap Case Study +description: How European fintech Agicap runs OpenFGA in production for 8,000+ customers at 250 RPS with conditional ReBAC across every backend service. +sidebar_position: 2 +slug: /adopters/agicap +--- + +# Agicap: Fine-grained authorization for a European fintech platform + +[Agicap](https://agicap.com) is a European fintech that helps small, medium, and large enterprises manage cash flow in real time. Its SaaS platform serves more than 8,000 customers across industries, and every backend service in the platform validates access through OpenFGA. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Fintech / cash flow management | +| **In production since** | April 2023 | +| **Scale** | ~250 requests per second, 8,000+ customers | +| **Deployment** | Self-hosted, on-premises | +| **Key features used** | ReBAC, conditional relationships | + +## Why OpenFGA + +Agicap needed an open-source authorization layer with a strong community, on-premises deployment for compliance, and a model flexible enough to express financial-product permissions that pure RBAC could not. They evaluated alternatives such as Oso and concluded OpenFGA was the most stable option that fit those requirements, with approachable maintainers and clear documentation. + +The team specifically chose ReBAC over an RBAC redesign because it let them express fine-grained relationships without re-inventing authorization logic inside every service. Learn more about that trade-off in [RBAC vs ReBAC](/docs/authorization-concepts). + +## Architecture and scale + +- All backend services call OpenFGA via an internal **secure facade** rather than the OpenFGA API directly. The facade enforces application-level rules on top of OpenFGA so the data plane is never exposed. +- Authorization is enforced consistently across development, pre-production, load-test, and production environments. +- Performance work over time pushed Agicap from a deeper hierarchy to a flatter authorization model, which improved both query latency and scalability — a pattern documented in the [performance best practices](/docs/best-practices). + +## Engineering with the community + +Agicap is an active upstream contributor: + +- Engineers from the platform and SRE teams open pull requests against `openfga/openfga` to fix bugs and tune performance. +- The team participates in the monthly OpenFGA community call. +- Agicap has co-presented OpenFGA talks with maintainers at KubeCon EU 2024 (Paris) and KubeCon NA 2024 (Salt Lake City). + +When the team filed a critical performance issue, the upstream maintainers shipped a fix within 24 hours. + +## Outcomes + +- A single, evolvable authorization layer behind every backend service. +- Faster delivery of new permissions — schema changes replace code changes. +- Cost savings from running self-hosted instead of a proprietary alternative. +- Confidence at production scale with 8,000+ customers and continuous traffic. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Pauline Jamin, Head of Engineering – Finance and Core at Agicap, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga), and a [presentation in the OpenFGA community meeting on Agicap's OpenFGA deployment](https://www.youtube.com/watch?v=XBHqGFfe-K4). diff --git a/docs/content/adopters/docker.mdx b/docs/content/adopters/docker.mdx new file mode 100644 index 0000000000..57f6dee85f --- /dev/null +++ b/docs/content/adopters/docker.mdx @@ -0,0 +1,46 @@ +--- +title: Docker Case Study +description: How Docker migrated to OpenFGA with a parallel-run strategy and now uses ReBAC to centralize permissions across products. +sidebar_position: 3 +slug: /adopters/docker +--- + +# Docker: Centralizing permissions with ReBAC + +[Docker](https://www.docker.com) provides tools that help developers build, share, run, and verify applications across environments. Docker adopted OpenFGA in early 2024 and uses it to centralize authorization across an expanding set of products. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Developer tools / platform | +| **In production since** | March 2024 | +| **Scale** | 100-150 requests per second | +| **Deployment** | Self-hosted | +| **Key features used** | ReBAC, DSL, SDKs and CLI | + +## Why OpenFGA + +Docker evaluated several access-control systems before choosing OpenFGA. The decision came down to: + +- **ReBAC** as a more flexible model than RBAC for the products Docker builds. +- **Self-hosted, open source**, easy to run locally (a working stack via Docker Compose in under five minutes). +- **CNCF backing** and contributors with strong security pedigree. +- Mature **SDKs, APIs, and testing tools**. +- A responsive maintainer community. + +## Migration approach + +Docker ran OpenFGA in **parallel with the existing authorization system**: every permission check went to both engines, and results were compared. Once both systems consistently agreed, traffic was incrementally cut over to OpenFGA. The parallel-run pattern is one we recommend for any production migration — see the [adoption patterns guide](/docs/best-practices). + +## Outcomes + +- Permission changes that previously required code changes are now centralized in the authorization model file. +- New Docker products integrate into the access-control system faster. +- Operational overhead for permission updates dropped substantially. + +The early scaling pain points the team hit — particularly batch checks across many records — were addressed quickly by upstream releases. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Gurleen Sethi, Senior Software Engineer at Docker, Inc., available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/grafana.mdx b/docs/content/adopters/grafana.mdx new file mode 100644 index 0000000000..8d0afc2989 --- /dev/null +++ b/docs/content/adopters/grafana.mdx @@ -0,0 +1,54 @@ +--- +title: Grafana Labs Case Study +description: Why Grafana Labs replaced its single-tenant access control engine with OpenFGA to power multi-tenant Grafana Cloud and embedded OSS deployments. +sidebar_position: 4 +slug: /adopters/grafana +--- + +# Grafana Labs: From single-tenant engine to multi-tenant ReBAC + +[Grafana Labs](https://grafana.com) is the company behind Grafana, Loki, Tempo, Mimir, and the LGTM observability stack. Grafana adopted OpenFGA to replace an internal single-tenant access-control engine that no longer fit the multi-tenant architecture of Grafana Cloud. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Observability | +| **First experiments** | February 2024 | +| **Mainline integration** | August 2024 | +| **Version** | v1.10.0 | +| **Deployment** | Multi-tenant SaaS, embedded OSS, on-premises | + +## Why OpenFGA + +Grafana needed an engine that did **two** things competitors did not bundle: + +1. **Authorization evaluation** — like an OPA-style policy engine. +2. **A storage layer for permissions** — a tuple store with a per-tenant schema. + +That combination, plus OpenFGA's **CNCF affiliation** and explicit governance policy, made it preferable to building yet another in-house system or adopting a project that could change its license later. + +## Architecture and scale + +OpenFGA runs in three Grafana environments: + +- **Development and staging** — already serving internal production workloads. +- **External production** — deployed to a single cluster in a pre-production capacity, shadowing real traffic to validate consistency and performance before broader rollout. + +The team standardized on the **PostgreSQL adapter** after finding the MySQL adapter less mature. Refactoring Grafana's legacy schema toward OpenFGA-native modeling produced significant performance gains — an outcome echoed by the [source-of-truth best practice](/docs/best-practices). + +## Upstream investment + +- Grafana **maintains the SQLite adapter**, which was contributed back to OpenFGA so it can ship with embedded Grafana. +- Future areas of contribution include **pluggable storage** (so non-core storage adapters work without rebuilding OpenFGA) and **observability** improvements. +- KubeCon EU 2025 talk: *From Chaos To Control: Migrating Access Control* by Jo Guerreiro and Poovamraj Thanganadar Thiagarajan. + +## Outcomes + +- One authorization platform spans Grafana Cloud (multi-tenant SaaS) and Grafana OSS (embedded), removing the need to maintain separate engines. +- Schema-driven iteration replaced engine-tuning work the team used to do manually. +- The team is targeting **list-users** to enable reverse permission search — showing all users who can access a given resource — a capability the legacy engine never had. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Joao Guerreiro, Senior Engineering Manager at Grafana Labs, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/headspace.mdx b/docs/content/adopters/headspace.mdx new file mode 100644 index 0000000000..91d8e8f35c --- /dev/null +++ b/docs/content/adopters/headspace.mdx @@ -0,0 +1,46 @@ +--- +title: Headspace Case Study +description: How Headspace authorizes Ebb, its empathetic AI companion, with OpenFGA — driving end-to-end checks down from 10–15 seconds to 10–15 milliseconds. +sidebar_position: 6 +slug: /adopters/headspace +--- + +# Headspace: Authorizing an empathetic AI companion at consumer scale + +[Headspace](https://www.headspace.com) is a global mental-health platform with over 105 million app downloads and 90 million lives reached. Its AI companion, Ebb, has handled more than 6 million conversations since launching, and every message Ebb processes runs through an OpenFGA authorization check. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Mental health / consumer health | +| **Use case** | AI companion (Ebb) gating | +| **Scale** | 90M+ lives, 105M+ downloads, 6M+ Ebb messages | +| **Deployment** | Self-hosted | +| **Key features used** | BatchCheck, contextual tuples, graph design, Terraform-managed model | + +## Why OpenFGA + +Ebb is gated on a combination of business rules: who the member is contracted through, which country they are messaging from, which language their app is set to, and whether their employer has opted them out. A pure RBAC system could not express this without exploding into a role per combination, and a hand-rolled SQL check ran 10–15 seconds in the worst case — unacceptable for a chat experience. + +The Headspace team chose OpenFGA so the AI gating rules could live in a single relationship graph the platform team owned, with the same model evaluated from every service that fronts Ebb. + +## Architecture + +- **Wrapper API in front of OpenFGA.** Application services do not call the OpenFGA store directly. They call an internal authorization service that fans out **four parallel [BatchCheck](/docs/interacting/relationship-queries) requests** — assigned-to-Ebb, country-allowed, language-allowed, and not-blocked-by-org — and combines the results. +- **Inverted graph for performance.** The original model put the AI feature at the top with users below; a check meant traversing the entire user population. Flipping the direction so the user is the object and Ebb access is reached through unions of small relations dropped end-to-end latency from 10–15 seconds to 10–15 milliseconds. +- **Bidirectional tuple writes.** When a member-to-feature relationship is written, the inverse tuple is written at the same time, keeping reads cheap in either direction. +- **Terraform-managed model and static tuples.** The authorization model and the static enablement tuples (countries, languages, default org policies) ship through the Headspace [OpenFGA Terraform provider](https://github.com/openfga/terraform-provider-openfga), so model changes go through the same review pipeline as infrastructure. +- **Hidden model version.** The wrapper API does not expose the OpenFGA model ID to consumers; rolling forward to a new model version is a deploy of the wrapper, not a coordinated change across every caller. +- **SDK 1.10 conflict resolution.** The team adopted the conflict-resolution behavior shipped in SDK 1.10 to safely handle concurrent tuple writes during high-traffic enrollment events. + +## Outcomes + +- **End-to-end Ebb authorization in 10-15 ms**, down from 10-15 seconds. +- **Per-user blocking added without touching call sites** — a new relation in the model and a tuple write was enough; no service had to ship code. +- **Single source of truth** for AI gating rules, owned by the platform team and reviewed in Terraform. +- **Operational headroom** to extend Ebb gating (new languages, new contracts, new opt-out criteria) without rewriting application code. + +## Source + +This case study is based on a [presentation in the OpenFGA community meeting by Jeremy, principal engineer at Headspace](https://www.youtube.com/watch?v=xCu39aG7B1A). Supporting public material on Ebb is available at [headspace.com](https://www.headspace.com). diff --git a/docs/content/adopters/openlane.mdx b/docs/content/adopters/openlane.mdx new file mode 100644 index 0000000000..e54c98b879 --- /dev/null +++ b/docs/content/adopters/openlane.mdx @@ -0,0 +1,44 @@ +--- +title: OpenLane Case Study +description: How compliance-automation startup OpenLane wires OpenFGA into ent at the data-access layer, with overfetch + BatchCheck replacing slow ListObjects. +sidebar_position: 7 +slug: /adopters/openlane +--- + +# OpenLane: Authorization at the data-access layer for compliance automation + +[OpenLane](https://theopenlane.io) is an open-source compliance automation platform that helps teams achieve and maintain SOC 2, ISO 27001, and similar attestations. OpenFGA is wired into its data-access layer, so every GraphQL query and mutation is authorized without each resolver having to remember to ask. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Compliance automation (GRC) | +| **Stack** | Go, GraphQL (gqlgen), [ent](https://entgo.io/) ORM, PostgreSQL, Kubernetes | +| **Deployment** | Self-hosted; separate Postgres databases for application data and OpenFGA | +| **Key features used** | BatchCheck, contextual tuples, object-owned cascading permissions, FGA-driven feature flags | + +## Why OpenFGA + +OpenLane builds the kind of platform whose customers will themselves be audited. Authorization had to be defensible end-to-end — every read, every write, every export — and could not live in a `if user.role == "admin"` switch sprinkled across resolvers. The team picked OpenFGA so authorization decisions were centralized, modeled as relationships, and could evolve without code changes in every service. + +A second motivation was packaging: OpenLane sells modules, and the same engine that grants access to a record can grant access to a feature. OpenFGA is the source of truth for **both**. + +## Architecture + +- **Authorization as ent middleware.** OpenLane uses [ent](https://entgo.io/) hooks to write tuples on every mutation and interceptors plus policies to evaluate every query. Resolvers do not call the OpenFGA SDK directly; the data-access layer does. +- **Object-owned mixin.** A reusable ent mixin attaches `owner` and parent relations to any record type, so cascading permissions ("an editor of the parent program can edit each control") are declared once and applied uniformly. +- **Overfetch + BatchCheck instead of ListObjects.** An early implementation used [ListObjects](/docs/interacting/relationship-queries) and saw ~8-second worst-case latency for large result sets. The team switched to overfetching candidates from Postgres (capped at 100 per page, up to 1,000 overfetched) and running [BatchCheck](/docs/interacting/relationship-queries) to filter in a single round trip. Total counts are computed via a separate query that short-circuits when the user is an admin. +- **In-house wrapper packages.** Three small Go packages — `FGX` (typed helpers around the OpenFGA SDK), `NFGA` (no-op fakes for unit tests), and `access-map` (declarative relation registration) — keep callers honest and make adding a new entity type a few lines of mixin configuration. +- **OpenFGA-as-feature-flags.** Module entitlements ("does this tenant have the policy module?") live in the same OpenFGA store as record-level permissions, so a check that returns `false` because the user is not an editor and a check that returns `false` because the tenant did not buy the module use the same call site. + +## Outcomes + +- **Authorization can't be forgotten.** Hooks and interceptors mean a new entity type inherits authorization automatically. +- **List-style endpoints went from ~8 s worst case to comfortably under a second** at expected page sizes, without changing the public API. +- **One store, two jobs.** Record permissions and feature entitlements live in OpenFGA, so packaging changes don't require a second policy system. +- **Auditable end-to-end.** Tuple writes happen in the same transaction boundary as the underlying data mutation, so the access graph and the data it protects don't drift. + +## Source + +This case study is based on a [presentation in the OpenFGA community meeting by Sarah Funkhouser, co-founder and head of engineering at OpenLane](https://www.youtube.com/watch?v=ZdlftEKQ0UA). The OpenLane platform itself is open source — the integration patterns above are visible in the [theopenlane GitHub organization](https://github.com/theopenlane). diff --git a/docs/content/adopters/overview.mdx b/docs/content/adopters/overview.mdx new file mode 100644 index 0000000000..a989e8577a --- /dev/null +++ b/docs/content/adopters/overview.mdx @@ -0,0 +1,38 @@ +--- +title: OpenFGA Adopters and Case Studies +description: Production OpenFGA case studies from Agicap, Docker, Grafana Labs, Headspace, OpenLane, Read AI, Vitrolife, Zuplo and other adopters running fine-grained authorization at scale. +sidebar_position: 1 +slug: /adopters +--- + +# OpenFGA in production + +OpenFGA is deployed in production at fintechs, observability platforms, AI products, developer-tool companies, and API platforms. The case studies below are based on public [CNCF TOC adopter interviews](https://github.com/cncf/toc/tree/main/projects/openfga). + +## Featured case studies + +| Adopter | Industry | In production since | Scale | +| --- | --- | --- | --- | +| [Read AI](/docs/adopters/read-ai) | AI meeting intelligence | April 2023 | 5,200 RPS peak, 5.3B+ tuples | +| [Agicap](/docs/adopters/agicap) | Fintech | April 2023 | ~250 RPS, 8,000+ customers | +| [Zuplo](/docs/adopters/zuplo) | API management | 2024 | 500+ RPS spikes, multi-region edge | +| [Grafana Labs](/docs/adopters/grafana) | Observability | 2024 | Multi-tenant SaaS + embedded OSS | +| [Docker](/docs/adopters/docker) | Developer tools | March 2024 | 100–150 RPS | +| [Headspace](/docs/adopters/headspace) | Mental health & consumer | 2024 | 90M lives, 6M Ebb messages, 10-15 ms p99 | +| [OpenLane](/docs/adopters/openlane) | Compliance SaaS | 2024 | ent ORM hooks, BatchCheck overfetch (100/1000) | +| [Vitrolife Group](/docs/adopters/vitrolife) | Healthcare | 2025 | Hybrid Entra + OpenFGA, hourly differential sync | + +## What these adopters have in common + +- **Self-hosted, open source.** Every adopter cited the ability to run OpenFGA themselves as a key reason for choosing it over proprietary offerings. +- **PostgreSQL at scale.** Production deployments are running on Postgres, with billions of tuples in the largest case. +- **ReBAC over RBAC.** Each team chose relationship-based access control for the flexibility it gives over flat role models. See [authorization concepts](/docs/authorization-concepts) for a refresher. +- **CNCF governance** matters. Teams explicitly contrasted CNCF stewardship with the licensing risk of source-available alternatives. + +## Adopter list + +OpenFGA is also publicly used by organizations including [Twilio](https://www.twilio.com), [Italia.it (Italian Government)](https://www.pagopa.gov.it/), [Mercado Libre](https://www.mercadolibre.com), [Wolt](https://wolt.com), [Canonical](https://canonical.com), and many more. The full, machine-readable list is maintained in the [`openfga/community` repository](https://github.com/openfga/community/blob/main/ADOPTERS.md). + +## Add your story + +If your team runs OpenFGA in production and wants to share lessons learned, open a pull request against the [`openfga/community` ADOPTERS file](https://github.com/openfga/community/blob/main/ADOPTERS.md) or join the [CNCF Slack `#openfga` channel](https://cloud-native.slack.com/archives/C06G1NNH47N). diff --git a/docs/content/adopters/read-ai.mdx b/docs/content/adopters/read-ai.mdx new file mode 100644 index 0000000000..a4f802e4eb --- /dev/null +++ b/docs/content/adopters/read-ai.mdx @@ -0,0 +1,48 @@ +--- +title: Read AI Case Study +description: How Read AI runs OpenFGA at 5,200 requests per second with 20ms p99 latency over more than 5 billion relationship tuples. +sidebar_position: 5 +slug: /adopters/read-ai +--- + +# Read AI: 5 billion tuples, 20ms p99 latency + +[Read AI](https://www.read.ai) is the AI meeting notetaker and assistant trusted by more than 100,000 organizations and 75% of the Fortune 500, adding more than one million new customers every month. OpenFGA backs the authorization layer that lets Read AI safely share intelligence across meetings, messages, email, and documents. + +## At a glance + +| | | +| --- | --- | +| **Industry** | AI productivity / meeting intelligence | +| **In production since** | April 28, 2023 | +| **Peak load** | 5,200 RPS | +| **Latency** | 20ms p99 / 1.8ms average | +| **Tuple count** | 5,323,283,829 (and growing) | +| **Version** | v1.8.16 | +| **Storage** | PostgreSQL | + +## Why OpenFGA + +Read AI ran a proprietary, organically built authorization system that hit performance and scalability ceilings as the platform grew. The team evaluated alternatives such as Authzed before choosing OpenFGA, citing: + +- **Zanzibar foundations** that aligned with the sharing semantics the product needed. +- **Documentation clarity**, especially the practical examples and modeling guides. +- The ability to **self-host** with predictable cost. +- Approachable, responsive maintainers. + +## Production at scale + +The self-hosted OpenFGA service handles peak load of **5,200 requests per second** with a **20ms p99 latency** and **1.8ms average latency**. The data store holds more than **5.3 billion tuples** and grows daily. + +OpenFGA upgrades are folded into a monthly cadence. The OpenFGA release pace is faster than Read AI's, but upgrades have been smooth with no significant backward-compatibility issues. + +## Outcomes + +- Confidence in secure data authorization across the entire product surface. +- Adoption of ReBAC best practices improved internal design decisions. +- Compute and hosting costs dropped versus the prior solution. +- OpenFGA has not been the bottleneck even at peak. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Andrew Powers, Software Engineering Manager at Read AI, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/content/adopters/vitrolife.mdx b/docs/content/adopters/vitrolife.mdx new file mode 100644 index 0000000000..08ae367e81 --- /dev/null +++ b/docs/content/adopters/vitrolife.mdx @@ -0,0 +1,47 @@ +--- +title: Vitrolife Group Case Study +description: How Vitrolife combines Microsoft Entra ID app roles with OpenFGA fine-grained ReBAC for a .NET 10 metadata platform serving IVF clinics worldwide. +sidebar_position: 8 +slug: /adopters/vitrolife +--- + +# Vitrolife Group: Hybrid Entra + OpenFGA authorization for a .NET healthcare platform + +The [Vitrolife Group](https://www.vitrolife.com) is a Swedish medical-device and software company serving IVF clinics worldwide. Its internal metadata platform — built on .NET 10 to organize landing zones, domains, platforms, and teams — uses a hybrid authorization design: Microsoft Entra ID app roles for coarse-grained access and OpenFGA for fine-grained, per-resource decisions. + +## At a glance + +| | | +| --- | --- | +| **Industry** | Healthcare / medical devices (IVF) | +| **Stack** | .NET 10, ASP.NET Core, Microsoft Entra ID, [Wolverine](https://wolverinefx.net/), Aspire | +| **Use case** | Internal metadata platform: landing zones, domains, platforms, teams | +| **Deployment** | Self-hosted alongside the .NET API | +| **Key features used** | Contextual tuples, intersections, conditions, full + differential sync from OpenFGA | + +## Why OpenFGA + +Entra ID gave Vitrolife a managed identity layer for both human users and service principals, but app roles alone could not express *per-resource* permissions ("can edit *this* landing zone, not all of them"). The platform team layered OpenFGA underneath to model ownership and group membership without inventing a parallel directory. + +Crucially, the team wanted OpenFGA — not Entra — to be the **source of truth** for access groups, so that the application's own model of "who has access to what" did not depend on a directory operation in a separate system. + +## Architecture + +- **App-role grammar.** Every Entra app role follows `...`. Capabilities are `viewer` / `editor` / `admin`. The qualifier is either `all` (the principal may act on every record of that resource) or `self` (the principal may act only on records they have an OpenFGA relationship with). `all` injects a contextual tuple at request time; `self` falls through to a normal [Check](/docs/interacting/relationship-queries) against the relationship graph. +- **Users and service principals are uniform.** Because both human users and service principals carry app roles, the application code never branches on principal type — the OpenFGA tuple set treats them identically. +- **Type-safe C# wrappers.** `FGAObjectId` enforces `:` shape at compile time. An `FGATuple` builder makes tuple construction explicit, and relations are modeled as `snake_case` enums to match the OpenFGA wire format without stringly-typed bugs. +- **Group memberships as contextual tuples.** Entra group memberships are surfaced into OpenFGA via contextual tuples on each request, intersected with FGA group definitions so a principal must satisfy *both* the Entra group and the FGA relationship to gain access. +- **Atomic writes via transactional outbox.** SQL writes and OpenFGA tuple writes are coordinated through a [Wolverine](https://wolverinefx.net/) outbox: the database row and the queued FGA mutation commit together; consumers process the outbox to make the tuple change visible. This is eventually consistent within a small, bounded window. +- **OpenFGA → Entra sync.** A scheduled job reads OpenFGA as the source of truth and reconciles Entra access groups: an hourly **full sync** plus a **differential sync** triggered by outbox events. If a tuple is removed in OpenFGA, the corresponding Entra membership is removed on the next pass. +- **Vertical slices and Aspire local dev.** Features are organized as vertical slices; .NET Aspire orchestrates a local environment with a mocked Microsoft Graph and a local OpenFGA, so the team can run the full authorization path end-to-end on a developer laptop. + +## Outcomes + +- **One mental model for authorization** that spans humans, service principals, and resources, with Entra handling identity and OpenFGA handling relationships. +- **Per-resource permissions** without a custom directory — the existing Entra investment stays in place. +- **OpenFGA as the durable source of truth** for access groups, with Entra reconciled on a schedule rather than the other way around. +- **Atomic SQL + tuple writes** through the Wolverine outbox, removing the class of bugs where the application data and the access graph drift apart. + +## Source + +This case study is based on a [presentation in the OpenFGA community meeting by Simon Gottschlag, CTO at Co-native, working with the Vitrolife Group on its platform](https://www.youtube.com/watch?v=nwu5SiiMpM8). diff --git a/docs/content/adopters/zuplo.mdx b/docs/content/adopters/zuplo.mdx new file mode 100644 index 0000000000..b60140e3f5 --- /dev/null +++ b/docs/content/adopters/zuplo.mdx @@ -0,0 +1,54 @@ +--- +title: Zuplo Case Study +description: How API management platform Zuplo runs OpenFGA across multiple data centers with PostgreSQL global replication for edge authorization. +sidebar_position: 6 +slug: /adopters/zuplo +--- + +# Zuplo: Edge authorization across multiple data centers + +[Zuplo](https://zuplo.com) is a developer-first API management platform that helps teams build, deploy, and scale APIs globally. Zuplo uses OpenFGA to enforce fine-grained authorization at the edge across every region the platform runs in. + +## At a glance + +| | | +| --- | --- | +| **Industry** | API management | +| **In production since** | ~2024 (12 months in production after 18 months total adoption) | +| **Scale** | Several hundred RPS, with spikes above 500 RPS | +| **Version** | v1.8.x | +| **Storage** | PostgreSQL with global replication | +| **Deployment** | Multi-region edge | + +## Why OpenFGA + +As Zuplo's customer base shifted toward larger enterprises, simple project-membership rules were no longer enough. The team evaluated: + +- Axiomatics (AuthZEN-based) +- Aserto (AuthZEN-based) +- Auth0 FGA +- Building a custom solution in-house + +They chose OpenFGA because it is **open source and self-hostable**, and because **PostgreSQL as a backend** let Zuplo replicate authorization data globally and run checks at the edge — exactly the topology API management requires. + +## Architecture + +- **One authorization model** governs the entire product, single-tenant. +- The same model handles **user access and API key access** to product features. +- OpenFGA is deployed in production across multiple data centers worldwide. +- Performance work for major upgrades uses k6-based end-to-end load tests. + +## Outcomes + +- Updating and versioning the authorization model independently of the application code accelerated development cycles. +- Roles and permissions can be tested and refined without code changes. +- Authorization is centralized across teams while keeping concerns separate. +- Caching introduced upstream replaced a homegrown cache layer Zuplo had built before OpenFGA shipped its own. + +## Outlook + +Zuplo continues to file feature requests upstream and has expressed interest in publicly sharing more details about how it implements authorization at the edge. + +## Source + +This case study is based on the public CNCF TOC adopter interview with Nate Totten, Co-founder & CTO of Zuplo, available in the [`cncf/toc` repository](https://github.com/cncf/toc/tree/main/projects/openfga). diff --git a/docs/sidebars.js b/docs/sidebars.js index 0b48d9d8bc..00994f6b97 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -500,6 +500,23 @@ const sidebars = { { type: 'doc', label: 'Microservices Authorization', id: 'content/use-cases/microservices-authorization' }, ], }, + { + type: 'category', + collapsible: true, + collapsed: true, + label: 'Adopters', + link: { type: 'doc', id: 'content/adopters/overview' }, + items: [ + { type: 'doc', label: 'Agicap', id: 'content/adopters/agicap' }, + { type: 'doc', label: 'Docker', id: 'content/adopters/docker' }, + { type: 'doc', label: 'Grafana Labs', id: 'content/adopters/grafana' }, + { type: 'doc', label: 'Read AI', id: 'content/adopters/read-ai' }, + { type: 'doc', label: 'Headspace', id: 'content/adopters/headspace' }, + { type: 'doc', label: 'Zuplo', id: 'content/adopters/zuplo' }, + { type: 'doc', label: 'OpenLane', id: 'content/adopters/openlane' }, + { type: 'doc', label: 'Vitrolife Group', id: 'content/adopters/vitrolife' }, + ], + }, { type: 'category', collapsible: true,