# OpenFGA Documentation
> OpenFGA is a CNCF open source authorization system for fine-grained, relationship-based access control.
Use the documentation for current product behavior and the API specification for exact request and response shapes. Blog posts are historical announcements and may describe older releases.
## Start Here
- [OpenFGA documentation home](https://openfga.dev/index.md): Product overview, benefits, and feature summary.
- [Introduction to OpenFGA](https://openfga.dev/docs/fga.md): Build relationship-based, role-based, and attribute-based access control at scale.
- [Getting Started](https://openfga.dev/docs/getting-started.md): OpenFGA tutorial and quickstart: install the server, configure an authorization model, write tuples, and run your first permission checks in minutes.
- [Setup OpenFGA](https://openfga.dev/docs/getting-started/setup-openfga/overview.md): Learn how to set up an OpenFGA server with Docker, Kubernetes, or as a Go library. Covers configuration, datastores (Postgres, MySQL, SQLite), and telemetry.
- [Get Started with Modeling](https://openfga.dev/docs/modeling/getting-started.md): An introduction to modeling
## FAQs and Concepts
- [Authorization Concepts](https://openfga.dev/docs/authorization-concepts.md): Learn fine-grained authorization concepts: ReBAC, RBAC, ABAC, PBAC, and Google Zanzibar. Understand how OpenFGA models permissions for modern apps.
- [Concepts](https://openfga.dev/docs/concepts.md): Learning about FGA concepts
- [What is Fine-Grained Authorization?](https://openfga.dev/docs/learn/fine-grained-authorization.md): Fine-grained authorization decides access at the resource and action level. Learn what FGA is, what it buys you, and how OpenFGA implements it.
- [What is ReBAC?](https://openfga.dev/docs/learn/rebac.md): ReBAC models permissions as relationships between users and resources. Learn what ReBAC is, when to use it, and how OpenFGA implements it.
- [RBAC vs. ReBAC](https://openfga.dev/docs/learn/rbac-vs-rebac.md): RBAC assigns roles to users; ReBAC models relationships between users and resources. Learn when roles run out and ReBAC takes over.
- [ABAC vs. ReBAC](https://openfga.dev/docs/learn/abac-vs-rebac.md): ABAC decides on attributes; ReBAC decides on relationships. Learn which fits which problem — and how OpenFGA covers both via conditions.
- [Policy Engines vs. Relationship Engines](https://openfga.dev/docs/learn/policy-engine.md): 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.
- [What is Google Zanzibar?](https://openfga.dev/docs/learn/zanzibar.md): Google Zanzibar is the paper behind Google's global authorization system. Learn what Zanzibar is, what it solved, and how OpenFGA implements it.
## API
- [OpenFGA API specification](https://raw.githubusercontent.com/openfga/api/main/docs/openapiv2/apidocs.swagger.json): Machine-readable OpenAPI specification for the OpenFGA HTTP API.
- [Relationship Queries: Check, Read, Expand, ListObjects and ListUsers](https://openfga.dev/docs/interacting/relationship-queries.md): An overview of how to use the Check, Read, Expand, and ListObject APIs
- [Install SDK Client](https://openfga.dev/docs/getting-started/install-sdk.md): Installing SDK client
- [Use the FGA CLI](https://openfga.dev/docs/getting-started/cli.md): Use the FGA CLI
## Site Pages
- [OpenFGA Project](https://openfga.dev/project.md): Learn why OpenFGA exists, where project work happens, how to contribute or give feedback, and how to report security issues.
## Complete Indexes
- [Complete documentation index](https://openfga.dev/docs/llms.txt): All current product documentation in Markdown.
- [Blog index](https://openfga.dev/blog/llms.txt): Product announcements and historical project updates.
## Optional
- [Complete documentation bundle](https://openfga.dev/llms-full.txt): Large single-file bundle; prefer the focused indexes and page links when context is limited.
- [OpenFGA source repository](https://github.com/openfga/openfga): Server source, releases, deployment guidance, and issue tracker.
- [OpenFGA SDKs](https://github.com/orgs/openfga/repositories?q=topic%3Asdk): Official client SDK repositories for supported programming languages.
# Full Documentation Content
# Conditional Relationship Tuples for OpenFGA
November 6, 2023 · 5 min read
[](https://github.com/aaguiarz)
[Andres Aguiar](https://github.com/aaguiarz)
Product Manager
Relationship Tuples are the facts that the OpenFGA evaluates to determine whether a user is permitted to access a resource.
The way tuples are considered when making authorization decisions in OpenFGA is guided by an authorization model, which employs concepts from Relationship-Based Access Control (ReBAC) to establish authorization policies. For instance, you might declare that users are allowed to view a document if they have permission to view its parent folder.
Although ReBAC offers a highly flexible method for structuring permissions, it encounters difficulties with defining permissions based on attributes that are not easily represented as relationships. Attributes such as “parent folder,” “department,” “region,” and “country” can be conceptualized as relationships between two entities. However, attributes like “IP address,” “time of day,” “team size limit,” or “maximum amount for a bank transfer” cannot be easily handled.
In our ongoing efforts to expand OpenFGA’s capacity for articulating a broader range of authorization policies, we are introducing **Conditional Relationship Tuples**. These allow for the specification of conditions under which a particular tuple is relevant when evaluating an authorization query.
Consider the following example, where we utilize Conditional Tuples to grant access for a user over a specified time duration. We stipulate that a user may be granted either unconditional access or access constrained to a certain time period:
```
model
schema 1.1
type user
type document
relations
define viewer: [user, user with non_expired_grant]
condition non_expired_grant(current_time: timestamp, grant_time: timestamp, grant_duration: duration) {
current_time < grant_time + grant_duration
}
```
If we write the following tuples:
| user | relation | object | condition |
| --------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| user:bob | viewer | document:1 | |
| user:anne | viewer | document:1 | `name` : `non_expired_grant`, `context` : { `grant_time` : `2023-01-01T00:00:00Z`, `grant_duration` : `1h` } |
You'll get the following results for the [Check](https://openfga.dev/api/service#/Relationship%20Queries/Check) operations below:
| user | relation | object | context | result |
| --------- | -------- | ---------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| user:bob | viewer | document:1 | | `allowed` : `true` |
| user:anne | viewer | document:1 | `current_time` : `2023-01-01T00:10:00Z` | `allowed` : `true` |
| user:anne | viewer | document:1 | `current_time` : `2023-01-01T02:00:00Z` | `allowed` : `false` |
| user:anne | viewer | document:1 | | `error` : "failed to evaluate relationship condition 'non\_expired\_grant': context is missing parameters '\[current\_time]' |
You'll get the following results for the [ListObjects](https://openfga.dev/api/service#/Relationship%20Queries/ListObjects) operations below:
| user | relation | object | context | result |
| --------- | -------- | ---------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| user:anne | viewer | document:1 | `current_time` : `2023-01-01T00:10:00Z` | `objects`: `[ "document:1"]` |
| user:anne | viewer | document:1 | | `error`: "failed to evaluate relationship condition 'non\_expired\_grant': tuple 'document:1#viewer\@user:anne' is missing context parameters '\[current\_time]' |
Note that:
- `user:bob` will always get `allowed:true` as we have assigned as viewer unconditionally.
- `user:anne` will get `allowed:true` if the `current_time` is before the `grant_time` + `grant_duration` and `allowed:false` otherwise.
- If you don't provide the `current_time` in the context, the Check and ListObjects operations will fail.
## Use Cases
The [OpenFGA Sample Stores repository](https://github.com/openfga/sample-stores) has several examples that take advantage of this new feature:
- [Granting access during a specific period of time (the use case explained above)](https://github.com/openfga/sample-stores/tree/main/stores/temporal-access).
- [Allow access based on the user’s IP Address](https://github.com/openfga/sample-stores/tree/main/stores/ip-based-access).
- [Granting access based on group membership and resource attributes](https://github.com/openfga/sample-stores/tree/main/stores/groups-resource-attributes).
- [Allow access to specific features based on usage](https://github.com/openfga/sample-stores/tree/main/stores/advanced-entitlements).
- [Determine if a user can make a bank transfer based .on the transaction amount](https://github.com/openfga/sample-stores/tree/main/stores/banking).
- [Data types and operations supported in conditions](https://github.com/openfga/sample-stores/tree/main/stores/condition-data-types).
## How to use it?
Conditional Relationship Tuples are included in OpenFGA 1.4.0-rc1 version. You can run it by pulling it from docker:
```
docker pull openfga/openfga:v1.4.0-rc1
docker run -p 8080:8080 -p 8081:8081 -p 3000:3000 openfga/openfga:v1.4.0-rc1 run`
```
OpenFGA has a rich ecosystem of developer tools. The following have been updated to support Conditional Relationship Tuples:
- [Visual Studio Code integration](https://github.com/openfga/vscode-ext) which provides syntax highlighting and model validations for conditions.
- Beta versions of the [Javascript SDK](https://www.npmjs.com/package/@openfga/sdk/v/0.3.0-beta.1) and the [Go SDK](https://github.com/openfga/go-sdk/releases/tag/v0.3.0-beta.1), which allows using the additional parameters.
- The [OpenFGA CLI](https://github.com/openfga/cli) allows validating models and runing tests that use conditional tuples. You can use it to test the new features by pointing to a `“.fga.yaml”` file that [defines the tests you want to run](https://github.com/openfga/cli#run-tests-on-an-authorization-model), without having to deploy OpenFGA.
## What’s Next?
We’ll address some limitations of the current implementation:
- The [Expand API](https://openfga.dev/api/service#/Relationship%20Queries/Expand) does not consider conditions.
- The Visual Studio Code integration is not validating the expressions in conditions.
- The Playground does not let you add context for tuples and assertions. You should use the VS Code Extension + the FGA CLI to test your models for now.
We'll also improve ListObjects scenarios when it's called with missing context. For example, consider the following model that enables access only to documents with a specific status:
```
model
schema 1.1
type user
type document
relations
define can_access: [user with docs_in_draft_status]
condition docs_in_draft_status(status: string) {
status == "draft"
}
```
If you want to list all the documents a user can view, you'll need to know the status of all of those documents. Given you don't know the documents the user has access too, you can't send the status of those as a parameter to ListObjects.
Our goal is to return a structure that you can use to filter documents on your side, similar to: `(document.id = ‘1’ and document.status = ‘draft’) or (document.id = ‘2’ and.status = draft)`
This won’t scale to a large number of documents, but would be useful in some scenarios.
## Reach out!
We want to learn how you use this feature and how we can improve it!
Please reach out through our [community channels](https://openfga.dev/community) with any questions or feedback.
**Tags:**
- [openfga](https://openfga.dev/blog/tags/openfga)
- [features](https://openfga.dev/blog/tags/features)
---
# Ignore Duplicate Tuples On Write
October 31, 2025 · 3 min read
[](https://github.com/tylernix)
[Tyler Nix](https://github.com/tylernix)
Product Manager
We've added two new optional parameters to the Write API endpoint to improve the experience of writing data to FGA. You can now gracefully ["ignore" duplicate writes and missing deletes](https://openfga.dev/docs/getting-started/update-tuples#05-ignoring-duplicate-or-missing-tuples).
## The Problem
When you're writing tuples to OpenFGA, it's almost inevitable that you'll try to write a relationship tuple that already exists (e.g., `user:anne` is already a `viewer` of `document:123`) or try to delete one that's already gone. In the past, OpenFGA would reject the entire Write request containing that single duplicate operation.
This forced developers to build complex error-handling and retry logic on the client-side, just to filter out the single problematic tuple and resend the rest of the batch. This adds latency and operational overhead.
## The Solution
The Write API now accepts two new optional parameters to gracefully handle these use cases:
- **`on_duplicate: "ignore"`**: When included in the `writes` section, this tells OpenFGA to simply skip any tuples that already exist instead of failing the request.
- **`on_missing: "ignore"`**: When included in the `deletes` section, this tells OpenFGA to skip any tuples that don't exist.
Now, you can send large batches of writes and deletes without worrying about these common conditions breaking your import.
## See it in Action
For writes:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
const options = {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA",
conflict: {
onDuplicateWrites: OnDuplicateWrites.Ignore,
}
};
await fgaClient.write({
writes: [
{"user":"user:anne","relation":"viewer","object":"document:roadmap"}
],
}, options);
```
```
options := ClientWriteOptions{
AuthorizationModelId: openfga.PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
Conflict: ClientWriteConflictOptions{
OnDuplicateWrites: CLIENT_WRITE_REQUEST_ON_DUPLICATE_WRITES_IGNORE,
},
}
body := ClientWriteRequest{
Writes: []ClientTupleKey{
{
User: "user:anne",
Relation: "viewer",
Object: "document:roadmap",
},
},
}
data, err := fgaClient.Write(context.Background()).
Body(body).
Options(options).
Execute()
if err != nil {
// .. Handle error
}
_ = data // use the response
```
```
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
Conflict = new ConflictOptions {
OnDuplicateWrites = OnDuplicateWrites.Ignore,
}
};
var body = new ClientWriteRequest() {
Writes = new List() {
new() {
User = "user:anne",
Relation = "viewer",
Object = "document:roadmap"
}
},
};
var response = await fgaClient.Write(body, options);
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"conflict": ConflictOptions(
on_duplicate_writes=ClientWriteRequestOnDuplicateWrites.IGNORE,
)
}
body = ClientWriteRequest(
writes=[
ClientTuple(
user="user:anne",
relation="viewer",
object="document:roadmap",
),
],
)
response = await fga_client.write(body, options)
```
```
var options = new ClientWriteOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA")
.onDuplicate(WriteRequestWrites.OnDuplicateEnum.IGNORE);
var body = new ClientWriteRequest()
.writes(List.of(
new ClientTupleKey()
.user("user:anne")
.relation("viewer")
._object("document:roadmap")
));
var response = fgaClient.write(body, options).get();
```
```
fga tuple write --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:anne viewer document:roadmap --on-duplicate ignore
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"writes": {
"tuple_keys": [
{
"user": "user:anne",
"relation": "viewer",
"object": "document:roadmap"
}
],
"on_duplicate": "ignore"
},
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}'
```
And deletes:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
const options = {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA",
conflict: {
onMissingDeletes: OnMissingDeletes.Ignore
}
};
await fgaClient.write({
deletes: [
{ user: 'user:anne', relation: 'owner', object: 'document:roadmap'}
],
}, options);
```
```
options := ClientWriteOptions{
AuthorizationModelId: openfga.PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
Conflict: ClientWriteConflictOptions{
OnMissingDeletes: CLIENT_WRITE_REQUEST_ON_MISSING_DELETES_IGNORE,
},
}
body := ClientWriteRequest{
Deletes: []ClientTupleKeyWithoutCondition{
{
User: "user:anne",
Relation: "owner",
Object: "document:roadmap",
},
},
}
data, err := fgaClient.Write(context.Background()).
Body(body).
Options(options).
Execute()
if err != nil {
// .. Handle error
}
_ = data // use the response
```
```
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
Conflict = new ConflictOptions {
OnMissingDeletes = OnMissingDeletes.Ignore
}
};
var body = new ClientWriteRequest() {
Deletes = new List() {
new() { User = "user:anne", Relation = "owner", Object = "document:roadmap" }
},
};
var response = await fgaClient.Write(body, options);
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"conflict": ConflictOptions(
on_missing_deletes=ClientWriteRequestOnMissingDeletes.IGNORE
)
}
body = ClientWriteRequest(
deletes=[
ClientTuple(
user="user:anne",
relation="owner",
object="document:roadmap",
),
],
)
response = await fga_client.write(body, options)
```
```
var options = new ClientWriteOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA")
.onMissing(WriteRequestDeletes.OnMissingEnum.IGNORE);
var body = new ClientWriteRequest()
.deletes(List.of(
new ClientTupleKey()
.user("user:anne")
.relation("owner")
._object("document:roadmap")
));
var response = fgaClient.write(body, options).get();
```
```
fga tuple delete --store-id=${FGA_STORE_ID} user:anne owner document:roadmap --on-missing ignore
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"deletes": {
"tuple_keys": [
{
"user": "user:anne",
"relation": "owner",
"object": "document:roadmap"
}
],
"on_missing": "ignore"
},
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}'
```
## Get Started
This is supported in the latest versions of the OpenFGA API, SDKs and CLI. Try it out and let us know what you think!
- [API Docs](https://openfga.dev/api/service#/Relationship%20Tuples/Write)
- [JavaScript SDK](https://github.com/openfga/js-sdk?tab=readme-ov-file#conflict-options-for-write-operations)
- [Go SDK](https://github.com/openfga/go-sdk?tab=readme-ov-file#conflict-options-for-write-operations)
- [.NET SDK](https://github.com/openfga/dotnet-sdk?tab=readme-ov-file#conflict-options-for-write-operations)
- [Python SDK](https://github.com/openfga/python-sdk?tab=readme-ov-file#conflict-options-for-write-operations)
- [Java SDK](https://github.com/openfga/java-sdk?tab=readme-ov-file#conflict-options-for-write-operations)
Special thanks to [@phamhieu](https://github.com/phamhieu) for his [contribution](https://github.com/openfga/js-sdk/pull/276) to the JavaScript SDK! 🙏
Learn more about [Writing Tuples in OpenFGA](https://openfga.dev/docs/getting-started/update-tuples#05-ignoring-duplicate-or-missing-tuples).
## We want your feedback!
Please reach out through our [community channels](https://openfga.dev/docs/community) with any questions or feedback.
**Tags:**
- [openfga](https://openfga.dev/blog/tags/openfga)
- [features](https://openfga.dev/blog/tags/features)
- [api](https://openfga.dev/blog/tags/api)
---
# OpenFGA Accepted into CNCF Incubation 🎉
January 7, 2026 · 6 min read
[](https://github.com/aaguiarz)
[Andres Aguiar](https://github.com/aaguiarz)
Product Manager
[](https://github.com/curfew-marathon)
[Jakub Hertyk](https://github.com/curfew-marathon)
OpenFGA has been accepted as a CNCF **Incubation** project! The Cloud Native Computing Foundation (CNCF) Technical Oversight Committee (TOC) [voted to advance OpenFGA](https://github.com/cncf/toc/issues/1287#issuecomment-3458442973) from Sandbox to Incubation status, recognizing years of community work and real-world adoption. This places OpenFGA on the same maturity path as other CNCF projects like OpenTelemetry, Keycloak, Artifact Hub, and Backstage. Learn more at the CNCF joint announcement: [OpenFGA becomes a CNCF Incubating Project](https://www.cncf.io/blog/2025/11/11/openfga-becomes-a-cncf-incubating-project/).
## Why Incubation Matters
Incubation signals that OpenFGA is production-ready with a healthy, diverse contributor base and real-world adoption at scale. For organizations evaluating OpenFGA, this milestone validates the project's maturity, governance, and long-term sustainability. The CNCF due diligence process assessed our security posture, documentation, community health, and adoption metrics—all meeting the standards required for broad enterprise use.
Learn more about [CNCF project stages](https://www.cncf.io/projects/) and review our [due diligence documentation](https://github.com/cncf/toc/blob/main/projects/openfga/openfga-incubation-dd.md).
## Technical Maturity Since Sandbox
Since joining CNCF as a Sandbox project in December 2022, OpenFGA has evolved significantly:
- **Performance improvements**: Substantial optimizations in query execution and caching, enabling sub-millisecond authorization checks at scale
- **Enhanced capabilities**: Introduction of conditional tuples, modular models, and list users functionality to support more complex authorization scenarios
- **Expanded ecosystem**: New SDKs, database adapters (including SQLite), Terraform provider, and IDE plugins
- **Production hardening**: Improved observability, configuration options, and operational tooling based on real-world deployment feedback
- **Security posture**: Comprehensive security assessments, vulnerability management processes, and regular dependency updates
These improvements reflect our commitment to building a reliable, performant authorization system ready for enterprise production use.
## How We Got Here
OpenFGA was open sourced in June 2022 and accepted as a CNCF Sandbox project in December 2022. Since then, we've seen incredible community support:
- Regular community meetings since 2022, available on [our YouTube channel](https://www.youtube.com/@OpenFGA), along with more than 40 presentations from community members.
- Contributions from the community—whether through questions, feedback, feature requests, PRs, bug reports, or guides and tools built around OpenFGA.
- [Grafana Labs](https://grafana.com/) joined the maintainer team.
- 600+ contributors across the OpenFGA organization.
- 5,000+ GitHub stars across our repositories.
- Presence at six consecutive KubeCon conferences (US and EU) with breakout sessions and a kiosk.
- Production adopters include [Canonical](https://canonical.com/), [Docker](https://www.docker.com), [Grafana Labs](https://grafana.com), [Read AI](https://read.ai/), [Agicap](https://agicap.com), [Headspace](https://headspace.com), [Zuplo](https://zuplo.com), [Sourcegraph](https://sourcegraph.com/), [OpenObserve](https://openobserve.ai/) and [LakeKeeper](https://lakekeeper.io/) along with [many others](https://github.com/openfga/community/blob/main/ADOPTERS.md).
- Several adopters [went on the record](https://github.com/cncf/toc/tree/main/projects/openfga) in interviews with CNCF around their OpenFGA experience through the due diligence project.
## Thank You
OpenFGA's success is the result of contributions from many individuals and organizations. We want to highlight a few who made significant impact:
### Community Contributors
- [Massimiliano Gori](https://www.linkedin.com/in/massi-gori) believed in OpenFGA early and led integration across Canonical.
- [Pauline Jamin](https://www.linkedin.com/in/paulinejamin) spearheaded adoption at [Agicap](https://agicap.com) and presented their learnings at [KubeCon Europe 2024](https://colocatedeventseu2024.sched.com/event/1YFhM/implementing-modern-cloud-native-authorization-using-openfga-andres-aguiar-okta-pauline-jamin-agicap).
- [JT aka Hawxy](https://github.com/Hawxy) has been maintaining [Fga.net](https://github.com/Hawxy/Fga.Net) for years, and has been graciously providing us feedback since.
- [Andrew Powers](https://www.linkedin.com/in/andrew-powers-geo) led [Read AI](https://www.read.ai/) implementation supporting collaboration and RAG for enterprise search.
- [Joao Guerreiro](https://www.linkedin.com/in/joguer) led the implementation at [Grafana](https://grafana.com/) and presented their journey at [KubeCon Europe 2025](https://kccnceu2025.sched.com/event/1txIJ/from-chaos-to-control-migrating-access-control-to-openfga-in-a-multi-tenant-world-jo-guerreiro-grafana-labs-poovamraj-thanganadar-thiagarajan-okta).
- [Dan Cech](https://www.linkedin.com/in/dancech) from [Grafana](https://grafana.com/) contributed the SQLite adapter and serves as a maintainer.
- [Nathan Totten](https://www.linkedin.com/in/nathantotten) led [Zuplo](https://zuplo.com/) integration, implementing [authorization at the edge](https://zuplo.com/examples/openfga) for the API gateway.
- [Gurleen Sethi](https://www.linkedin.com/in/gurleensethi) led the implementation of organization and team management at [Docker, Inc](https://www.docker.com/).
- [Siddhant Khare](https://github.com/Siddhant-K-code) was the first independent contributor who onboarded as a maintainer of the OpenFGA project.
- [Maurice Ackel](https://github.com/mauriceackel) donated the [OpenFGA Terraform Provider](https://registry.terraform.io/providers/openfga/openfga/latest/docs), and joined as a maintainer.
- [Yann D'Isanto](https://www.linkedin.com/in/yann-d-19851110) from [Agicap](https://agicap.com), who contributed the JetBrains IDE plugin.
- [Martin Besozzi](https://www.linkedin.com/in/embesozzi) has been involved in the OpenFGA community from the beginning, driving integrations with [Keycloak](https://www.keycloak.org/).
### CNCF Support
- [Chris Aniszczyk](https://www.linkedin.com/in/caniszczyk) helped guide the project donation and acceptance.
- [Eddie Knight](https://www.linkedin.com/in/knight1776), [Evan Anderson](https://www.linkedin.com/in/evankanderson), [Marina Moore](https://www.linkedin.com/in/marina-moore-5a7242105) and [Justin Cappos](https://github.com/JustinCappos) from [CNCF TAG Security](https://tag-security.cncf.io/) supported the self-assessment and helped strengthen our posture for due diligence.
- [Karena Angell](https://www.linkedin.com/in/karenaangell) and [Faseela K](https://www.linkedin.com/in/faseela-k-42178528) helped us navigate the due diligence process and carried much of the heavy lift to make it happen.
## What's Next
Incubation is a milestone, not a destination. Our focus continues on:
- **Performance**: Optimizing latency and throughput for large-scale deployments
- **Developer Experience**: Enhanced tooling, IDE plugins, and debugging capabilities
- **Integrations**: Expanding our ecosystem with more identity providers, frameworks, and platforms
- **Documentation**: Comprehensive guides, tutorials, and real-world implementation patterns
- **Governance**: Strengthening our contributor pathways and security practices
Our next milestone: CNCF Graduation. Track our progress on the [project roadmap](https://github.com/orgs/openfga/projects/1).
## Get Involved
This achievement belongs to every contributor, user, and community member who has supported OpenFGA. Thank you for being part of our journey!
**New to OpenFGA?** Start here:
- [Try OpenFGA locally](https://openfga.dev/docs/getting-started) and explore the documentation
- [Join our CNCF Slack community](https://openfga.dev/community) to ask questions and connect with users
**Already using OpenFGA?**
- [Star the repo](https://github.com/openfga/openfga) and follow development
- Share your adoption story—add your organization to our [ADOPTERS.md](https://github.com/openfga/community/blob/main/ADOPTERS.md)
- Check out the [roadmap](https://github.com/orgs/openfga/projects/1) and contribute to upcoming features
**Tags:**
- [announcement](https://openfga.dev/blog/tags/announcement)
---
# Join the OpenFGA team at KubeCon NA 2023
October 12, 2023 · 2 min read
[](https://github.com/aaguiarz)
[Andres Aguiar](https://github.com/aaguiarz)
Product Manager
As you'd expect, the OpenFGA team will be at KubeCon NA 2023 in Chicago, IL!
We'll have a packed agenda for the week:
- [Jonathan Whitaker](https://www.linkedin.com/in/jonathan-whitaker-5a8b2484/) and [Lucas Käldström](https://www.linkedin.com/in/luxas/) will be presenting in [Could\_Native Rejects](https://cloud-native.rejekts.io/) on how to use OpenFGA to manage and extend authorization in Kubernetes. Learn more [here](https://cfp.cloud-native.rejekts.io/cloud-native-rejekts-na-chicago-2023/speaker/XB7EUR/).
- [Maria Ines Parnisari](https://www.linkedin.com/in/miparnisari/) and [Andres Aguiar](https://www.linkedin.com/in/aaguiar/) will be presenting in [AppDeveloperCon](https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/co-located-events/appdevelopercon/) about modernizing authorization for cloud native applications using OpenFGA. Learn more [here](https://colocatedeventsna2023.sched.com/event/1Rj2j/modernizing-authorization-for-cloud-native-applications-using-openfga-andres-aguiar-maria-ines-parnisari-okta).
- We'll host a Project Meeting on Monday 9.30 AM in the Hudson room at the [Hilton Garden Inn](https://maps.app.goo.gl/77FwgGdpsWK5jWHd6). We'll share how the product is being used, demo the latests features like our new CLI, the VS Code Extension, Conditional Relationships, the Java SDK... and more!
- We'll be in the CNCF Project Pavilion during the afternoons.
- We'll host our [OpenFGA community meeting](https://github.com/openfga/community/blob/main/community-meetings.md) directly from KubeCon on Thursday 9th at 3PM UTC (8AM PST/11AM EST).
If you want to meet with the team outside of these events, please pick any spot that works for you in our [calendar](https://calendar.app.google/GonEwLboKvPkG8pL6).
See you in Chicago!
**Tags:**
- [conferences](https://openfga.dev/blog/tags/conferences)
- [kubecon](https://openfga.dev/blog/tags/kubecon)
---
# List Users API
May 30, 2024 · 2 min read
[](https://github.com/miparnisari)
[Maria Ines Parnisari](https://github.com/miparnisari)
Today we are launching a new API for OpenFGA: ListUsers.
This API will answer the question "what users have relation X with object Y?". This will be useful, for example, in UIs that want to display the list of users that a resource has been shared with, e.g. the "share" dialog in Google Docs.
You can read more about it in the [API docs](https://openfga.dev/api/service#/Relationship%20Queries/ListUsers) and the [product documentation](https://openfga.dev/docs/getting-started/perform-list-users).
## How to use it?
ListUsers is available in OpenFGA starting with [v1.5.4](https://github.com/openfga/openfga/releases/tag/v1.5.4).
To be able to call this API, you must turn on this flag on the server: `--experimentals enable-list-users`. Be sure to also check out the various configuration flags that were added to control its behavior.
The new functionality is available on the latest versions of the [Java](https://github.com/openfga/java-sdk/), [.NET](https://github.com/openfga/dotnet-sdk/), [Go](https://github.com/openfga/go-sdk/) and [Javascript SDK](https://github.com/openfga/js-sdk/), [CLI](https://github.com/openfga/cli?tab=readme-ov-file#list-users) and [VS Code integration](https://marketplace.visualstudio.com/items?itemName=openfga.openfga-vscode).
We'll be releasing support for the Python SDK soon.
## We want your feedback!
We want to learn how you use this API and how we can improve it!
Please reach out through our [community channels](https://openfga.dev/community) with any questions or feedback.
**Tags:**
- [openfga](https://openfga.dev/blog/tags/openfga)
- [features](https://openfga.dev/blog/tags/features)
---
# Modular Models
April 25, 2024 · 2 min read
[](https://github.com/ewanharris)
[Ewan Harris](https://github.com/ewanharris)
Modular models aims to improve the model authoring experience when multiple teams are maintaining a model, such as:
- A model can grow large and difficult to understand
- As more teams begin to contribute to a model, the ownership boundaries may not be clear and code review processes might not scale
With modular models, a single model can be separated across multiple files allow grouping of types and conditions into modules. This means that a model can be organized more easily in terms of team or organizational structure. Used in conjunction with features such as [GitHub](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners), [GitLab](https://docs.gitlab.com/ee/user/project/codeowners/) or [Gitea's](https://docs.gitea.com/usage/repository/code-owners) code owners, it should become easier to ensure the owners of a portion of your model are correctly assigned to review it.
## How to use it?
Modular models is available in the latest version of OpenFGA. To use it you need to:
- Update to the [v0.3.0 release](https://github.com/openfga/cli/releases/tag/v0.3.0) of the CLI
- Update to [v0.2.21](https://github.com/openfga/vscode-ext/releases/tag/v0.2.21) of the VS Code Extension
- Download [v1.5.3](https://github.com/openfga/openfga/releases/tag/v1.5.3) of OpenFGA
- Check out the modular models sample store in the [sample-stores repo](https://github.com/openfga/sample-stores/tree/main/stores/modular)
- Review the [documentation for this feature](https://openfga.dev/docs/modeling/modular-models)
- Check a [demo video in Youtube](https://youtu.be/oeqroL8-wCQ)
## What's next?
Looking beyond the near term, modular models allows us to implement [additional API authorization options for OpenFGA](https://github.com/openfga/roadmap/issues/30).
## Reach out!
We want to learn how you use this feature and how we can improve it!
Please reach out through our [community channels](https://openfga.dev/community) with any questions or feedback.
**Tags:**
- [openfga](https://openfga.dev/blog/tags/openfga)
- [features](https://openfga.dev/blog/tags/features)
---
# Query Consistency Options in OpenFGA
July 30, 2024 · 2 min read
[](https://github.com/aaguiarz)
[Andres Aguiar](https://github.com/aaguiarz)
Product Manager
OpenFGA query APIs now allow specifying the desired consistency of query results. By default, OpenFGA does not use a cache. However, when caching is enabled, it applies to all requests. This means that any changes in permissions won't be reflected in authorization checks during the cache TTL period.
The community expressed the need for flexibility in using the cache on a per-request basis. In response, starting with [OpenFGA v1.5.7](https://github.com/openfga/openfga/releases/tag/v1.5.7), all query APIs can accept a consistency parameter with the following values:
| Name | Description |
| --------------------------- | ------------------------------------------------------------------------------- |
| MINIMIZE\_LATENCY (default) | OpenFGA will try to minimize latency (e.g. by making use of the cache) |
| HIGHER\_CONSISTENCY | OpenFGA will try to optimize for stronger consistency (e.g. by bypassing cache) |
When `HIGHER_CONSISTENCY` is specified, OpenFGA reads directly from the database, even when the cache is enabled.
## How to use it?
The new consistency parameter is available in OpenFGA starting [v1.5.7](https://github.com/openfga/openfga/releases/tag/v1.5.7).
The parameter is supported by all OpenFGA SDKs.
For more information on enabling the cache and best practices for specifying consistency values, refer to the [documentation](https://openfga.dev/docs/interacting/consistency).
## Custom database adapter implementations
For those with a custom database adapter for a multi-region database, the behavior of the HIGHER\_CONSISTENCY parameter can be defined according to your needs. With an eventually consistent database (e.g., Dynamo DB) in a multi-region setup, there will be replication lag even if the cache is bypassed. If the database supports strong reads, you can choose to perform those at an extra cost. Otherwise, you can perform an eventually consistent read without providing full consistency semantics to the caller. In some other databases where you have Read/Write replicas, you may choose to go to the Write replica when the `HIGHER_CONSISTENCY` preference is selected.
## Future work
[Google Zanzibar](https://zanzibar.academy) features a consistency token called `Zookies`, returned from write operations. This token can be stored in a resource table and specified in subsequent query API calls. We are considering introducing a similar feature in future releases.
## We want your feedback!
We want to learn how you use this API and how we can improve it!
Please reach out through our [community channels](https://openfga.dev/community) with any questions or feedback.
**Tags:**
- [openfga](https://openfga.dev/blog/tags/openfga)
- [features](https://openfga.dev/blog/tags/features)
---
# OpenFGA's Move to Weighted Graph Resolution: What's Changing
July 21, 2026 · 11 min read
[](https://github.com/tylernix)
[Tyler Nix](https://github.com/tylernix)
Product Manager
OpenFGA is continuing to roll out a **weighted graph-based resolution algorithm** across its core query endpoints — Check, BatchCheck, ListObjects, Expand, and ListUsers. ListObjects already runs on this updated algorithm, and Check is next. As a precaution, all core query endpoints currently fall back to the legacy algorithm for models that are incompatible with the weighted graph, but **that fallback option will be removed soon**. A date for the final changeover has not been set at this time.
This post explains which modeling and check patterns are incompatible with the weighted graph algorithm, and how to migrate before the fallback is removed.
## Why a Weighted Graph?
The legacy Check algorithm resolves authorization queries by recursively traversing relation definitions at _request time_. While functional, this approach has limitations:
- **Unpredictable resource usage**: A shallow recursive graph can be more resource-intensive than a deep linear one, but the old algorithm used a fixed depth limit of 25 as its only complexity guard.
- **Non-deterministic error handling**: Errors in one branch of a union could halt evaluation of other valid branches.
The new approach shifts some resolution work earlier to _build time_: when a model is saved, the weighted graph is built and sub-graph weights are calculated for every relation. These weights reflect the relative complexity of traversing each part of the graph. At request time, the algorithm uses those pre-calculated weights to traverse the graph more efficiently, prioritizing lower-cost paths and managing load without arbitrary depth limits.
A key consequence of this shift is that **if a model cannot be resolved, it will not be built**. Problems that previously surfaced at query time are now caught at model validation time, before any requests are made.
The benefits, however, are:
1. **Better performance** — resolution paths are informed by pre-calculated weights rather than discovered through live traversal.
2. **Dynamic load management** — datastore throttling, graph flattening, and context cancellation replace the fixed depth limit of 25.
3. **Consistent short-circuit evaluation** — failing fast on intersection/exclusion errors while being resilient to errors in union branches.
4. **Determinism by construction** — patterns that cannot be reliably resolved are rejected when the model is saved, not when a user makes a request.
## What's Changing
### Model Build Errors
The following model patterns are incompatible with the weighted graph algorithm. Today, requests using these models fall back to the legacy algorithm. When the fallback is eventually removed, these models will fail to build and return a model build error.
#### 1. Missing Relation
When a relation uses `relation from parent` and the `parent` relation allows multiple types, **every type must define the referenced relation**. Previously, a TTU was resolved at query time. If a relation didn’t exist, it would skip it and return no results (`allowed: false`). The new weighted graph builds the complete resolution graph at model build time. When the relation for a TTU doesn’t exist, there’s no node to connect, and the graph fails to build.
**Broken pattern:**
```
type organization
relations
define member: [user]
type folder
relations
define viewer: [user]
# ❌ Missing: member is not defined here
type document
relations
define parent: [organization, folder]
define viewer: member from parent # but folder has no member, only viewer!
```
**Why this fails:** The weighted graph cannot build a graph with unreachable nodes. Since `member` does not exist on `folder`, when the `document.viewer` part of the graph attempts to reach all the `member` relations in `parent`, it fails and does not build.
**Fix 1:** Add the missing relation to the type. If the type has an equivalent role, you can alias it:
```
type folder
relations
define viewer: [user]
define member: viewer # alias of viewer
```
> **Migration impact:** No tuple changes required. The model change is additive: existing `viewer` tuples on `folder` objects continue to work, and the new `member` alias picks them up automatically. No check call changes needed either, since `member from parent` already resolves through the new `member` relation.
**Fix 2:** Alternatively, you can separate out the `parent` relation in `type document` into explicit per-type relations. This is more verbose but makes the intent explicit in the model.
```
type document
relations
define organization: [organization]
define folder: [folder]
define viewer: member from organization or viewer from folder
```
> **Migration impact:** This is a breaking tuple change. All existing `(folder:x, parent, document:y)` and `(organization:x, parent, document:y)` tuples must be deleted and rewritten as `(folder:x, folder, document:y)` and `(organization:x, organization, document:y)` respectively. Check calls that reference `parent` also need to be updated if your application uses that relation directly.
#### 2. Tuple Cycles in Intersection or Exclusion
Recursive relations (like nested group membership) that use `and` or `but not` create cycles that cannot be resolved deterministically.
**Broken pattern (AND):**
```
type group
relations
define approved: [user, group#member]
define member: [user, group#member] and approved # ❌ Cycle with AND
```
**Broken pattern (BUT NOT):**
```
type group
relations
define blocked: [user, group#member]
define member: [user, group#member] but not blocked # ❌ Cycle with BUT NOT
```
**Why this fails:** To check if a user is a `member`, the system must resolve `group#member` (recursion) while simultaneously checking the `and`/`but not` condition, which itself depends on `member` resolution. This creates a circular dependency.
**Fix:** Split into a "base" relation (allows recursion) and an "allowed" relation (applies the access gate):
```
type group
relations
define blocked: [user, group#member]
define member: [user, group#member] # Pure union recursion (allowed)
define allowed_member: member but not blocked # Exclusion at leaf level only
```
> **Migration impact:** Your existing `member` and `blocked` tuples stay exactly as they are; no tuple writes or deletes needed. The only change is in how your application calls Check: replace `check(user, "member", object)` with `check(user, "allowed_member", object)`. The new `allowed_member` relation reads from the same underlying data, just with the exclusion gate applied at the right level.
### Check Request Errors
#### 3. Userset or Wildcard Requests with Exclusion
Sometimes you want to ask "Does this whole group have access?" (i.e. Userset `document:contract#owner`) or "Does everyone have access?" (i.e. Wildcard `user:*`) by passing the group reference or wildcard as the user in a check call. However, when the relation being checked uses `but not`, OpenFGA can't reliably answer that question. To be sure, it would need to check every individual in that group or every possible user to confirm none of them are excluded. Rather than guess, it will return an **error**.
**Userset example:**
```
type document
relations
define owner: [user]
define member: [user]
define viewer: [user, document#owner] but not member
```
```
check("document:contract#owner", "viewer", "document:report")
# ❌ Error — can't confirm every owner passes the exclusion
```
**Wildcard example:**
```
type document
relations
define public: [user:*]
define blocked: [user]
define viewer: public but not blocked
```
```
check("user:*", "viewer", "document:readme")
# ❌ Error — can't confirm no one in user:* is blocked
```
**Fix:** Check specific users individually instead:
```
check("user:alice", "viewer", "document:report") # ✓ — userset exclusion applied correctly per user
check("user:alice", "viewer", "document:readme") # ✓ — wildcard exclusion applied correctly per user
```
***
### Check Resolution Changes
These changes affect the same Check request pattern as #3: passing a group reference (e.g., userset `document:d1#viewer`) as the user. But in these scenarios, the request does not error; it completes, but may return a different answer than before. Since most applications check access for a specific user (`user:alice`), these scenarios are rare and most applications likely won't encounter them, but are still worth mentioning.
#### 4. Userset Must Exist
When you write a tuple granting a group access to something, the relation name used in the check must use the same as the tuple written. If your model defines two names as equivalent (i.e. aliases), that equivalence is not used when looking up group access. Only the exact name stored in the tuple is used.
**What was broken:** The legacy algorithm would follow aliases in the model at resolution time by inferring they were equivalent and bridging the gap. The issue appears when subtly renaming a relation or restructuring an alias, silently changing what access checks returned.
**What is changing:** The weighted graph resolves this by making stored tuples the authoritative source of truth. Alias traversal is no longer performed during a check — what's stored is what counts. This makes access decisions deterministic and independent of how relations happen to be defined at check time.
```
type document
relations
define reader: [user]
define allowed: reader # allowed is an alias for reader
define viewer: [user, document#allowed]
```
```
# Stored tuple: {document:source#allowed, viewer, document:target}
check("document:source#allowed", "viewer", "document:target")
# ✓ Returns TRUE — matches what's stored
check("document:source#reader", "viewer", "document:target")
# ❌ Returns FALSE (when it previously would return TRUE) — the stored tuple only uses #allowed, not #reader.
```
**Fix:** Check using the exact relation name stored in the tuple. Alias relation traversal at Check time is no longer supported. Each relation is now treated as distinct.
```
write(document:source#allowed, viewer, document:target) # Already stored
```
> **Migration impact:**
>
> 1. First, check whether your model has any relations that accept a group reference as a value. Look for type lists that include `type:object#relation` (e.g., `define viewer: [user, document#allowed]`). If none of your relations accept group references, this change does not affect you.
> 2. If they do exist in your model, audit your check calls for any that pass a group reference as the user. Verify that the relation name in the check matches the relation name used in the model. If your application relied on alias inference (checking with `#reader` when `#allowed` was stored), update those calls to use the stored relation name.
#### 5. Self-Referential Usersets
**What was broken:** Previously, asking "do the viewers of document A have access to document A?" like `check("document:A#viewer", "viewer", "document:A")`, the legacy algorithm always evaluated `TRUE`, just because the relation existed in the model and the `user_id` and `object_id` in the query matched, not because an actual tuple granted that access.
**What is changing:** The weighted graph requires both schema and data to grant access. A relation existing in the model is not sufficient evidence that a group has access to an object. Now it returns `FALSE` unless actual tuples exist to support the access decision. The weighted graph algorithm requires both schema and data, not schema alone.
This can be represented in three scenarios:
**Scenario A — Direct relation:**
```
type document
relations
define viewer: [user]
```
```
check("document:d1#viewer", "viewer", "document:d1")
# ❌ OLD: TRUE (just because the viewer relation exists on type document)
# ✓ NEW: FALSE (since no tuple grants document:d1#viewer access to document:d1)
```
**Scenario B — Computed relation:**
```
type document
relations
define editor: [user]
define writer: [user]
define viewer: editor or writer
```
```
check("document:d1#writer", "viewer", "document:d1")
# ❌ OLD: TRUE (model has viewer = editor or writer, and writer exists on type document)
# ✓ NEW: FALSE (since no tuple grants document:d1#writer as a viewer of document:d1)
```
**Scenario C — TTU (tuple-to-userset) relation:**
```
type folder
relations
define viewer: [user]
type document
relations
define parent: [folder]
define viewer: viewer from parent
```
```
# Given tuple: (folder:f2, parent, document:d1)
check("folder:f2#viewer", "viewer", "document:d1")
# ❌ OLD: TRUE (the parent tuple exists and the schema connects them)
# ✓ NEW: FALSE (since no explicit userset tuple stores folder:f2#viewer as viewer of document:d1)
```
**Fix:** Check individual users instead of self-referential group references:
```
check("user:alice", "viewer", "document:d1") # ✓ Checks actual data
```
Or use ListUsers to discover who has the relation:
```
listUsers("document:d1", "viewer") → [user:alice, user:bob]
```
> **Migration impact:** This self-referential userset pattern (asking whether a group defined on an object has access to that same object) is uncommon. Search your application for check calls where the user field is a group reference and the object and the group reference share the same type and ID. If you find any, replace them with checks against specific users, or use ListUsers to find who actually has the relation.
***
## Timeline
**Now**:
- ListObjects runs on the weighted graph algorithm, with a fallback to the legacy algorithm for incompatible models.
**Next**:
- All core query endpoints will transition to the weighted graph algorithm as default, with a fallback available for a period of time.
- A CLI command `fga model validate` to test if your model contains any issues and needs a migration will be coming soon.
- We will be announcing a final deadline for migrating incompatible models, after which new OpenFGA versions will only support the weighted graph algorithm.
**Later**:
- Weighted graph algorithm is enforced for all core query endpoints, and the fallback algorithm is removed. Incompatible models will fail to build and requests will be rejected.
## Get Help
We want to hear from you. If these changes affect your deployment, reach out in our community channels and we'll help you migrate.
- [OpenFGA Community Slack](https://openfga.dev/docs/community)
- [GitHub Discussions](https://github.com/orgs/openfga/discussions)
**Tags:**
- [announcement](https://openfga.dev/blog/tags/announcement)
- [check](https://openfga.dev/blog/tags/check)
- [migration](https://openfga.dev/blog/tags/migration)
---
# OpenFGA Project
OpenFGA is an open source, [CNCF Incubating project](https://www.cncf.io/projects/openfga/) for building fine-grained authorization systems.
## Why OpenFGA exists
Modern applications need permissions that can express relationships between users and resources, adapt as products evolve, and remain consistent across services. OpenFGA provides a centralized authorization system with an easy-to-read modeling language and consistent APIs, so application teams do not have to build and maintain a separate permissions system for every product.
OpenFGA was inspired by [Google's Zanzibar paper](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/). It was originally developed by Auth0/Okta, open sourced in June 2022, and donated to the Cloud Native Computing Foundation. OpenFGA joined CNCF as a Sandbox project in 2022 and moved to the Incubating maturity level on October 28, 2025. Read more about the project's history in the [Incubation announcement](https://openfga.dev/blog/incubation-announcement.md).
## Where project work happens
| Area | Repository or resource | What you will find there |
| ------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Server | [`openfga/openfga`](https://github.com/openfga/openfga) | The OpenFGA server, release artifacts, runtime issues, and feature development. |
| API | [`openfga/api`](https://github.com/openfga/api) | The API definition shared by the server, SDKs, and other tooling. |
| Website and documentation | [`openfga/openfga.dev`](https://github.com/openfga/openfga.dev) | This website, guides, modeling examples, and API documentation. |
| Design proposals | [`openfga/rfcs`](https://github.com/openfga/rfcs) | Requests for Comments for significant changes to OpenFGA. |
| Community and governance | [`openfga/community`](https://github.com/openfga/community) | Governance, maintainers, adopters, community projects, and meeting information. |
| Roadmap | [OpenFGA roadmap](https://github.com/orgs/openfga/projects/1) | Planned and in-progress project work. |
| SDKs and tools | [OpenFGA repositories](https://github.com/orgs/openfga/repositories) | Official SDKs, the CLI, IDE extensions, the Terraform provider, and other integrations. |
| Example models | [`openfga/sample-stores`](https://github.com/openfga/sample-stores) | Sample authorization models and a list of models used by open source projects. |
## Get OpenFGA
Follow the [OpenFGA setup guide](https://openfga.dev/docs/getting-started/setup-openfga/overview.md) to run the server with Docker, Docker Compose, or Kubernetes. You can also download a binary from the [OpenFGA releases](https://github.com/openfga/openfga/releases) page.
## Ask questions and give feedback
Use the kapa.ai-powered **Ask AI** widget in the site navigation to ask questions about OpenFGA. For AI-assisted implementation, install the [OpenFGA Best Practices Skill](https://github.com/openfga/agent-skills) or connect an MCP-compatible client to the [OpenFGA kapa.ai MCP server](https://openfga.mcp.kapa.ai). AI-generated answers may be inaccurate, so verify them against the project documentation and code.
- Ask usage questions and suggest product ideas in [GitHub Discussions](https://github.com/orgs/openfga/discussions).
- Join the [`#openfga` channel in CNCF Slack](https://openfga.dev/docs/community.md) to talk with users and maintainers or attend a community meeting.
- Report server bugs in the [`openfga/openfga` issue tracker](https://github.com/openfga/openfga/issues), and report website or documentation problems in the [`openfga/openfga.dev` issue tracker](https://github.com/openfga/openfga.dev/issues).
When reporting a problem, include a minimal example or [FGA Playground](https://play.fga.dev/) link, the result you expected, the result you observed, and the steps you already tried.
## Contribute
Contributions of code, documentation, issue triage, RFC feedback, examples, and community support are all welcome.
1. Read the [contribution guide](https://github.com/openfga/.github/blob/main/CONTRIBUTING.md) and [Code of Conduct](https://github.com/openfga/.github/blob/main/CODE_OF_CONDUCT.md).
2. Look for an issue in the relevant repository, including issues labeled [`good first issue`](https://github.com/search?q=org%3Aopenfga+is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22\&type=issues).
3. For a substantial design change, start a discussion with the maintainers and review the [RFC process](https://github.com/openfga/rfcs).
4. Fork the affected repository, make and test the change, and open a pull request.
## Report a security vulnerability
Do not disclose security vulnerabilities in a public issue or discussion. Email and review the [OpenFGA security policy](https://github.com/openfga/.github/blob/main/SECURITY.md) for scope and reporting guidance. The project strives to reply within five business days.
## Thank you
OpenFGA was created by the Auth0 FGA team with support from Auth0 Labs and Auth0's Office of the CTO, and its development continued at Okta before the project was donated to CNCF. We are grateful to the people who shaped the project there, to the CNCF community, and to every maintainer, contributor, early design partner, and adopter who has shared code and feedback.
The [Incubation announcement](https://openfga.dev/blog/incubation-announcement.md#thank-you) recognizes many of the people and organizations that helped OpenFGA reach this milestone. The community repository maintains the broader list of [OpenFGA adopters](https://github.com/openfga/community/blob/main/ADOPTERS.md).
---
# 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) and OpenFGA community meeting presentations.
## Featured case studies
| Adopter | Industry | In production since | Scale |
| ----------------------------------------------------------------- | ------------------------ | ------------------- | ------------------------------------------------ |
| [Read AI](https://openfga.dev/docs/adopters/read-ai.md) | AI meeting intelligence | April 2023 | 5,200 RPS peak, 5.3B+ tuples |
| [Agicap](https://openfga.dev/docs/adopters/agicap.md) | Fintech | April 2023 | \~250 RPS, 8,000+ customers |
| [Zuplo](https://openfga.dev/docs/adopters/zuplo.md) | API management | 2024 | 500+ RPS spikes, multi-region edge |
| [Grafana Labs](https://openfga.dev/docs/adopters/grafana.md) | Observability | 2024 | Multi-tenant SaaS + embedded OSS |
| [Docker](https://openfga.dev/docs/adopters/docker.md) | Developer tools | March 2024 | 100-150 RPS |
| [Headspace](https://openfga.dev/docs/adopters/headspace.md) | Mental health & consumer | 2024 | 90M lives, 6M Ebb messages, 10-15 ms p99 |
| [OpenLane](https://openfga.dev/docs/adopters/openlane.md) | Compliance SaaS | 2024 | ent ORM hooks, BatchCheck overfetch (100/1000) |
| [Vitrolife Group](https://openfga.dev/docs/adopters/vitrolife.md) | 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](https://openfga.dev/docs/authorization-concepts.md) 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 [Wolt](https://wolt.com), [Canonical](https://canonical.com), and many more. The full 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).
---
# 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](https://openfga.dev/docs/authorization-concepts.md).
## 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](https://openfga.dev/docs/best-practices.md).
## 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).
---
# 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](https://openfga.dev/docs/best-practices.md).
## 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).
---
# 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](https://openfga.dev/docs/best-practices.md).
## 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).
---
# 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](https://openfga.dev/docs/interacting/relationship-queries.md) 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).
---
# 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](https://openfga.dev/docs/getting-started/perform-list-objects.md) 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](https://openfga.dev/docs/getting-started/perform-check.md#03-calling-batch-check-api) 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 — `FGAX` (typed helpers around the OpenFGA SDK), [`entfga`](https://github.com/theopenlane/iam/tree/main/entfga) (ent hooks that write tuples), 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).
---
# 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).
---
# 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](https://openfga.dev/docs/interacting/relationship-queries.md) 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).
---
# 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 |
| **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).
---
# Authorization Concepts
## Authentication and Authorization
[Authentication](https://en.wikipedia.org/wiki/Authentication) ensures a user's identity. [Authorization](https://en.wikipedia.org/wiki/Authorization) determines if a user can perform a certain action on a particular resource.
For example, when you log in to Google, Authentication is the process of verifying that your username and password are correct. Authorization is the process of ensuring that you can access a given Google service or feature.
## What is Fine-Grained Authorization?
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.
See [Fine-Grained Authorization](https://openfga.dev/docs/learn/fine-grained-authorization.md) 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 roles like `editor` or `admin`. RBAC fits flat, single-tenant access models but breaks down with hierarchy, sharing, or multi-tenancy.
See [RBAC vs. ReBAC](https://openfga.dev/docs/learn/rbac-vs-rebac.md) for when roles run out and how OpenFGA 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 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.
See [ABAC vs. ReBAC](https://openfga.dev/docs/learn/abac-vs-rebac.md) for how the two combine.
## What is Policy-Based Access Control?
Policy-Based Access Control (PBAC) manages authorization policies centrally, external to application code. Most ABAC implementations are also PBAC. OpenFGA's [model DSL](https://openfga.dev/docs/configuration-language.md) is itself a policy: committed to Git, reviewed via PR, deployed like any other code — see [Policy Engines vs. Relationship Engines](https://openfga.dev/docs/learn/policy-engine.md).
## What is Relationship-Based Access Control?
[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. OpenFGA extends ReBAC with [Conditions](https://openfga.dev/docs/modeling/conditions.md) and [Contextual Tuples](https://openfga.dev/docs/modeling/token-claims-contextual-tuples.md) for the remaining attribute-driven cases.
See [What is ReBAC?](https://openfga.dev/docs/learn/rebac.md) for the full picture.
## What is Zanzibar?
[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. OpenFGA implements the Zanzibar model on your existing databases.
See [What is Zanzibar?](https://openfga.dev/docs/learn/zanzibar.md) for what the paper introduced and how OpenFGA maps to it.
Learn about OpenFGA.
**OpenFGA Concepts**
Learn about the OpenFGA Concepts
- [More](https://openfga.dev/docs/concepts.md)
**Modeling: Getting Started**
Learn about how to get started with modeling your permission system in OpenFGA.
- [More](https://openfga.dev/docs/getting-started.md)
---
# OpenFGA Best Practices
This section contains a collection of best practices when implementing OpenFGA.
**Running OpenFGA in Production**
Learn about best practices for running OpenFGA in production environments.
- [More](https://openfga.dev/docs/best-practices/running-in-production.md)
**Adoption Patterns**
Learn the best ways to introduce OpenFGA in your project.
- [More](https://openfga.dev/docs/best-practices/adoption-patterns.md)
---
# OpenFGA Adoption Patterns
This document outlines key implementation patterns for adopting OpenFGA in your organization.
## Starting with coarse-grained access control
When evaluating this solution, many companies start by replicating their existing permissions structure before moving to more granular controls. For example, if you're using Role-Based Access Control (RBAC) in a B2B scenario, you might start with a simple model:
```
model
schema 1.1
type user
type organization
relations
define admin: [user]
define member: [user]
# .. add additional organization roles
# map permissions to organization roles
define can_add_member: admin
define can_delete_member: admin
define can_view_member: admin or member
define can_add_resource: admin or member
```
You can define any number of roles for the organization type and then define the permissions based on those roles. You can then check if users have a specific permission at the organization level by calling the Check API on the organization object:
```
Check(user: "user:anne", relation: "can_add_member", object: "organization:acme")
```
A better implementation is to define the application's resource types in the model (e.g. documents, projects, insurance policies, bank accounts, etc):
```
model
schema 1.1
type user
type organization
relations
define admin: [user]
define member: [user]
define can_add_member: admin
define can_delete_member: admin
define can_view_member: admin or member
define can_add_resource: admin or member
type resource
relations
define organization: [organization]
# map resource permissions to organization roles
define can_delete_resource: admin from organization or member from organization
define can_view_resource: admin from organization or member from organization
```
In this case, you'll need to write tuples that establish the relationship between resource instances and organizations, or use Contextual Tuples to specify them, e.g:
```
user: organization:acme
relation: organization
object: resource:root
```
In this case, the Check() call will be at the resource level, for example:
```
Check(user: "user:anne", relation: "can_view_resource", object: "resource:root")
```
The main advantage of this approach is that your APIs will be checking permissions at the proper level. If you later want to evolve your authorization model to be more fine grained, you won't need to change your app. For example, you can add fine grained access permissions at the resource level, and your authorization check won't change:
```
type resource
relations
define organization: [organization]
define owner: [user]
define viewer: [user]
# map resource permissions to organization roles
define can_delete_resource: admin from organization or member from organization or owner
define can_view_resource: admin from organization or member from organization or owner or viewer
```
## Provide request-level data
One of the advantages of the Zanzibar/OpenFGA approach is that all the data you need to make authorization decisions is stored in a centralized database. That greatly simplifies how application implement access control. Applications do not need to retrieve all the required data before invoking an authorization service.
However, writing the data to the centralized store adds implementation complexity. You need to implement a data pipeline that makes sure the data is always up to date.
OpenFGA provides a feature called [Contextual Tuples](https://openfga.dev/docs/interacting/contextual-tuples.md) that allows sending the required data as part of each authorization request instead of storing it on the OpenFGA database. Overusing this feature has many drawbacks, as you are now adding additional complexity and latency around collecting the data, and you are not benefiting from using OpenFGA as intended. However, implementing a hybrid approach can make sense in many scenarios and can also be a helpful tool at the start when you are transitioning into a more OpenFGA tailored approach.
When the data is already available to the calling API, sending it as a contextual tuple is very simple. A common use case is you have data in [your access tokens](https://openfga.dev/docs/modeling/token-claims-contextual-tuples.md) (for example, roles/groups claims). Instead of synchronizing groups/roles relations to OpenFGA, you can send those as contextual tuples.
When the data is not already, you will need to retrieve it. This is what you need to do if you are implementing pure Attribute Access Control. You'd retrieve the data and send it to the authorization policy engine. You can do the same with OpenFGA using Contextual Tuples.
You'll need to make the trade-off between writing the data to OpenFGA so it's always available for any authorization request, or requesting it before making an authorization check.
We've seen companies successfully following a hybrid approach, starting by synchronizing the data that's easy first and providing the rest as contextual tuples. As their implementation matures, they implement more synchronization processes and stop sending the contextual tuples.
## Use OpenFGA to enrich JWTs
Once you have your authorization model and data set up, you can start making authorization checks from your application. The preferred way is to perform a [Check()](https://openfga.dev/docs/getting-started/perform-check.md) call.
However, you might have a large set of APIs that are already making authorization checks using JWTs. Changing those applications can be a significant investment. Even if JWTs have several drawbacks compared to making FGA API calls, it can be reasonable to first start by using OpenFGA to generate the claims that are stored in JWTs, while the applications keep using those claims to make authorization decisions.
Over time, you'll migrate the applications and APIs to use authorization check instead.
Authentication services usually provide a way to enrich access tokens during the authorization flow. You can see an example on how to do it with Auth0 [here](https://auth0.com/blog/enrich-auth0-access-tokens-with-auth0-fga-data/).
For example, if you want to include in the access token the organizations that a user can log-in to, based on the following model:
```
type user
type organization
relations
define member: [user]
```
You can call `ListObjects(type:"organization", relation:"member", user: "user:xxx")` and include those.
## Promoting Organization-Wide Adoption
To introduce OpenFGA in a large company, it's recommended that you identify a problem where the additional enables quickly delivering business value to customers. It can be a new project, a new module, a new feature. Using OpenFGA for such a project can be an easier decision. Once an implementation is successful, you can try influencing the rest of the organization to adopt it.
However, influencing the decision makers of a large organization can be hard. Each team has their own internal roadmaps and not all of the teams will see value in implementing a new authorization system. Migration can be seen as a tech-debt project instead of a business-value-driven one.
The can take advantage of the following capabilities to simplify adoption by multiple teams:
- [Modular Models](https://openfga.dev/docs/modeling/modular-models.md) enable each team to independently evolve their authorization policies without relying on a central team.
- [Access Control](https://openfga.dev/docs/getting-started/setup-openfga/access-control.md) allows you to issue different credentials for each application, with permissions that ensure that each credential can only write data to the types defined in the Modules they own.
## Domain-Specific Authorization Server
Some companies decide to wrap OpenFGA with their own authorization service. They decide to do this for multiple reasons:
- Sometimes they already have a centralized service, and it's easy to replace it with another without changing the calling applications.
- It can simplify internal adoption by providing domain-specific APIs. Instead of calling `write` or `check`, applications can call a `/share-document` endpoint or a `/can-view-document` one. Each team does not need to learn the OpenFGA API.
- If they are using Contextual Tuples, they can keep the logic to retrieve additional data to send to OpenFGA in a single service.
- They only need to provide OpenFGA configuration data like Store ID and Model ID in a single service.
On the other hand, adding another service increases latency, adds additional complexity and would make the teams less likely to find help from existing public OpenFGA documentation and resources.
## Shadowing the OpenFGA API
When migrating from an existing authorization system to OpenFGA, it's recommended to first run both systems in parallel, with OpenFGA in "shadow mode". This means that while the existing system continues to make the actual authorization decisions, you also make calls to OpenFGA asynchronously and compare the results.
This approach has several benefits:
- You can validate that your authorization model and relationship tuples are correctly configured before switching to OpenFGA.
- You can measure the performance impact of adding OpenFGA calls to your application.
- You can identify edge cases where the OpenFGA results differ from your existing system.
- You can gradually build confidence in the OpenFGA implementation.
To implement shadow mode:
1. Configure your application to make authorization checks against both systems
2. Log any discrepancies between the two systems
3. Analyze the logs to identify and fix any issues
4. Once confident in the results, switch to using OpenFGA as the source of truth. The same approach of shallow checks when [migrating between models](https://openfga.dev/docs/getting-started/immutable-models.md#potential-use-cases).
This pattern is particularly useful for critical systems where authorization errors could have significant impact.
## Related Sections
Check out these related resources for more information about adopting OpenFGA
**Running OpenFGA in Production**
Learn about best practices for running OpenFGA in production environments.
- [More](https://openfga.dev/docs/best-practices/running-in-production.md)
**Modular Authorization Models**
Learn how to break down your authorization model into modules.
- [More](https://openfga.dev/docs/modeling/modular-models.md)
---
# Modeling ABAC with OpenFGA
Attribute-Based Access Control (ABAC) extends traditional relationship-based access control by incorporating attributes into authorization decisions. While Role-Based Access Control (RBAC) answers "does this user have this role?", ABAC answers questions like "does this user have this role AND is their email verified?" or "can this user access this resource given their current session context?".
With OpenFGA, you can model ABAC patterns using attributes that are stored in the system or attributes that are provided dynamically with each authorization request.
This guide covers two main approaches:
1. **Stored attributes**: Attributes persisted in OpenFGA as part of your relationship data
2. **Request-time attributes**: Dynamic attributes sent with each authorization check request
## Stored Attribute Data
When attributes are relatively static and can be stored alongside your relationship data, you can model them directly in OpenFGA. This approach works well for attributes like "email verified", "SSO enabled", or "account tier".
Consider a scenario where you want to allow users to perform an action only if their organization has SSO enabled. Below are three patterns for modeling this requirement.
### Modeling Attributes as Relations
#### Using `user:*` as a Boolean Flag
The simplest approach for boolean attributes is using the [Public Access](https://openfga.dev/docs/modeling/public-access.md) pattern. By assigning `user:*` to a relation, you effectively create a boolean flag that applies to all users.
**When to use this approach:**
- You need a simple true/false flag
- The attribute applies uniformly to all users in a context
- You want minimal tuple overhead
```
model: |
model
schema 1.1
type user
type organization
relations
define member : [user]
define sso_enabled : [user:*]
define can_use_sso : sso_enabled and member
tuples:
- user: user:*
relation: sso_enabled
object: organization:acme
- user: user:anne
relation: member
object: organization:acme
tests:
- check:
- user: user:anne
object: organization:acme
assertions:
can_use_sso: true
```
#### Using Self-Relations
An alternative approach is to use a self-referential relation, where an object points to itself to indicate an attribute is set. This pattern makes the relationship semantics more explicit.
**When to use this approach:**
- You want clearer semantics about what the attribute represents
- The attribute is a property of the object itself rather than a universal grant
- You prefer explicit object references over wildcards
```
model: |
model
schema 1.1
type user
type organization
relations
define member : [user]
define sso_enabled : [organization]
define can_use_sso : member from sso_enabled
tuples:
- user: organization:acme
relation: sso_enabled
object: organization:acme
- user: user:anne
relation: member
object: organization:acme
tests:
- check:
- user: user:anne
object: organization:acme
assertions:
can_use_sso: true
```
### Storing Attributes in Conditional Tuple Context
For attributes that require typed values beyond simple booleans, you can store the attribute value directly in the condition context of a relationship tuple. This approach combines the relationship with its associated attribute data.
**When to use this approach:**
- You need typed attribute values (strings, numbers, etc.)
- The attribute is tightly coupled to a specific relationship
- You want to avoid additional tuples for attribute storage
```
model: |
model
schema 1.1
type user
type organization
relations
define member : [user]
define sso_enabled : [organization#member with sso_enabled]
define can_use_sso : sso_enabled
condition sso_enabled(sso_enabled: bool) {
sso_enabled
}
tuples:
- user: organization:acme#member
relation: sso_enabled
object: organization:acme
condition:
name: sso_enabled
context:
sso_enabled : true
- user: user:anne
relation: member
object: organization:acme
tests:
- check:
- user: user:anne
object: organization:acme
assertions:
can_use_sso: true
```
## Request-Time Attribute Data
Some attributes cannot be stored because they are dynamic and change with each request. Common examples include the current time, client IP address, or the user's current session context. For these scenarios, you provide the attribute values at request time.
For time-based, IP-based conditions and more, see the [Conditions documentation](https://openfga.dev/docs/modeling/conditions.md) for detailed examples.
### Multi-Organization Session Context
A common use case is when users can authenticate to multiple organizations but should only access resources belonging to their currently active organization. When accessing content, you need to verify the content belongs to the organization the user is currently logged into.
You can achieve this with either contextual tuples or conditional relationship tuples.
#### Using Contextual Tuples
With this approach, you add a relation (e.g., `user_in_context`) that is not stored but sent as a contextual tuple with each request. This contextual tuple represents the user's current session context.
**When to use this approach:**
- Session context is determined entirely at request time
- You want to keep stored tuples simple and context-free
- The context applies across multiple relation checks
```
model: |
model
schema 1.1
type user
type organization
relations
define user_in_context: [user]
define project_editor: [user] and user_in_context
define project_viewer: [user] and user_in_context
type project
relations
define organization: [organization]
define editor: project_editor from organization
define viewer: project_viewer from organization
tuples:
- user: user:anne
relation: project_editor
object: organization:acme
- user: organization:acme
relation: organization
object: project:acme-website
- user: user:anne
relation: project_editor
object: organization:contoso
- user: organization:contoso
relation: organization
object: project:contoso-website
tests:
- name: Anne can access the acme-website when she's in context of the acme organization
tuples:
# This contextual tuple represents Anne's current session
- user: user:anne
relation: user_in_context
object: organization:acme
check:
- user: user:anne
object: project:acme-website
assertions:
editor: true
- user: user:anne
object: project:contoso-website
assertions:
editor: false
- name: Anne can access the contoso-website when she's in context of the contoso organization
tuples:
# This contextual tuple represents Anne's current session
- user: user:anne
relation: user_in_context
object: organization:contoso
check:
- user: user:anne
object: project:acme-website
assertions:
editor: false
- user: user:anne
object: project:contoso-website
assertions:
editor: true
```
#### Using Conditional Relationship Tuples
Instead of adding contextual tuples, you can attach conditions to your stored relationship tuples and provide the context values at request time. The condition compares stored values against request-time values.
**When to use this approach:**
- You want to avoid sending contextual tuples with every request
- The condition logic involves comparing stored and request-time values
- You prefer conditions over additional relations in your model
```
model: |
model
schema 1.1
type user
type organization
relations
define project_editor: [user with in_context]
define project_viewer: [user with in_context]
type project
relations
define organization: [organization]
define editor: project_editor from organization
define viewer: project_viewer from organization
condition in_context(project_org: string, user_org: string) {
project_org == user_org
}
tuples:
- user: user:anne
relation: project_editor
object: organization:acme
condition:
name: in_context
context:
project_org: "acme"
- user: user:anne
relation: project_editor
object: organization:contoso
condition:
name: in_context
context:
project_org: "contoso"
- user: organization:acme
relation: organization
object: project:acme-website
- user: organization:contoso
relation: organization
object: project:contoso-website
tests:
- name: Anne can access the acme-website when she's in context of the acme organization
check:
- user: user:anne
object: project:acme-website
context:
user_org: "acme"
assertions:
editor: true
- user: user:anne
object: project:contoso-website
context:
user_org: "acme"
assertions:
editor: false
- name: Anne can access the contoso-website when she's in context of the contoso organization
check:
- user: user:anne
object: project:acme-website
context:
user_org: "contoso"
assertions:
editor: false
- user: user:anne
object: project:contoso-website
context:
user_org: "contoso"
assertions:
editor: true
```
## Choosing the Right Approach
| Approach | Best For | Pros | Cons |
| ------------------------------- | ----------------------------------- | ------------------------------------------------ | ----------------------------------- |
| `user:*` boolean flag | Simple on/off attributes | Minimal tuples, easy to understand | Limited to boolean values |
| Self-relations | Object-level properties | Clear semantics, explicit references | Requires self-referential tuple |
| Contextual tuples | Dynamic session context | Context-free stored data, flexible | Must send tuples with every request |
| Conditional relationship tuples | Comparing stored vs. request values | No extra tuples per request, powerful conditions | Condition logic can become complex |
When deciding between these approaches, consider:
- **Data volatility**: Use stored attributes for stable data, request-time attributes for dynamic data
- **Model complexity**: Start with simpler patterns (like `user:*`) and evolve to more complex ones as needed
- **Attribute types**: Use conditions when you need typed values beyond booleans
## Related Sections
Check out these related resources for more information about ABAC patterns in OpenFGA
**Conditions**
Learn how to use conditions for time-based, IP-based, and other dynamic checks.
- [More](https://openfga.dev/docs/modeling/conditions.md)
**Contextual Tuples**
Understand how to send dynamic relationship data with each request.
- [More](https://openfga.dev/docs/interacting/contextual-tuples.md)
**Public Access**
Learn about the user:\* pattern for granting access to everyone.
- [More](https://openfga.dev/docs/modeling/public-access.md)
**Modular Authorization Models**
Learn how to break down your authorization model into modules.
- [More](https://openfga.dev/docs/modeling/modular-models.md)
---
# Authorization Model Design Principles
A well-designed authorization model is easier to understand, debug, and maintain. It also performs better and scales more gracefully as your application grows. This guide covers key principles for modeling authorization in OpenFGA.
## Core Principle: Model Your Domain, Not a Meta-Model
The most common mistake when starting with OpenFGA is creating an overly generic model that can represent "anything." While this seems flexible, it trades clarity for abstraction and often hurts performance.
Rule of Thumb
**If end-users can define it, store it in tuples. If it's built into your application, define it in the model.**
For example: built-in roles like "admin" or "billing\_manager" should be relations in your model. User-defined custom roles should be stored as tuples with a `role` type.
### The Recommended Approach
Define types and relations that mirror your application's domain. If your app has organizations, projects, and documents, model exactly that with explicit relationships:
```
model
schema 1.1
type user
type organization
relations
define admin: [user]
define member: [user]
type project
relations
define org: [organization]
define lead: [user]
define member: [user] or member from org
type document
relations
define project: [project]
define owner: [user]
define editor: [user] or owner or lead from project
define viewer: [user] or editor or member from project
```
Notice how this model has a clear hierarchy (organization → project → document) where each type and relationship directly reflects the application's domain. Permission inheritance follows a well-defined path that's easy to understand and audit.
This approach has several advantages:
- **Enhanced clarity and maintainability**: Authorization logic is easier to understand, debug, and maintain. Developers and security auditors can readily grasp the meaning of each type and relationship just by reading the model.
- **Better performance**: Models with specific types and flatter hierarchies perform better. OpenFGA processes queries more efficiently with well-defined types compared to navigating complex recursive relationships within generic types.
- **Easier evolution**: OpenFGA's modeling language is designed to be adaptable. You can define numerous distinct types and relationships without significant overhead. Model changes rarely require data migrations, allowing you to evolve your model as your application grows.
- **Team autonomy with modules**: Resource types owned by each application team can be maintained in independent [modules](https://openfga.dev/docs/modeling/modular-models.md). You can control which application can write to specific resource types through API credentials, providing better security boundaries.
What to avoid: The overly generic model
The model below can technically represent any organization hierarchy, any resource hierarchy, and any role hierarchy:
```
model
schema 1.1
type user
type role
relations
define assignee: [user, role#assignee]
type entity
relations
define parent: [entity]
define editor: [role#assignee] or editor from parent
define viewer: [role#assignee] or editor or viewer from parent
type resource
relations
define entity: [entity]
define parent: [resource]
define editor: [role#assignee] or editor from entity or editor from parent
define viewer: [role#assignee] or editor or viewer from parent
```
While flexible, this approach creates problems:
- The model doesn't communicate what your application actually does
- To understand the actual relationships you need to rely on tuples, e.g., the fact that a project can have documents.
- Generic recursive relations are slower to evaluate
- You can't use modules to isolate different resource types
- ListObjects returns mixed results (all "resources" instead of just "documents")
## Modeling Roles
Most applications have roles. The key question is: are they built-in or user-defined?
### Built-in Roles
For roles that come with your application (admin, member, viewer, etc.), define them directly as relations:
```
model
schema 1.1
type user
type organization
relations
define admin: [user]
define member: [user]
define billing_manager: [user]
define can_manage_billing: admin or billing_manager
define can_manage_users: admin
define can_view_dashboard: admin or member
```
Adding new built-in roles is straightforward: add a relation to the model. This happens infrequently and doesn't require data migration.
### Custom Roles (User-Defined)
Some applications let end-users create their own roles. In this case, combine static roles with a dynamic `role` type:
```
model
schema 1.1
type user
type role
relations
define assignee: [user]
type organization
relations
# Built-in roles
define admin: [user]
define billing_manager: [user]
# Permissions combine built-in and custom roles
define can_manage_billing: [role#assignee] or admin or billing_manager
define can_manage_users: [role#assignee] or admin
```
This hybrid approach gives you the clarity of static roles for common cases while supporting custom roles when needed.
For more details, see [Modeling Roles](https://openfga.dev/docs/best-practices/modeling-roles.md) and [Custom Roles](https://openfga.dev/docs/modeling/custom-roles.md).
## Modeling Organizational Structures
B2B SaaS applications often have two distinct organizational requirements:
1. **Super-admin access**: Your company's employees need to access customer data for support or disaster recovery
2. **Customer hierarchies**: Your customers have their own organizational structures
### Super-Admin Access
For internal support and admin access, use a dedicated `system` type rather than making organizations recursive:
```
model
schema 1.1
type user
type system
relations
define admin: [user]
type organization
relations
define system: [system]
define admin: [user] or admin from system
define member: [user]
```
This approach:
- Clearly separates your internal access from customer access
- Avoids recursive relations (faster to evaluate)
- Makes audit and compliance reviews easier
See a complete [super-admin example](https://github.com/openfga/sample-stores/blob/main/stores/superadmin) for more details.
### Customer Organization Hierarchies
If customers need hierarchical organizations, prefer explicit types for each level when the structure is well-defined:
```
model
schema 1.1
type user
type system
relations
define admin: [user]
type organization
relations
define system: [system]
define admin: [user] or admin from system
define member: [user]
type department
relations
define org: [organization]
define manager: [user]
define member: [user] or member from org
```
This makes the hierarchy explicit: organizations contain departments, and department members inherit from the organization.
If the hierarchy depth is truly dynamic (customers can nest arbitrarily), then add recursion only where needed:
```
model
schema 1.1
type user
type system
relations
define admin: [user]
type organization
relations
define system: [system]
define parent: [organization]
define admin: [user] or admin from system or admin from parent
define member: [user] or member from parent
```
Now you have two distinct hierarchies:
- **System hierarchy**: Non-recursive, for your internal super-admin access
- **Organization hierarchy**: Recursive only if customers truly need arbitrary nesting
Prefer explicit types when possible; use recursion only when the depth is genuinely unbounded.
## Modeling Resource Types
Applications have different kinds of resources: documents, folders, projects, tickets, accounts, etc. Some have parent-child relationships (folders contain documents, projects contain tickets).
### Use Specific Types, Not a Generic "Resource"
Define a type for each kind of resource in your application:
```
model
schema 1.1
type user
type folder
relations
define parent: [folder]
define owner: [user]
define editor: [user] or owner or editor from parent
define viewer: [user] or editor or viewer from parent
type document
relations
define parent: [folder]
define owner: [user]
define editor: [user] or owner or editor from parent
define viewer: [user] or editor or viewer from parent
define can_print: [user] or owner
define can_share: owner
```
Benefits of specific types:
- **Accurate ListObjects results**: Querying for documents returns only documents, not all resources
- **Type-specific permissions**: `can_print` makes sense for documents but not folders
- **Clearer model**: Each type shows exactly what permissions apply to it
- **Module support**: Different teams can own different resource types
What to avoid: The generic resource type
```
type resource
relations
define entity: [entity]
define parent: [resource]
define editor: [role#assignee] or editor from entity or editor from parent
define viewer: [role#assignee] or editor or viewer from parent
```
Problems with this approach:
- ListObjects returns mixed results (folders, documents, and everything else)
- You end up with a superset of all permissions, making it unclear which apply to what
- No way to use modules for team ownership
- Harder to understand and audit
## Quick Reference
| Scenario | Recommendation |
| -------------------------------- | -------------------------------------------------------------------- |
| Built-in roles (admin, member) | Define as relations directly in the model |
| User-defined custom roles | Create a `role` type, store assignments as tuples |
| Internal super-admin access | Use a non-recursive `system` type |
| Customer org hierarchies | Add recursive `parent` relation only if needed |
| Different resource types | Create specific types (`document`, `folder`), not generic `resource` |
| Type-specific permissions | Define permissions on the relevant type only |
| Team ownership of resource types | Use modules to separate concerns |
## Related Sections
Check out these related resources for more information about modeling in OpenFGA
**Custom Roles**
Learn how to implement user-defined custom roles.
- [More](https://openfga.dev/docs/modeling/custom-roles.md)
**Modular Authorization Models**
Learn how to break down your authorization model into modules.
- [More](https://openfga.dev/docs/modeling/modular-models.md)
**Modeling Roles**
Detailed guidance on role-based access control patterns.
- [More](https://openfga.dev/docs/best-practices/modeling-roles.md)
**Building Blocks**
Understand the fundamental concepts for building authorization models.
- [More](https://openfga.dev/docs/modeling/building-blocks.md)
---
# Modeling Roles
Roles are a common way to group users and assign permissions to those groups. They can be used to simplify permission management, especially in larger systems where many users have similar access needs.
In this guide, we'll explore common approaches to modeling roles with OpenFGA.
## When to Use Each Approach
Before diving into implementation details, here's a quick guide to help you choose the right approach:
| Approach | Best For | Complexity | Flexibility | Example |
| ----------------------------- | ---------------------------------- | ---------- | ----------- | ----------------------------------------------------------------------------------------------------------------- |
| **Relations as Roles** | Static, predefined roles | Low | Low | In all instances, company admins can view project information. |
| **Simple User-Defined Roles** | User-defined roles at org level | Medium | Medium | Company Acme creates an "Auditor" role that is configured to view project information for all projects. |
| **Role Assignments** | Instance-specific role assignments | High | High | In Company Acme, Anne can be a custom Auditor role for Projects 1 and 5, but Beth can be an Auditor on Project 3. |
## Approach 1: Relations as Roles
The simplest way to implement roles is to use directly assignable relations. They work well for roles that always exist and can be defined at development-time. Adding relations is straightforward, and you do not need to add roles very frequently. If roles are static, this is always the preferred approach.
### Example: Organization Admin Role
In the model below, we define an `admin` role at the organization level. Admins can edit billing details and create projects.
```
model
schema 1.1
type user
type organization
relations
define admin: [user]
define can_create_project: admin
define can_edit_billing_details: admin
```
### Adding Users to Roles
To add users to the admin role, create a tuple like:
```
- user: user:anne
relation: admin
object: organization:acme
```
### Extending with Additional Roles
If later you need to add a `project_admin` role with permissions to view/edit projects, the model evolves to:
```
model
schema 1.1
type user
type organization
relations
define admin: [user]
define project_admin: [user] # new role
# existing permissions
define can_edit_billing_details: admin
define can_create_project: admin or project_admin
# new permissions for project admins
define can_edit_project: admin or project_admin
define can_view_project: admin or project_admin
```
### Pros and Cons
**✅ Advantages:**
- Simple to implement and understand
- Fast evaluation performance
- Clear authorization policies
- No additional tuples needed when adding permissions
- Role permissions are straightforward to change, regardless of scale
**❌ Disadvantages:**
- Roles must be predefined in the model
- Not suitable for user-defined roles
***
## Approach 2: Simple User-Defined Roles
Many applications require the flexibility for end-users to define their own custom roles, in addition to any pre-defined roles. This approach enables organizations to tailor permissions to their specific needs.
### Example: Custom Project Admin Role
With the following model, your application can support both static roles and user-defined roles:
```
model
schema 1.1
type user
type role
relations
define assignee: [user]
type organization
relations
define admin: [user] # static role
# permissions can be assigned to custom roles or static roles
define can_create_project: [role#assignee] or admin
define can_edit_project: [role#assignee] or admin
```
### Setting Up Custom Roles
1. **Define role permissions** by creating tuples that grant the role-specific permissions:
```
- user: role:acme-project-admin#assignee
relation: can_create_project
object: organization:acme
- user: role:acme-project-admin#assignee
relation: can_edit_project
object: organization:acme
```
2. **Assign users to the role**:
```
- user: user:anne
relation: assignee
object: role:acme-project-admin
```
### Adding New Permissions
When you add new permissions to your model, existing roles don't automatically receive them:
```
model
schema 1.1
type user
type role
relations
define assignee: [user]
type organization
relations
define admin: [user]
define can_create_project: [role#assignee] or admin
define can_edit_project: [role#assignee] or admin
define can_delete_project: [role#assignee] or admin # new permission
```
To grant the new permission to existing roles, create additional tuples:
```
- user: role:acme-project-admin#assignee
relation: can_delete_project
object: organization:acme
```
You do not need to add these tuples when adding the new permission. End-users will add the new permission to their custom roles when they find it appropriate.
### Pros and Cons
**✅ Advantages:**
- Supports user-defined roles
- Flexible permission assignment
- No model changes needed for new role instances
**❌ Disadvantages:**
- More complex than static relations
- Requires additional tuples for role-permission mapping
***
## Approach 3: Role Assignments
The previous approach works well when custom roles are global for the organization. However, if you need roles that can be attached to different object instances with different members for each instance, you need role assignments.
### Example: Project-Specific Admin Roles
Let's say you want a "Project Admin" role where each project can have different admins, but the role permissions remain consistent.
### Step 1: Define the Role and its Permissions
Define a `role` type where you list all the permissions that any role can have:
```
model
schema 1.1
type role
relations
define can_view_project: [user:*]
define can_edit_project: [user:*]
```
A "Project Admin" role can have `can_view_project` and `can_edit_project`:
```
# Project Admin role has both the can_view_project and can_edit_project assigned
- user: user:*
relation: can_view_project
object: role:project-admin
- user: user:*
relation: can_edit_project
object: role:project-admin
```
### Step 2: Assign Users to a Role on an Entity
Add a `role_assignment` type to assign users to the role:
```
type role_assignment
relations
define assignee: [user]
define role: [role]
define can_view_project: assignee and can_view_project from role
define can_edit_project: assignee and can_edit_project from role
```
### Step 3: Connect to Your Objects
Define an `organization` type with an `admin` role. Then, define a `project` type that links to an `organization` and a `role_assignment`. Note that we are combining a static `admin` role with custom role assignments. We recommend to always use static roles when they are known in advance.
```
type organization
relations
define admin: [user]
type project
relations
define organization: [organization]
define role_assignment: [role_assignment]
# combine role assignments and static roles
define can_edit_project: can_edit_project from role_assignment or admin from organization
define can_view_project: can_view_project from role_assignment or admin from organization
```
### Setting Up Role Assignments
1. **Create the role assignment instance**:
```
- user: user:anne
relation: assignee
object: role_assignment:project-admin-openfga
- user: role:project-admin
relation: role
object: role_assignment:project-admin-openfga
```
2. **Link the role assignment to the project**:
```
- user: role_assignment:project-admin-openfga
relation: role_assignment
object: project:openfga
```
3. **Link the project to an organization**:
```
- user: organization:acme
relation: organization
object: project:openfga
```
### Pros and Cons
**✅ Advantages:**
- Maximum flexibility for instance-specific roles
- Reusable role definitions across different objects
- Fine-grained control over role membership
**❌ Disadvantages:**
- Most complex approach to implement
- Requires careful planning of the role hierarchy
- More tuples needed for setup and maintenance
***
## Choosing the Right Approach
### Decision Tree
1. **Do you need user-defined roles?**
- No → Use **Relations as Roles**
- Yes → Continue to step 2
2. **Do roles need different members per object instance?**
- No → Use **Simple User-Defined Roles**
- Yes → Use **Role Assignments**
### Performance Considerations
- **Relations as Roles**: Fastest evaluation
- **Simple User-Defined Roles**: Moderate performance impact
- **Role Assignments**: Highest performance impact
## Best Practices
1. **Start simple**: Begin with relations as roles and evolve as needed
2. **Hybrid approach**: Combine static relations for well-known roles with dynamic roles for custom ones
3. **Documentation**: Clearly document your role model for your team
4. **Functional Testing**: [Write tests](https://openfga.dev/docs/modeling/testing.md) to verify your model behaves as expected
5. **Performance Testing**: Test performance with realistic data volumes
## Related Sections
Check out these related resources for more information about adopting OpenFGA.
**Custom Roles Step by Step**
Follow a detailed walkthrough of implementing custom roles.
- [More](https://openfga.dev/docs/modeling/custom-roles.md)
**Multi-tenant RBAC Example**
See a complete multi-tenant role-based access control implementation.
- [More](https://github.com/openfga/sample-stores/blob/main/stores/multitenant-rbac)
**Role Assignments Example**
Explore a full role assignments implementation.
- [More](https://github.com/openfga/sample-stores/tree/main/stores/role-assignments)
---
# Running OpenFGA in Production
The following list outlines best practices for running OpenFGA in a production environment:
- [Configure Authentication](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md#configuring-authentication)
- Enable HTTP TLS or gRPC TLS or both
- Set the log format to "json" and log level to "info"
- [Disable the Playground](https://openfga.dev/docs/getting-started/setup-openfga/playground.md#disabling-the-playground)
- [Set Cluster](#cluster-recommendations)
- [Set Database Options](#database-recommendations)
- [Set Maximum Results](#maximum-results)
- [Set Concurrency Limits](#concurrency-limits)
## Cluster recommendations
We recommend:
1. Turn on in-memory caching in Check API via flags. This will reduce latency of requests, but it will increase the staleness of OpenFGA's responses. Please see [Cache Expiration](https://openfga.dev/docs/interacting/consistency.md#cache-expiration) for details on the flags.
2. Prefer having a small pool of servers with high capacity (memory and CPU cores) instead of a big pool of servers, to increase cache hit ratios and simplify pool management.
3. Turn on metrics collection via the flags `--metrics-enabled` and `--datastore-metrics-enabled`. This will allow you to debug issues.
4. Turn on tracing via the flag `--trace-enabled`, but set sampling ratio to a low value, for example `--trace-sample-ratio=0.3`. This will allow you to debug issues without overwhelming the tracing server. However, keep in mind that enabling tracing comes with a slight performance cost.
## Database recommendations
To ensure good performance for OpenFGA, it is recommended that the [database](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md#configuring-data-storage) be:
- Co-located in the same physical datacenter and network as your OpenFGA servers. This will minimize latency of database calls.
- Used exclusively for OpenFGA and not shared with other applications. This allows scaling the database independently and avoiding contention with your database.
- Bootstrapped and managed with the `openfga migrate` command. This will ensure the appropriate database indexes are created.
It's strongly recommended to fine-tune your server database connection settings to avoid having to re-establish database connections frequently. Establishing database connections is slow and will negatively impact performance. The recommended settings differ depending on your database type:
### PostgreSQL
For PostgreSQL, tune the server database with the following parameters:
- [OPENFGA\_DATASTORE\_MIN\_OPEN\_CONNS](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_MIN_OPEN_CONNS): This parameter should be the minimum number of database connections you want to maintain. This helps ensure that a baseline pool of connections is always available. As a starting point, consider setting this to a small, fixed baseline (for example between 5 and 20 connections), or roughly 10–30% of the maximum connections your PostgreSQL instance allows, while ensuring it does not exceed that database limit. If you are running multiple instances of the OpenFGA server, you should divide this setting equally among the instances.
- [OPENFGA\_DATASTORE\_MIN\_IDLE\_CONNS](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_MIN_IDLE_CONNS): This parameter controls the minimum number of connections to keep in the idle pool. **This value must not exceed `OPENFGA_DATASTORE_MIN_OPEN_CONNS`**, since OpenFGA validates on startup that `MinOpenConns >= MinIdleConns`. As a starting point, set this to around 50–75% of your `OPENFGA_DATASTORE_MIN_OPEN_CONNS` value to maintain a stable connection pool and avoid the overhead of frequently recreating connections, then adjust based on observed connection churn and database load.
- The server setting [OPENFGA\_DATASTORE\_MAX\_OPEN\_CONNS](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_MAX_OPEN_CONNS) should be set to be equal to your database's max connections. For example, in Postgres, you can see this value via running the SQL query `SHOW max_connections;`. If you are running multiple instances of the OpenFGA server, you should divide this setting equally among the instances. For example, if your database's `max_connections` is 100, and you have 2 OpenFGA instances, `OPENFGA_DATASTORE_MAX_OPEN_CONNS` should be set to 50 for each instance.
PostgreSQL also accepts connection to secondary datastore by setting [OPENFGA\_DATASTORE\_SECONDARY\_URI](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_SECONDARY_URI), [OPENFGA\_DATASTORE\_SECONDARY\_USERNAME](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_SECONDARY_USERNAME) and [OPENFGA\_DATASTORE\_SECONDARY\_PASSWORD](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_SECONDARY_PASSWORD).
### MySQL and SQLite
For MySQL and SQLite, tune the server database with the following parameters:
- The server setting [OPENFGA\_DATASTORE\_MAX\_OPEN\_CONNS](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_MAX_OPEN_CONNS) should be set to be equal to your database's max connections. For example, in MySQL, you can see this value via running the SQL query `SHOW VARIABLES LIKE 'max_connections';`. If you are running multiple instances of the OpenFGA server, you should divide this setting equally among the instances. For example, if your database's `max_connections` is 100, and you have 2 OpenFGA instances, `OPENFGA_DATASTORE_MAX_OPEN_CONNS` should be set to 50 for each instance.
- The [OPENFGA\_DATASTORE\_MAX\_IDLE\_CONNS](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_MAX_IDLE_CONNS) should be set to a value no greater than the maximum open connections (see the bullet point above), but it should be set sufficiently high enough to avoid having to recreate connections on each request.
If, when monitoring your database stats, you see a lot of database connections being closed and subsequently reopened, then you should consider setting the `OPENFGA_DATASTORE_MAX_IDLE_CONNS` to the same number as `OPENFGA_DATASTORE_MAX_OPEN_CONNS`.
- If idle connections are getting reaped frequently, then consider increasing the [OPENFGA\_DATASTORE\_CONN\_MAX\_IDLE\_TIME](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_DATASTORE_CONN_MAX_IDLE_TIME) to a large value. When in doubt, prioritize keeping connections around for longer rather than shorter, because doing so will drastically improve performance.
## Concurrency limits
note
Before modifying concurrency limits please make sure you've followed the guidance for [Database Recommendations](#database-recommendations)
OpenFGA queries such as Check, ListObjects and ListUsers can be quite database and CPU intensive in some cases. If you notice that a single request is consuming a lot of CPU or creating a high degree of database contention, then you may consider setting some concurrency limits to protect other requests from being negatively impacted by overly aggressive queries.
The following table enumerates the server's concurrency specific settings:
| flag | env | config |
| --------------------------------------- | --------------------------------------------------- | -------------------------------- |
| --max-concurrent-reads-for-list-objects | OPENFGA\_MAX\_CONCURRENT\_READS\_FOR\_LIST\_OBJECTS | maxConcurrentReadsForListObjects |
| --max-concurrent-reads-for-list-users | OPENFGA\_MAX\_CONCURRENT\_READS\_FOR\_LIST\_USERS | maxConcurrentReadsForListUsers |
| --max-concurrent-reads-for-check | OPENFGA\_MAX\_CONCURRENT\_READS\_FOR\_CHECK | maxConcurrentReadsForCheck |
| --resolve-node-limit | OPENFGA\_RESOLVE\_NODE\_LIMIT | resolveNodeLimit |
| --resolve-node-breadth-limit | OPENFGA\_RESOLVE\_NODE\_BREADTH\_LIMIT | resolveNodeBreadthLimit |
| --max-concurrent-checks-per-batch-check | OPENFGA\_MAX\_CONCURRENT\_CHECKS\_PER\_BATCH\_CHECK | maxConcurrentChecksPerBatchCheck |
Determining the right values for these settings will be based on a variety of factors including, but not limited to, the database specific deployment topology, the FGA model(s) involved, and the relationship tuples in the system. However, here are some high-level guidelines:
- If a single ListObjects or ListUsers query is negatively impacting other query endpoints by increasing their latency or their error rate, then consider setting a lower value for `OPENFGA_MAX_CONCURRENT_READS_FOR_LIST_OBJECTS` or `OPENFGA_MAX_CONCURRENT_READS_FOR_LIST_USERS`.
- If a single Check query is negatively impacting other query endpoints by increasing their latency or their error rate, then consider setting a lower value for `OPENFGA_MAX_CONCURRENT_READS_FOR_CHECK`.
If you still see high request latencies despite the guidance above, then you may additionally consider setting stricter limits on the query resolution behavior by limiting the resolution depth and resolution breadth. These can be controlled with the `OPENFGA_RESOLVE_NODE_LIMIT` and `OPENFGA_RESOLVE_NODE_BREADTH_LIMIT` settings, respectively. Consider these guidelines:
- `OPENFGA_RESOLVE_NODE_LIMIT` limits the resolution depth of a single query, and thus it sets an upper bound on how deep a relationship hierarchy may be. A high value will allow a single query to involve more hierarchical resolution and therefore more database queries, while a low value will reduce the number of hierarchical resolutions that will be allowed and thus reduce the number of database queries.
- `OPENFGA_RESOLVE_NODE_BREADTH_LIMIT` limits the resolution breadth. It sets an upper bound on the number of in-flight resolutions that can be taking place on one or more [usersets](https://openfga.dev/docs/concepts.md#what-is-a-user). A high value will allow a single query to involve more concurrent evaluations to take place and therefore more database queries and server processes, while a low value will reduce the overall number of concurrent resolutions that will be allowed and thus reduce the number of database queries and server processes.
## Maximum results
Both the ListObjects and ListUsers endpoints will continue retrieving results until one of the following conditions is met:
- The maximum number of results is found
- The entire pool of possible results has been searched
- The API times out
By default, both ListObjects and ListUsers have a maximum results limit of 1,000. The higher the quantity of potential results in the system, the more time and resource-intensive it becomes to search for a large number of maximum results. This increased load can impact performance, potentially leading to time-outs in some cases. If your use case allows, consider setting a lower max results value via the `OPENFGA_LIST_OBJECTS_MAX_RESULTS` or `OPENFGA_LIST_USERS_MAX_RESULTS` configuration properties. This adjustment can lead to immediate improvements in time and resource efficiency.
## Related Sections
Check the following sections for more on how to run OpenFGA in production environment.
**Data and API Best Practices**
Learn the best practices for managing data and invoking APIs in production environment
- [More](https://openfga.dev/docs/getting-started/tuples-api-best-practices.md)
**Migrating Relations**
Learn how to migrate relations in a production environment
- [More](https://openfga.dev/docs/modeling/migrating/migrating-relations.md)
---
# When to use OpenFGA as the 'source of truth' for authorization data
OpenFGA is inspired by [Google’s Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/). In Google’s architecture, Zanzibar is an extremely efficient system for authorization checks, but it's never the source of truth for application data. The [Read endpoint](https://openfga.dev/docs/interacting/relationship-queries#read) is mostly used when you need to inspect the stored data, e.g. for troubleshooting consistency issues.
For developers using OpenFGA, following Google's approach isn't always practical. In most cases, applications will use OpenFGA as the source of truth for some data.
**When OpenFGA is not the right source of truth:**
- User data: The source of truth for user profile data is typically an identity provider like Azure, Okta or Auth0.
- Entity hierarchies: Structures like project/tickets or folder/documents already live in application's databases. Repeatedly querying OpenFGA just to navigate that hierarchy would be inefficient. Having this in the application's database would allow for better optimizations when searching within a folder (see: [search with permissions](https://openfga.dev/docs/interacting/search-with-permissions.md)), as it would let the applications narrow down the scope of what it needs to check, and then call check in parallel instead of filtering through other methods.
- Data relevant for search and filtering: When performing searches, you need to combine data that's on your database and data that's in OpenFGA. Your application's database is the right place to do filtering/sorting/joins. The data required for performing those operations should live in application's databases.
**When OpenFGA is a good source of truth:**
- Fine-grained permissions: If an application allows users to assign permissions directly to resources (e.g., sharing a document), and you don't need to store that data in the application's database, you can store it only in OpenFGA.
- Role membership: If you are not using another system to manage roles, storing role membership in OpenFGA is reasonable. Remember that OpenFGA does not store role metadata (like names or descriptions), so you'll still need a 'Roles' table in your application's database.
---
# OpenFGA Community
## Slack (CNCF Community)
The OpenFGA community has a channel in the [CNCF](https://cncf.io) Slack.
If you don't have access to the CNCF Slack you can request an invitation [here](https://slack.cncf.io). You can join the community in the [#openfga](https://cloud-native.slack.com/archives/C06G1NNH47N) channel.
## GitHub Discussions
You can also use [GitHub discussions](https://github.com/orgs/openfga/discussions) to ask questions and submit product ideas.
## X (formerly Twitter)
Follow us on X to get the latest updates on all things OpenFGA. [@OpenFGA](https://twitter.com/OpenFGA).
## YouTube
Subscribe to [the OpenFGA YouTube Channel](https://www.youtube.com/@OpenFGA) to see our latest videos and recordings.
## LinkedIn
Follow us on [LinkedIn](https://www.linkedin.com/company/openfga/) for the latest updates, community highlights, and insights into fine-grained authorization.
## Mastodon
For the Fediverse fans among you, follow us on Mastodon at [@openfga@mastodon.social](https://mastodon.social/@openfga)!
## Monthly Community Meetings
We hold a monthly community meeting on the second Thursday of every month @ [11am Eastern Time (US)](https://www.worldtimebuddy.com/?qm=1\&lid=12,100,5,6,8\&h=5\&sln=11-12\&hf=1).
- [Calendar](https://zoom-lfx.platform.linuxfoundation.org/meetings/openfga)
- [Agenda](https://docs.google.com/document/d/1Y6rbD0xpGLVl-7CmeMgxi56_a0ibIQ_RojvWBbT9MZk/edit#)
- [Zoom Link](https://zoom-lfx.platform.linuxfoundation.org/meetings/openfga)
- [Recordings of Previous Meetings](https://www.youtube.com/playlist?list=PLUR5l-oTFZqUneyHz-h4WzaJssgxBXdxB)
Read more details [here](https://github.com/openfga/community/blob/main/community-meetings.md)
---
# Concepts
The [OpenFGA](https://openfga.dev/docs/fga.md) service answers [authorization](https://openfga.dev/docs/authorization-concepts.md#authentication-and-authorization) [checks](#what-is-a-check-request) by determining whether a **[relationship](#what-is-a-relation)** exists between an [object](#what-is-an-object) and a [user](#what-is-a-user). Checks reference your **[authorization model](#what-is-an-authorization-model)** against your **[relationship tuples](#what-is-a-relationship-tuple)** for authorization authority. Below are explanations of basic FGA concepts, like type and authorization model, and a [playground](https://play.fga.dev/) to test your knowledge.
## What Is A Type?A **type** is a string. It defines a class of objects with similar characteristics.
The following are examples of types:
- `workspace`
- `repository`
- `organization`
- `document`
## What Is A Type Definition?A **type definition** defines all possible relations a user or another object can have in relation to this type.
Below is an example of a type definition:
```
type document
relations
define viewer: [user]
define commenter: [user]
define editor: [user]
define owner: [user]
```
## What Is An Authorization Model?An **authorization model** combines one or more type definitions. This is used to define the permission model of a system.
Below is an example of an authorization model:
```
model
schema 1.1
type document
relations
define viewer: [domain#member, user]
define commenter: [domain#member, user]
define editor: [domain#member, user]
define owner: [domain#member, user]
type domain
relations
define member: [user]
type user
```
Together with [relationship tuples](#what-is-a-relationship-tuple), the authorization model determines whether a [relationship](#what-is-a-relationship) exists between a [user](#what-is-a-user) and an [object](#what-is-an-object).
OpenFGA uses two different syntaxes to define the authorization model:
- A JSON syntax accepted by the OpenFGA API that closely follows the original syntax in the [Zanzibar Paper](https://research.google/pubs/pub48190/). For more information, see [Equivalent Zanzibar Concepts](https://openfga.dev/docs/configuration-language.md#equivalent-zanzibar-concepts).
- A simpler-to-use DSL that's accepted by both the [OpenFGA VS Code extension](https://marketplace.visualstudio.com/items?itemName=openfga.openfga-vscode) and [OpenFGA CLI](https://github.com/openfga/cli/) and offers syntax highlighting and validation in the VS Code extension. The DSL is used in the [Sample Stores](https://github.com/openfga/sample-stores) modeling examples and is translated to API-supported syntax using the CLI or [OpenFGA language](https://github.com/openfga/language) before being sent to the API.
Click here to learn more about the [OpenFGA Configuration Language](https://openfga.dev/configuration-language).
## What Is A Store?A **store** is an OpenFGA entity used to organize authorization check data.
Each store contains one or more versions of an [authorization model](#what-is-an-authorization-model) and can contain various [relationship tuples](#what-is-a-relationship-tuple). Store data cannot be shared across stores; we recommended storing all data that may be related or affect your authorization result in a single store.
Separate stores can be created for separate authorization needs or isolated environments, e.g. development/prod.
## What Is An Object?An **object** represents an entity in the system. Users' relationships to it are defined by relationship tuples and the authorization model.
An object is a combination of a [type](#what-is-a-type) and an identifier.
For example:
- `workspace:fb83c013-3060-41f4-9590-d3233a67938f`
- `repository:auth0/express-jwt`
- `organization:org_ajUc9kJ`
- `document:new-roadmap`
[User](#what-is-a-user), [relation](#what-is-a-relation) and object are the building blocks for [relationship tuples](#what-is-a-relationship-tuple).
For an example, see [Direct Access](https://openfga.dev/docs/modeling/direct-access.md).
## What Is A User?A **user** is an entity in the system that can be related to an object.
A user is a combination of a [type](#what-is-a-type), an identifier, and an optional relation.
For example,
- any identifier: e.g. `user:anne` or `user:4179af14-f0c0-4930-88fd-5570c7bf6f59`
- any object: e.g. `workspace:fb83c013-3060-41f4-9590-d3233a67938f`, `repository:auth0/express-jwt` or `organization:org_ajUc9kJ`
- a group or a set of users (also called a **userset**): e.g. `organization:org_ajUc9kJ#members`, which represents the set of users related to the object `organization:org_ajUc9kJ` as `member`
- everyone, using the special syntax: `*`
User, [relation](#what-is-a-relation) and [object](#what-is-an-object) are the building blocks for [relationship tuples](#what-is-a-relationship-tuple).
For more information, see [Direct Access](https://openfga.dev/docs/modeling/direct-access.md).
## What Is A Relation?A **relation** is a string defined in the type definition of an authorization model. Relations define a possible relationship between an object (of the same type as the type definition) and a user in the system.
Examples of relation:
- User can be a `reader` of a document
- Team can `administer` a repo
- User can be a `member` of a team
## What Is A Relation Definition?A **relation definition** lists the conditions or requirements under which a relationship is possible.
For example:
- `editor` describing a possible relationship between a user and an object in the `document` type allows the following:
- **user identifier to object relationship**: the user id `anne` of type `user` is related to the object `document:roadmap` as `editor`
- **object to object relationship**: the object `application:ifft` is related to the object `document:roadmap` as `editor`
- **userset to object relationship**: the userset `organization:auth0.com#member` is related to `document:roadmap` as `editor`
- indicates that the set of users who are related to the object `organization:auth0.com` as `member` are related to the object `document:roadmap` as `editor`s
- allows for potential solutions to use-cases like sharing a document internally with all members of a company or a team
- **everyone to object relationship**: everyone (`*`) is related to `document:roadmap` as `editor`
- this is how one could model publicly editable documents
These would be defined in the [authorization model](#what-is-an-authorization-model):
```
type document
relations
define viewer: [user]
define commenter: [user]
define editor: [team#member, user]
define owner: [user]
type user
type team
relations
define member: [user]
```
info
There are four relations in the document type configuration: `viewer`, `commenter`, `editor` and `owner`. The `editor` relation exists when the report is directly assigned to the user or for any member of an assigned team.
[User](#what-is-a-user), relation and [object](#what-is-an-object) are the building blocks for [relationship tuples](#what-is-a-relationship-tuple).
For an example, see [Direct Access](https://openfga.dev/docs/modeling/direct-access.md).
## What Is A Directly Related User Type?A **directly related user type** is an array specified in the type definition to indicate which types of users can be directly related to that relation.
For the following model, only [relationship tuples](#what-is-a-relationship-tuple) with [user](#what-is-a-user) of [type](#what-is-a-type) `user` may be assigned to document.
```
type document
relations
define viewer: [user]
```
A relationship tuple with user `user:anne` or `user:3f7768e0-4fa7-4e93-8417-4da68ce1846c` may be written for objects with type `document` and relation `viewer`, so writing `{"user": "user:anne","relation":"viewer","object":"document:roadmap"}` succeeds. A relationship tuple with a disallowed user type for the `viewer` relation on objects of type `document` - for example `workspace:auth0` or `folder:planning#editor` - will be rejected, so writing `{"user": "folder:product","relation":"viewer","object":"document:roadmap"}` will fail. This affects only relations that are [directly related](#what-are-direct-and-implied-relationships) and have [direct relationship type restrictions](https://openfga.dev/docs/configuration-language.md#direct-relationship-type-restrictions) in their relation definition.
## What is a Condition?A **condition** is a function composed of one or more parameters and an expression. Every condition evaluates to a boolean outcome, and expressions are defined using [Google's Common Expression Language (CEL)](https://github.com/google/cel-spec).
In the following snippet `less_than_hundred` defines a Condition that evaluates to a boolean outcome. The provided parameter `x`, defined as an integer type, is used in the boolean expression `x < 100`. The condition returns a truthy outcome if the expression returns a truthy outcome, but is otherwise false.
```
condition less_than_hundred(x: int) {
x < 100
}
```
## What Is A Relationship Tuple?A **relationship tuple** is a base tuple/triplet consisting of a user, relation, and object. Tuples may add an optional condition, like [Conditional Relationship Tuples](#what-is-a-conditional-relationship-tuple). Relationship tuples are written and stored in OpenFGA.
A relationship tuple consists of:
- a **[user](#what-is-a-user)**, e.g. `user:anne`, `user:3f7768e0-4fa7-4e93-8417-4da68ce1846c`, `workspace:auth0` or `folder:planning#editor`
- a **[relation](#what-is-a-relation)**, e.g. `editor`, `member` or `parent_workspace`
- an **[object](#what-is-an-object)**, e.g `repo:auth0/express_jwt`, `domain:auth0.com` or `channel:marketing`
- a **[condition](#what-is-a-condition)** (optional), e.g. `{"condition": "in_allowed_ip_range", "context": {...}}`
An [authorization model](#what-is-an-authorization-model), together with relationship tuples, determine whether a [relationship](#what-is-a-relationship) exists between a [user](#what-is-a-user) and an [object](#what-is-an-object).
Relationship tuples are usually shown in the following format:
```
[{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap"
}]
```
For more information, see [Direct Access](https://openfga.dev/docs/modeling/direct-access.md).
## What Is A Conditional Relationship Tuple?A **conditional relationship tuple** is a [relationship tuple](#what-is-a-relationship-tuple) that represents a [relationship](#what-is-a-relationship) conditioned upon the evaluation of a [condition](#what-is-a-condition).
If a relationship tuple is conditioned, then that condition must to a truthy outcome for the relationship tuple to be permissible.
The following relationship tuple is a conditional relationship tuple because it is conditioned on `less_than_hundred`. If the expression for `less_than_hundred` is defined as `x < 100`, then the relationship is permissible because the expression - `20 < 100` - evaluates to a truthy outcome.
```
[{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap",
"condition": {
"name": "less_than_hundred",
"context": {
"x": 20
}
}
}]
```
## What Is A Relationship?A **relationship** is the realization of a relation between a user and an object.
An [authorization model](#what-is-an-authorization-model), together with [relationship tuples](#what-is-a-relationship-tuple), determine whether a relationship exists between a user and an object. Relationships may be [direct](#what-are-direct-and-implied-relationships) or [implied](#what-are-direct-and-implied-relationships).
## What Are Direct And Implied Relationships?A **direct relationship** (R) between user X and object Y means the relationship tuple (user=X, relation=R, object=Y) exists, and the OpenFGA authorization model for that relation allows the direct relationship because of [direct relationship type restrictions](https://openfga.dev/docs/configuration-language.md#direct-relationship-type-restrictions).An **implied (or computed) relationship** (R) exists between user X and object Y if user X is related to an object Z that is in a direct or implied relationship with object Y, and the OpenFGA authorization model allows it.
- `user:anne` has a direct relationship with `document:new-roadmap` as `viewer` if the [type definition](#what-is-a-type-definition) allows it with [direct relationship type restrictions](https://openfga.dev/docs/configuration-language.md#direct-relationship-type-restrictions), and one of the following [relationship tuples](#what-is-a-relationship-tuple) exist:
- ```
[// Anne of type user is directly related to the document
{
"_description": "Anne of type user is directly related to the document",
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap"
}]
```
- ```
[// Everyone (`*`) of type user is directly related to the document
{
"_description": "Everyone (`*`) of type user is directly related to the document",
"user": "user:*",
"relation": "viewer",
"object": "document:new-roadmap"
}]
```
- ```
[// The userset is directly related to this document
{
"_description": "The userset is directly related to this document",
"user": "team:product#member",
"relation": "viewer",
"object": "document:new-roadmap"
}// AND Anne of type user is a member of the userset team:product#member
{
"_description": "AND Anne of type user is a member of the userset team:product#member",
"user": "user:anne",
"relation": "member",
"object": "team:product"
}]
```
- `user:anne` has an **implied relationship** with `document:new-roadmap` as `viewer` if the type definition allows it, and the presence of relationship tuples satisfying the relationship exist.
For example, assume the following type definition:
```
type document
relations
define viewer: [user] or editor
define editor: [user]
```
And assume the following relationship tuple exists in the system:
```
[{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap"
}]
```
In this case, the [relationship](#what-is-a-relationship) between `user:anne` and `document:new-roadmap` as a `viewer` is implied from the direct `editor` relationship `user:anne` has with that same document. Thus, the following request to [check](#what-is-a-check-request) whether a viewer relationship exists between `user:anne` and `document:new-roadmap` will return `true`.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
- Pseudocode
- Playground
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'viewer',
object: 'document:new-roadmap',
}, {
authorizationModelId: '01HVMMBCMGZNT3SED4Z17ECXCA',
});
// allowed = true
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "viewer",
Object: "document:new-roadmap",
}
data, err := fgaClient.Check(context.Background()).
Body(body).
Options(options).
Execute()
// data = { allowed: true }
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
var options = new ClientCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA"
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "viewer",
Object = "document:new-roadmap",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
```
Initialize the SDK
```
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
}
body = ClientCheckRequest(
user="user:anne",
relation="viewer",
object="document:new-roadmap",
)
response = await fga_client.check(body, options)
# response.allowed = true
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
var options = new ClientCheckOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientCheckRequest()
.user("user:anne")
.relation("viewer")
._object("document:new-roadmap");
var response = fgaClient.check(body, options).get();
// response.getAllowed() = true
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
fga query check --store-id=$FGA_STORE_ID --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:anne viewer document:new-roadmap
# Response: {"allowed":true}
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"tuple_key": {
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap"
}
}'
# Response: {"allowed": true}
```
```
check(
user = "user:anne", // check if the user `user:anne`
relation = "viewer", // has an `viewer` relation
object = "document:new-roadmap", // with the object `document:new-roadmap`
);
Reply: true
```
```
is user:anne related to document:new-roadmap as viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
```
## What Is A Check Request?A **check request** is a call to the OpenFGA check endpoint, returning whether the user has a certain relationship with an object.
Check requests use the `check` methods in the OpenFGA SDKs ([JavaScript SDK](https://www.npmjs.com/package/@openfga/sdk)/[Go SDK](https://github.com/openfga/go-sdk)/[.NET SDK](https://www.nuget.org/packages/OpenFga.Sdk)) by manually calling the [check endpoint](https://openfga.dev/api/service#Relationship%20Queries/Check) using curl or in your code. The check endpoint responds with `{ "allowed": true }` if a relationship exists, and with `{ "allowed": false }` if the relationship does not.
For example, the following will check whether `anne` of type user has a `viewer` relation to `document:new-roadmap`:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
- Pseudocode
- Playground
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'viewer',
object: 'document:new-roadmap',
}, {
authorizationModelId: '01HVMMBCMGZNT3SED4Z17ECXCA',
});
// allowed = true
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "viewer",
Object: "document:new-roadmap",
}
data, err := fgaClient.Check(context.Background()).
Body(body).
Options(options).
Execute()
// data = { allowed: true }
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
var options = new ClientCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA"
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "viewer",
Object = "document:new-roadmap",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
```
Initialize the SDK
```
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
}
body = ClientCheckRequest(
user="user:anne",
relation="viewer",
object="document:new-roadmap",
)
response = await fga_client.check(body, options)
# response.allowed = true
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
var options = new ClientCheckOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientCheckRequest()
.user("user:anne")
.relation("viewer")
._object("document:new-roadmap");
var response = fgaClient.check(body, options).get();
// response.getAllowed() = true
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
fga query check --store-id=$FGA_STORE_ID --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:anne viewer document:new-roadmap
# Response: {"allowed":true}
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"tuple_key": {
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap"
}
}'
# Response: {"allowed": true}
```
```
check(
user = "user:anne", // check if the user `user:anne`
relation = "viewer", // has an `viewer` relation
object = "document:new-roadmap", // with the object `document:new-roadmap`
);
Reply: true
```
```
is user:anne related to document:new-roadmap as viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
```
For more information, see the [Relationship Queries page](https://openfga.dev/docs/interacting/relationship-queries.md) and the official [Check API Reference](https://openfga.dev/api/service#Relationship%20Queries/Check).
## What Is A List Objects Request?A **list objects request** is a call to the OpenFGA list objects endpoint that returns all objects of a given type that a user has a specified relationship with.
List objects requests are completed using the `listobjects` methods in the OpenFGA SDKs ([JavaScript SDK](https://www.npmjs.com/package/@openfga/sdk)/[Go SDK](https://github.com/openfga/go-sdk)/[.NET SDK](https://www.nuget.org/packages/OpenFga.Sdk)) by manually calling the [list objects endpoint](https://openfga.dev/api/service#Relationship%20Queries/ListObjects) using curl or in your code.
The list objects endpoint responds with a list of objects for a given type that the user has the specified relationship with.
For example, the following returns all the objects with document type for which `anne` of type user has a `viewer` relation with:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
- Pseudocode
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
const response = await fgaClient.listObjects({
user: "user:anne",
relation: "viewer",
type: "document",
}, {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA",
});
// response.objects = ["document:otherdoc", "document:planning"]
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
options := ClientListObjectsOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientListObjectsRequest{
User: "user:anne",
Relation: "viewer",
Type: "document",
}
data, err := fgaClient.ListObjects(context.Background()).
Body(body).
Options(options).
Execute()
// data = { "objects": ["document:otherdoc", "document:planning"] }
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
var options = new ClientCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientListObjectsRequest {
User = "user:anne",
Relation = "viewer",
Type = "document",
};
var response = await fgaClient.ListObjects(body, options);
// response.Objects = ["document:otherdoc", "document:planning"]
```
Initialize the SDK
```
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
body = ClientListObjectsRequest(
user="user:anne",
relation="viewer",
type="document",
)
response = await fga_client.list_objects(body, options)
# response.objects = ["document:otherdoc", "document:planning"]
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
var options = new ClientListObjectsOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientListObjectsRequest()
.user("user:anne")
.relation("viewer")
.type("document");
var response = fgaClient.listObjects(body, options).get();
// response.getObjects() = ["document:otherdoc", "document:planning"]
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
fga query list-objects --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:anne viewer document
# Response: {"objects": ["document:otherdoc", "document:planning"]}
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-objects \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"type": "document",
"relation": "viewer",
"user":"user:anne"
}'
# Response: {"objects": ["document:otherdoc", "document:planning"]}
```
```
listObjects(
"user:anne", // list the objects that the user `user:anne`
"viewer", // has an `viewer` relation
"document", // and that are of type `document`
);
Reply: ["document:otherdoc", "document:planning"]
```
For more information, see the [Relationship Queries page](https://openfga.dev/docs/interacting/relationship-queries.md) and the [List Objects API Reference](https://openfga.dev/api/service#Relationship%20Queries/ListObjects).
## What Is A List Users Request?A **list users request** is a call to the OpenFGA list users endpoint that returns all users of a given type that have a specified relationship with an object.
List users requests are completed using the relevant `ListUsers` method in SDKs, the `fga query list-users` command in the CLI, or by manually calling the [ListUsers endpoint](https://openfga.dev/api/service#Relationship%20Queries/ListUsers) using curl or in your code.
The list users endpoint responds with a list of users for a given type that have the specificed relationship with an object.
For example, the following returns all the users of type `user` that have the `viewer` relationship for `document:planning`:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
- Pseudocode
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
const response = await fgaClient.listUsers({
object: {
type: "document",
id: "planning"
},
user_filters: [{
type: "user"
}],
relation: "viewer",
}, {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA",
});
// response.users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
options := ClientListUsersOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
userFilters := []openfga.UserTypeFilter{{ Type:"user" }}
body := ClientListUsersRequest{
Object: openfga.Object{
Type: "document",
Id: "planning",
},
Relation: "viewer",
UserFilters: userFilters,
}
data, err := fgaClient.ListUsers(context.Background()).
Body(body).
Options(options).
Execute()
// data.Users = [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientListUsersRequest {
Object = new FgaObject {
Type = "document",
Id = "planning"
},
Relation = "viewer",
UserFilters = new List {
new() {
Type = "user"
}
}
};
var response = await fgaClient.ListUsers(body, options);
// response.Users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
Initialize the SDK
```
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
userFilters = [
UserTypeFilter(type="user")
]
body = ClientListUsersRequest(
object=FgaObject(type="document",id="planning"),
relation="viewer",
user_filters=userFilters,
)
response = await fga_client.list_users(body, options)
# response.users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
var options = new ClientListUsersOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var userFilters = new ArrayList() {
{
add(new UserTypeFilter().type("user"));
}
};
var body = new ClientListUsersRequest()
._object(new FgaObject().type("document").id("planning"))
.relation("viewer")
.userFilters(userFilters);
var response = fgaClient.listUsers(body, options).get();
// response.getUsers() = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
fga query list-users --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA --object document:planning --relation viewer --user-filter user
# Response: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]}
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-users \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"object": {
"type": "document",
"id": "planning",
},
"relation": "viewer",
"user_filters": [
{
"type": "user"
}
]
}'
# Response: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]}
```
```
listUsers(
user_filter=[ "user" ], // list users of type `user`
"viewer", // that have the `viewer` relation
"document:planning", // for the object `document:planning`
);
Reply: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]}
```
For more information, see the [ListUsers API Reference](https://openfga.dev/api/service#Relationship%20Queries/ListUsers).
## What Are Contextual Tuples?Contextual tuples are tuples that can be added to a Check request, a ListObjects request, a ListUsers request, or an Expand request. They only exist within the context of that particular request and are not persisted in the datastore.
Similar to [relationship tuples](#what-is-a-relationship-tuple), contextual tuples are composed of a user, relation and object. Unlike relationship tuples, they are not written to the store. However, if contextual tuples are sent alongside a check request in the context of a particular check request, they are treated if they had been written in the store.
For more information, see [Contextual and Time-Based Authorization](https://openfga.dev/docs/modeling/contextual-time-based-authorization.md), [Authorization Through Organization Context](https://openfga.dev/docs/modeling/organization-context-authorization.md) and [Check API Request Documentation](https://openfga.dev/api/service#Relationship%20Queries/Check).
## What Is Type Bound Public Access?In OpenFGA, type bound public access (represented by `:*`) is a special OpenFGA syntax meaning "every object of \[type]" when invoked as a user within a relationship tuple. For example, `user:*` represents every object of type `user` , including those not currently present in the system.
For example, to indicate `document:new-roadmap` is publicly writable (in other words, has everyone of type `user` as an editor, add the following [relationship tuple](#what-is-a-relationship-tuple):
```
[{
"user": "user:*",
"relation": "editor",
"object": "document:new-roadmap"
}]
```
Note: `:*` cannot be used in the `relation` or `object` properties. In addition, `:*` cannot be used as part of a userset in the tuple's user field. For more information, see [Modeling Public Access](https://openfga.dev/docs/modeling/public-access.md) and [Advanced Modeling: Modeling Google Drive](https://openfga.dev/docs/modeling/advanced/gdrive.md).
## Related Sections
Check the following sections for more on how object-to-object relationships can be used.
**Authorization Concepts**
Learn about Authorization.
- [More](https://openfga.dev/docs/authorization-concepts.md)
**Configuration Language**
Learning about the FGA configuration language
- [More](https://openfga.dev/docs/configuration-language.md)
**Direct access**
Get started with modeling your permission system in OpenFGA
- [More](https://openfga.dev/docs/modeling/direct-access.md)
---
# Configuration Language
OpenFGA's Configuration Language builds a representation of a system's [authorization model](https://openfga.dev/docs/concepts.md#what-is-an-authorization-model), which informs [OpenFGA's API](https://openfga.dev/api/service) on the [object types](https://openfga.dev/docs/concepts.md#what-is-a-type) in the system and how they relate to each other. The Configuration Language describes the [relations](https://openfga.dev/docs/concepts.md#what-is-a-relation) possible for an object of a given type and lists the conditions under which one is related to that object.
The Configuration Language can be presented in **DSL** or **JSON** syntax. The JSON syntax is accepted by the API and closely tracks the language in the [Zanzibar paper](https://research.google/pubs/pub48190/). The DSL adds syntactic sugar on top of JSON for ease of use, but compiles down to JSON before being sent to OpenFGA's API. JSON syntax is used to call API directly or through the [SDKs](https://openfga.dev/docs/getting-started.md), while DSL is used to interact with OpenFGA in the [Playground](https://play.fga.dev/), the [CLI](https://github.com/openfga/cli), and the IDE extensions for [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=openfga.openfga-vscode) and [IntelliJ](https://plugins.jetbrains.com/plugin/24394-openfga). They can be switched between throughout this documentation.
Please familiarize yourself with basic [OpenFGA Concepts](https://openfga.dev/docs/concepts.md) and [How to get started on modeling](https://openfga.dev/docs/modeling/getting-started.md) before starting this guide.
## What Does The Configuration Language Look Like?
Below is a sample authorization model. The next sections discuss the basics of the OpenFGA configuration language.
- DSL
- JSON
```
model
schema 1.1
type user
type domain
relations
define member: [user]
type folder
relations
define can_share: writer
define owner: [user, domain#member] or owner from parent_folder
define parent_folder: [folder]
define viewer: [user, domain#member] or writer or viewer from parent_folder
define writer: [user, domain#member] or owner or writer from parent_folder
type document
relations
define can_share: writer
define owner: [user, domain#member] or owner from parent_folder
define parent_folder: [folder]
define viewer: [user, domain#member] or writer or viewer from parent_folder
define writer: [user, domain#member] or owner or writer from parent_folder
```
```
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "domain",
"relations": {
"member": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "folder",
"relations": {
"can_share": {
"computedUserset": {
"object": "",
"relation": "writer"
}
},
"owner": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent_folder"
},
"computedUserset": {
"object": "",
"relation": "owner"
}
}
}
]
}
},
"parent_folder": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "writer"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent_folder"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "owner"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent_folder"
},
"computedUserset": {
"object": "",
"relation": "writer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"parent_folder": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
}
}
}
},
{
"type": "document",
"relations": {
"can_share": {
"computedUserset": {
"object": "",
"relation": "writer"
}
},
"owner": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent_folder"
},
"computedUserset": {
"object": "",
"relation": "owner"
}
}
}
]
}
},
"parent_folder": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "writer"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent_folder"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
},
"writer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "owner"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent_folder"
},
"computedUserset": {
"object": "",
"relation": "writer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"parent_folder": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "domain",
"relation": "member"
}
]
}
}
}
}
]
}
```
info
The authorization model describes four [types](https://openfga.dev/docs/concepts.md#what-is-a-type) of objects: `user`, `domain`, `folder` and `document`.
The `domain` [type definition](https://openfga.dev/docs/concepts.md#what-is-a-type-definition) has a single [relation](https://openfga.dev/docs/concepts.md#what-is-a-relation) called `member` that only allows [direct relationships](https://openfga.dev/docs/concepts.md#what-are-direct-and-implied-relationships).
The `folder` and `document` type definitions each have five relations: `parent_folder`, `owner`, `writer`, `viewer` and `can_share`.
### Direct Relationship Type Restrictions
When used at the beginning of a [relation definition](https://openfga.dev/docs/concepts.md#what-is-a-relation-definition), `[, ...]` allows [direct relationships](https://openfga.dev/docs/concepts.md#what-are-direct-and-implied-relationships) by the objects of these specified types. The strings can be in one of three formats:
- ``: indicates that tuples relating objects of those types as users can be written. For example, `group:marketing` can be related if `group` is in the type restrictions.
- ``: indicates that a tuple relating all objects of that type can be written. For example, `user:*` can be added if `user:*` is in the type restrictions.
- `#`: indicates tuples with sets of users related to an object of that type by that particular relation. For example, `group:marketing#member` can be added if `group#member` is in the type restrictions.
If no direct relationship type restrictions are specified, direct relationships are disallowed and tuples cannot be written relating other objects of this particular relation with objects of this type.
info
`[, , ...]` in the OpenFGA DSL translates to `this` in the OpenFGA API syntax.
For example, below is a snippet of the `team` type:
```
type team
relations
define member: [user, user:*, team#member]
```
The `team` [type definition](https://openfga.dev/docs/concepts.md#what-is-a-type-definition) above defines all the [relations](https://openfga.dev/docs/concepts.md#what-is-a-relation) that [users](https://openfga.dev/docs/concepts.md#what-is-a-user) can have with an _[object](https://openfga.dev/docs/concepts.md#what-is-an-object)_ of type `team`. In this example, the relation is `member`.
Because of the `[user, team#member]` direct relationship type restrictions used, a user in the system can have a **[direct relationship](https://openfga.dev/docs/concepts.md#what-are-direct-and-implied-relationships)** with the `team` type as a `member` for objects of:
- type `user`
- the `user` [type bound public access](https://openfga.dev/docs/concepts.md#what-is-type-bound-public-access) (`user:*`)
- [usersets](https://openfga.dev/docs/modeling/building-blocks/usersets.md) that have a `team` type and a `member` relation (e.g. `team:product#member`)
In the type definition snippet above, `anne` is a `member` of `team:product` if any of the following relationship tuple sets exist:
- ```
[// Anne is directly related to the product team as a member
{
"user": "user:anne",
"relation": "member",
"object": "team:product",
"_description": "Anne is directly related to the product team as a member"
}]
```
- ```
[// Everyone (`*`) is directly related to the product team as a member
{
"user": "user:*",
"relation": "member",
"object": "team:product",
"_description": "Everyone (`*`) is directly related to the product team as a member"
}]
```
- ```
[// Members of the contoso team are members of the product team
{
"user": "team:contoso#member",
"relation": "member",
"object": "team:product",
"_description": "Members of the contoso team are members of the product team"
}// Anne is a member of the contoso team
{
"user": "user:anne",
"relation": "member",
"object": "team:contoso",
"_description": "Anne is a member of the contoso team"
}]
```
For more examples, see [Modeling Building Blocks: Direct Relationships](https://openfga.dev/docs/modeling/building-blocks/direct-relationships.md).
### Referencing Other Relations On The Same Object
The same object can also reference other relations. Below is a simplified `document` type definition:
```
type document
relations
define editor: [user]
define viewer: [user] or editor
define can_rename: editor
```
Above, `document` [type definition](https://openfga.dev/docs/concepts.md#what-is-a-type-definition) defines all the [relations](https://openfga.dev/docs/concepts.md#what-is-a-relation) that [users](https://openfga.dev/docs/concepts.md#what-is-a-user) can have with an [object](https://openfga.dev/docs/concepts.md#what-is-an-object) of type `document`. In this case, the relations are `editor`, `viewer` and `can_rename`. The `viewer` and `can_rename` relation definitions both reference `editor`, which is another relation of the same type.
info
`can_rename` does not reference the [direct relationship type restrictions](#direct-relationship-type-restrictions), which means a user cannot be directly assigned this relation and it must be inherited when the `editor` relation is assigned. Conversely, the `viewer` relation allows both direct and indirect relationships using the [Union Operator](#the-union-operator).
In the type definition snippet above, `anne` is a `viewer` of `document:new-roadmap` if any one of the following relationship tuple sets exists:
- _anne_ is an _editor_ of _document:new-roadmap_
```
[// Anne is an editor of the new-roadmap document
{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap",
"_description": "Anne is an editor of the new-roadmap document"
}]
```
- _anne_ is a _viewer_ of _document:new-roadmap_
```
[// Anne is a viewer of the new-roadmap document
{
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap",
"_description": "Anne is a viewer of the new-roadmap document"
}]
```
`anne` has a `can_rename` relationship with `document:new-roadmap` only if `anne` has an `editor` relationship with the document:
- _anne_ is an _editor_ of _document:new-roadmap_
```
[// Anne is an editor of thew new-roadmap document
{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap",
"_description": "Anne is an editor of thew new-roadmap document"
}]
```
For more examples, see [Modeling Building Blocks: Concentric Relationships](https://openfga.dev/docs/modeling/building-blocks/concentric-relationships.md), [Modeling: Roles and Permissions](https://openfga.dev/docs/modeling/roles-and-permissions.md) and [Advanced Modeling: Google Drive](https://openfga.dev/docs/modeling/advanced/gdrive.md).
### Referencing Relations On Related Objects
Another set of [indirect relationships](https://openfga.dev/docs/concepts.md#what-are-direct-and-implied-relationships) are made possible by referencing relations to other objects.
The syntax is `X from Y` and requires that:
- the other object is related to the current object as `Y`
- the _user_ is related to another object as `X`
See the _authorization model_ below.
```
model
schema 1.1
type user
type folder
relations
define viewer: [user, folder#viewer]
type document
relations
define parent_folder: [folder]
define viewer: [user] or viewer from parent_folder
```
The snippet below (taken from the authorization model above) states that viewers of a document are both (a) all users directly assigned the viewer relation and (b) all users who can view the document's parent folder.
```
type document
relations
define viewer: [user] or viewer from parent_folder
```
In the authorization model above, `user:anne` is a `viewer` of `document:new-roadmap` if any one of the following relationship tuples sets exists:
- Anne is a viewer of the parent folder of the new-roadmap document
```
[// planning folder is the parent folder of the new-roadmap document
{
"user": "folder:planning",
"relation": "parent_folder",
"object": "document:new-roadmap",
"_description": "planning folder is the parent folder of the new-roadmap document"
}// anne is a viewer of the planning folder
{
"user": "user:anne",
"relation": "viewer",
"object": "folder:planning",
"_description": "anne is a viewer of the planning folder"
}]
```
- Anne is a viewer of the new-roadmap document (direct relationship)
```
[// anne is a viewer of the new-roadmap document
{
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap",
"_description": "anne is a viewer of the new-roadmap document"
}]
```
Referencing relations on related objects defines transitive implied relationship. If User A is related to Object B as a viewer, and Object B is related to Object C as parent, then User A is related to Object C as viewer. This can indicate that viewers of a folders are viewers of all documents in that folder.
caution
OpenFGA does not allow the referenced relation (the word after `from`, also called the tupleset) to reference another relation and does not allow non-concrete types (type bound public access (`:*`) or usersets (`#`)) in its type restrictions; adding them throws a validation error when calling `WriteAuthorizationModel`.
For more examples, see [Modeling: Parent-Child Objects](https://openfga.dev/docs/modeling/parent-child.md), [Advanced Modeling: Google Drive](https://openfga.dev/docs/modeling/advanced/gdrive.md), [Advanced Modeling: GitHub](https://openfga.dev/docs/modeling/advanced/github.md), and [Advanced Modeling: Entitlements](https://openfga.dev/docs/modeling/advanced/entitlements.md).
### The Union Operator
The **union operator** (`or` in the DSL, `union` in the JSON syntax) indicates that a [relationship](https://openfga.dev/docs/concepts.md#what-is-a-relationship) exists if the [user](https://openfga.dev/docs/concepts.md#what-is-a-user) is in any of the sets of users (`union`).
```
type document
relations
define viewer: [user] or editor
```
In the [type definition](https://openfga.dev/docs/concepts.md#what-is-a-type-definition) snippet above, `user:anne` is a `viewer` of `document:new-roadmap` if any of the following conditions are satisfied:
- there exists a [direct relationship](https://openfga.dev/docs/concepts.md#what-are-direct-and-implied-relationships) with _anne_ as _editor_ of _document:new-roadmap_
```
[{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap"
}]
```
- _anne_ is a _viewer_ of _document:new-roadmap_
```
[{
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap"
}]
```
info
The above [authorization model](https://openfga.dev/docs/concepts.md#what-is-an-authorization-model) indicates that a user is related as a viewer if they are in any of the following:
- the userset of all users related to the object as "viewer", indicating that a user can be assigned a direct `viewer` relation
- the userset of all users related to the object as "editor", indicating that a user who is an editor is also implicitly a viewer
If `anne` is in at least one of those usersets, meaning `anne` is either an `editor` or a `viewer`, the [check](https://openfga.dev/docs/concepts.md#what-is-a-check-request) on `{"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"}` returns `{"allowed": true}`.
For more examples, see [Modeling Building Blocks: Concentric Relationships](https://openfga.dev/docs/modeling/building-blocks/concentric-relationships.md), [Modeling Roles and Permissions](https://openfga.dev/docs/modeling/roles-and-permissions.md) and [Advanced Modeling: Modeling for IoT](https://openfga.dev/docs/modeling/advanced/iot.md#03-updating-our-authorization-model-to-facilitate-future-changes).
### The Intersection Operator
The **intersection operator** (`and` in the DSL, `intersection` in the JSON syntax) indicates that a [relationship](https://openfga.dev/docs/concepts.md#what-is-a-relationship) exists if the [user](https://openfga.dev/docs/concepts.md#what-is-a-user) is in all the sets of users.
```
type document
relations
define viewer: authorized_user and editor
```
In the [type definition](https://openfga.dev/docs/concepts.md#what-is-a-type-definition) snippet above, `user:anne` is a `viewer` of `document:new-roadmap` if all of the following conditions are satisfied:
- _anne_ is an _editor_ of _document:new-roadmap_
```
[{
"user": "user:anne",
"relation": "editor",
"object": "document:new-roadmap"
}]
```
AND
- _anne_ is an _authorized\_user_ of _document:new-roadmap_:
```
[{
"user": "user:anne",
"relation": "authorized_user",
"object": "document:new-roadmap"
}]
```
info
The above [authorization model](https://openfga.dev/docs/concepts.md#what-is-an-authorization-model) indicates that a user is related as a viewer if they are in all of the following:
- the userset of all users related to the object as `authorized_user`
- the userset of all users related to the object as `editor`
`anne` must be in the intersection of the usersets (meaning both an `editor` AND an `authorized_user`) for the [check](https://openfga.dev/docs/concepts.md#what-is-a-check-request) on `{"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"}` to return `{"allowed": true}`.
`anne` is not a `viewer` for `document:new-roadmap` if either of the following is true:
- `anne` is not an `editor` to `document:new-roadmap`: no relationship tuple of `{"user": "user:anne", "relation": "editor", "object": "document:new-roadmap"}`
- `anne` is not an `authorized_user` on the `document:new-roadmap`: no relationship tuple of `{"user": "user:anne", "relation": "authorized_user", "object": "document:new-roadmap"}`
For more examples, see [Modeling with Multiple Restrictions](https://openfga.dev/docs/modeling/multiple-restrictions.md).
### The Exclusion Operator
The **exclusion operator** (`but not` in the DSL, `difference` in the JSON syntax) indicates that a [relationship](https://openfga.dev/docs/concepts.md#what-is-a-relationship) exists if the [user](https://openfga.dev/docs/concepts.md#what-is-a-user) is in the base userset but not in the excluded userset. This operator is particularly useful when modeling exclusion or block lists.
```
type document
relations
define viewer: [user] but not blocked
```
In the type definition snippet above, `user:anne` is a `viewer` of `document:new-roadmap` if and only if:
- `anne` has a direct relationship as `viewer` to `document:new-roadmap`
```
[{
"user": "user:anne",
"relation": "viewer",
"object": "document:new-roadmap"
}]
```
AND
- `anne` is not blocked from `document:new-roadmap` (i.e., the following relationship tuple must not exist):
```
[{
"user": "user:anne",
"relation": "blocked",
"object": "document:new-roadmap"
}]
```
For more information, see [Modeling: Blocklists](https://openfga.dev/docs/modeling/blocklists.md).
info
The [authorization model](https://openfga.dev/docs/concepts.md#what-is-an-authorization-model) above indicates that a user is related as a viewer if they are in:
- the userset of all users related to the object as `viewer`
but not in:
- the userset of all users related to the object as `blocked`
`anne` must be both a `viewer` and not `blocked` for the [check](https://openfga.dev/docs/concepts.md#what-is-a-check-request) on `{"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"}` to return `{"allowed": true}`.
`anne` is not a viewer for document:new-roadmap if either of the following is true:
- `anne` is **not** assigned direct relationship as viewer to document:new-roadmap: **no relationship tuple of** `{"user": "user:anne", "relation": "viewer", "object": "document:new-roadmap"}`
- `anne` is blocked on the document:new-roadmap `{"user": "user:anne", "relation": "blocked", "object": "document:new-roadmap"}`
### Grouping and nesting operators
You can define complex conditions by using parentheses to group and nest operators. Note that direct relationships can be included in an expression with parentheses.
```
type user
type organization
relations
define member: [user]
type folder
relations
define organization: [organization]
define parent: [folder]
define viewer: ([user] or viewer from parent) and member from organization
```
### Conditional relationships
OpenFGA supports conditional relationships, which are only considered if a specific condition is met. You can learn more about Conditional Relationships in the [Modeling: Conditional Relationships](https://openfga.dev/docs/modeling/conditions.md) guide.
## Equivalent Zanzibar Concepts
The JSON syntax accepted by the OpenFGA API closely mirrors the syntax represented in the Zanzibar paper. The major modifications are a slight flattening and conversion of keys from `snake_case` to `camelCase`.
| Zanzibar | OpenFGA JSON | OpenFGA DSL |
| ------------------ | ---------------- | ------------------------------------------------------------- |
| `this` | `this` | [`[,]`](#direct-relationship-type-restrictions) |
| `union` | `union` | `or` |
| `intersection` | `intersection` | `and` |
| `exclusion` | `difference` | `but not` |
| `tuple_to_userset` | `tupleToUserset` | `x from y` |
The [Zanzibar paper](https://research.google/pubs/pub48190/) presents this example:
```
name: "doc"
relation { name: "owner" }
relation {
name: "editor"
userset_rewrite {
union {
child { _this {} }
child { computed_userset { relation: "owner" } }
}}}
relation {
name: "viewer"
userset_rewrite {
union {
child { _this {} }
child { computed_userset { relation: "editor" } }
child { tuple_to_userset {
tupleset { relation: "parent" }
computed_userset {
object: $TUPLE_USERSET_OBJECT # parent folder
relation: "viewer" }}}
}}}
```
In the OpenFGA DSL, it becomes:
```
model
schema 1.1
type doc
relations
define owner: [user]
define editor: [user] or owner
define viewer: [user] or editor or viewer from parent
```
In the OpenFGA JSON, it becomes:
```
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "doc",
"relations": {
"owner": {
"this": {}
},
"editor": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "owner"
}
}
]
}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "editor"
}
},
{
"tupleToUserset": {
"tupleset": {
"relation": "parent"
},
"computedUserset": {
"relation": "viewer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
```
The following snippet:
```
model
schema 1.1
type doc
relations
define viewer: [user] or editor or viewer from parent
```
Results in the following outcome:
- The users with a viewer relationship to a certain doc are any of:
- the set of users who are [directly related](https://openfga.dev/docs/concepts.md#what-are-direct-and-implied-relationships) with this doc as `viewer`
- the set of users who are related to this doc as `editor`
- the set of users who are related to any object OBJ\_1 as `viewer`, where object OBJ\_1 is any object related to this doc as `parent` (e.g. viewers of this doc's parent folder, where the parent folder is OBJ\_1)
Learn more about Zanzibar at the [Zanzibar Academy](https://zanzibar.academy).
## Related Sections
Check the following sections for more on how to use the configuration language in modeling authorization.
**OpenFGA Concepts**
Learn about the OpenFGA Concepts.
- [More](https://openfga.dev/docs/concepts.md)
**Modeling: Getting Started**
Learn about how to get started with modeling your permission system in OpenFGA.
- [More](https://openfga.dev/docs/modeling/getting-started.md)
**Direct Access**
Learn about modeling user access to an object.
- [More](https://openfga.dev/docs/modeling/direct-access.md)
---
# Introduction to OpenFGA
OpenFGA is a scalable open source authorization system for developers that allows implementing authorization for any kind of application and smoothly evolve as complexity increases over time. It is owned by the [Cloud Native Computing Foundation](https://cncf.io).
Inspired by [Google’s Zanzibar](https://zanzibar.academy), Google’s internal authorization system, OpenFGA relies on Relationship-Based Access Control, which allows developers to easily implement Role-Based Access Control and provides additional capabilities to implement Attribute-Based Access Control. You can learn more about different authorization concepts [here](https://openfga.dev/docs/authorization-concepts.md).
## Benefits
OpenFGA provides developers the following benefits:
- Move authorization logic outside of application code, making it easier to write, change and audit.
- Increase velocity by standardizing on a single authorization solution.
- Centralize authorization decisions and audit logs making it simpler to comply with security and compliance requirements.
- Help their products to move faster because it is simpler to evolve authorization policies.
## Features
OpenFGA helps developers achieve those benefits with features as:
- Support for multiple [stores](https://openfga.dev/docs/concepts.md#what-is-a-store) that allow authorization management in different environments (prod/testing/dev) and use cases (internal apps, external apps, infrastructure).
- Support for some ABAC scenarios with [Contextual Tuples](https://openfga.dev/docs/modeling/token-claims-contextual-tuples.md) and [Conditional Relationship Tuples](https://openfga.dev/docs/modeling/conditions.md).
- SDKs for [Java](https://github.com/openfga/java-sdk), [.NET](https://github.com/openfga/dotnet-sdk), [Javascript](https://github.com/openfga/js-sdk), [Go](https://github.com/openfga/go-sdk), and [Python](https://github.com/openfga/python-sdk).
- [HTTP](https://docs.fga.dev/api/service) and [gRPC](https://buf.build/openfga/api) APIs.
- Support for being run as a library, from with a Go based service.
- Support for using Postgres, MySQL or SQLite as the production datastore, as well as an in-memory datastore for non-production usage.
- [A Command Line Interface tool](https://openfga.dev/docs/getting-started/cli.md) for managing OpenFGA stores, test models, import/export models, and data.
- Github Actions for [testing](https://github.com/marketplace/actions/openfga-model-testing-action) and [deploying](https://github.com/marketplace/actions/openfga-model-deploy-action) models.
- A [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=openfga.openfga-vscode) with syntax highlighting and validation of FGA models and tests.
- [Helm Charts](https://github.com/openfga/helm-charts) to easily deploy to Kubernetes.
- [OpenTelemetry](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga#telemetry) support to integrate it with your monitoring infrastructure.
## Related Sections
Check the following sections to learn more about OpenFGA.
**Authorization Concepts**
Learn about Authorization.
- [More](https://openfga.dev/docs/authorization-concepts.md)
**Product Concepts**
Learn about OpenFGA.
- [More](https://openfga.dev/docs/concepts.md)
**Modeling: Getting Started**
Learn about how to get started with modeling your permission system in OpenFGA.
- [More](https://openfga.dev/docs/modeling/getting-started.md)
**Use Cases**
Patterns for AI agents, RAG, MCP servers, multi-tenant SaaS, and microservices.
- [More](https://openfga.dev/docs/use-cases.md)
**Industries**
Sample models for healthcare, banking, e-commerce, HR, CRM, and LMS.
- [More](https://openfga.dev/docs/industries.md)
**Learn Authorization**
Zanzibar, ReBAC vs RBAC, ABAC vs ReBAC, and fine-grained authorization explained.
- [More](https://openfga.dev/docs/learn.md)
---
The following will provide a step-by-step guide on how to get started with OpenFGA.
**When to use**
This section is useful if you understand the basic concepts of OpenFGA, and want to learn how to get started.
# Getting Started
**Setup OpenFGA**
How to setup an OpenFGA server.
- [Setup OpenFGA](https://openfga.dev/docs/getting-started/setup-openfga/overview.md)
**Install SDK Client**
Install the SDK for the language of your choice.
- [Install SDK Client](https://openfga.dev/docs/getting-started/install-sdk.md)
**Create a Store**
Creating an OpenFGA entity that owns an authorization model and relationship tuples.
- [Create a Store](https://openfga.dev/docs/getting-started/create-store.md)
**Setup SDK Client for Store**
Configure the SDK client for your store.
- [Setup SDK Client for Store](https://openfga.dev/docs/getting-started/setup-sdk-client.md)
**Configure Authorization Model**
Programmatically configure authorization model for an OpenFGA store.
- [Configure Authorization Model](https://openfga.dev/docs/getting-started/configure-model.md)
**Update Relationship Tuples**
Programmatically write authorization data to an OpenFGA store.
- [Update Relationship Tuples](https://openfga.dev/docs/getting-started/update-tuples.md)
**Perform a Check**
Programmatically perform an authorization check against an OpenFGA store.
- [Perform a Check](https://openfga.dev/docs/getting-started/perform-check.md)
**Perform a List Objects Request**
Programmatically perform a list objects request against an OpenFGA store.
- [Perform a List Objects Request](https://openfga.dev/docs/getting-started/perform-list-objects.md)
**Integrate Within a Framework**
Integrate authorization checks with a framework.
- [Integrate Within a Framework](https://openfga.dev/docs/getting-started/framework.md)
**Immutable Authorization Models**
Learn how to take advantage of the immutable properties of Authorization Models in OpenFGA.
- [Immutable Authorization Models](https://openfga.dev/docs/getting-started/immutable-models.md)
**Best Practices**
Best Practices for implementing OpenFGA.
- [Best Practices](https://openfga.dev/docs/best-practices.md)
---
# Use the FGA CLI
The OpenFGA Command Line Interface (CLI) enables you to interact with an FGA store, where you can manage tasks, create stores, and update FGA models, among other actions. For more information on FGA stores, see [What Is A Store](https://openfga.dev/docs/concepts.md#what-is-a-store).
For instructions on installing it, visit the [OpenFGA CLI Github repository](https://github.com/openfga/cli).
## Configuration
The CLI is configured to use a specific FGA server in one of three ways:
- Using CLI flags.
- Using environment variables.
- Using a `.fga.yaml` configuration file, searched in the following order (highest to lowest priority):
1. **Current working directory**
2. **User-specific config directory**
- Unix: `$XDG_CONFIG_HOME` (if set), otherwise `$HOME/.config`
- Windows: `%AppData%`
3. **`fga` subdirectory under the user config directory**
4. **User's home directory**
- Unix: `$HOME`
- Windows: `%USERPROFILE%`
The API Url setting needs to point to the OpenFGA server:
| Name | Flag | Environment | \~/.fga.yaml | Default Value |
| ------- | --------- | ------------- | ------------ | ----------------------- |
| API Url | --api-url | FGA\_API\_URL | api-url | `http://localhost:8080` |
If you use [pre-shared key authentication](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md#pre-shared-key-authentication), provide the following parameters which appends the pre-shared key in the HTTP request header:
| Name | Flag | Environment | \~/.fga.yaml |
| --------- | ----------- | --------------- | ------------ |
| API Token | --api-token | FGA\_API\_TOKEN | api-token |
If you use [OIDC authentication](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md#oidc), configure the following parameters based on the OIDC server that’s used to issue tokens:
| Name | Flag | Environment | \~/.fga.yaml |
| -------------- | ------------------ | ----------------------- | ---------------- |
| Client ID | --client-id | FGA\_CLIENT\_ID | client-id |
| Client Secret | --client-secret | FGA\_CLIENT\_SECRET | client-secret |
| Scopes | --api-scopes | FGA\_API\_SCOPES | api-scopes |
| Token Issuer | --api-token-issuer | FGA\_API\_TOKEN\_ISSUER | api-token-issuer |
| Token Audience | --api-audience | FGA\_API\_AUDIENCE | api-audience |
A default store Id and authorization model Id can also be configured:
| Name | Flag | Environment | \~/.fga.yaml |
| ---------------------- | ---------- | -------------- | ------------ |
| Store ID | --store-id | FGA\_STORE\_ID | store-id |
| Authorization Model ID | --model-id | FGA\_MODEL\_ID | model-id |
All of the examples in this document assume the CLI is properly configured and that the Store ID is set either in an environment variable or the `~/.fga.yaml` file.
## Basic operations
The CLI commands below show you how to create a store and run your application’s most common operations, including how to write a model and write/delete/read tuples, and run queries.
```
# Create a store with a model
$ fga store create --model docs.fga
{
"store": {
"created_at":"2024-02-09T23:20:28.637533296Z",
"id":"01HP82R96XEJX1Q9YWA9XRQ4PM",
"name":"docs",
"updated_at":"2024-02-09T23:20:28.637533296Z"
},
"model": {
"authorization_model_id":"01HP82R97B448K89R45PW7NXD8"
}
}
# Keep the returned store id in an environment variable
$ export FGA_STORE_ID=01HP82R96XEJX1Q9YWA9XRQ4PM
# Get the latest model
$ fga model get
model
schema 1.1
type user
type organization
relations
define admin: [user with non_expired_grant]
define member: [user]
type document
relations
define editor: admin from organization
define organization: [organization]
define viewer: editor or member from organization
condition non_expired_grant(current_time: timestamp, grant_duration: duration, grant_time: timestamp) {
current_time < grant_time + grant_duration
}
# Write a tuple
$ fga tuple write user:anne member organization:acme
{
"successful": [
{
"object":"organization:acme",
"relation":"member",
"user":"user:anne"
}
]
}
# Read all tuples. It returns the one added above
$ fga tuple read
{
"continuation_token":"",
"tuples": [
{
"key": {
"object":"organization:acme",
"relation":"member",
"user":"user:anne"
},
"timestamp":"2024-02-09T23:05:43.586Z"
}
]
}
# Write another tuple, adding a document for the acme organization
$ fga tuple write organization:acme organization document:readme
{
"successful": [
{
"object":"document:readme",
"relation":"organization",
"user":"organization:acme"
}
]
}
# Check if anne can view the document.
# Anne can view it as she's a member of organization:acme, which is the organization that owns the document
$ fga query check user:anne viewer document:readme
{
"allowed":true,
"resolution":""
}
# List all the documents user:anne can view
$ fga query list-objects user:anne viewer document
{
"objects": [
"document:readme"
]
}
# List all the relations that user:anne has with document:readme
$ fga query list-relations user:anne document:readme
{
"relations": [
"viewer"
]
}
# Delete user:anne as a member of organization:acme
$ fga tuple delete user:anne member organization:acme
{}
# Verify that user:anne is no longer a viewer of document:readme
$ fga query check user:anne viewer document:readme
{
"allowed":false,
"resolution":""
}
```
## Work with authorization model versions
OpenFGA models are [immutable](https://openfga.dev/docs/getting-started/immutable-models.md); each time a model is written to a store, a new version of the model is created.
All OpenFGA API endpoints receive an optional authorization model ID that points to a specific version of the model and defaults to the latest model version. Always use a specific model ID and update it each time a new model version is used in production.
The following CLI commands lists the model Ids and find the latest one:
```
# List all the authorization models
$ fga model list
{
"authorization_models": [
{
"id":"01HPJ8JZV091THNTDFE2SFYNNJ",
"created_at":"2024-02-13T22:14:50Z"
},
{
"id":"01HPJ808Q8J56QMK4WNT7MG7P7",
"created_at":"2024-02-13T22:04:37Z"
},
{
"id":"01HPJ7YKNV0QT0S6CFRJMK231P",
"created_at":"2024-02-13T22:03:43Z"
}
]
}
# List the last model, displaying just the model ID
$ fga model get --field id
# Model ID: 01HPJ8JZV091THNTDFE2SFYNNJ
# List the last model, displaying just the model ID, in JSON format, to make it simpler to parse
$ fga model get --field id --format json
{
"id":"01HPJ8JZV091THNTDFE2SFYNNJ"
}
```
When using the CLI, the model ID can be specified as a `--model-id` parameter or as part of the configuration.
## Import tuples
To import tuples, use the`fga tuple write` command. It has the following parameters:
| Parameter | Description |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| --file | Specifies the file name json, yaml and csv files are supported |
| --max-tuples-per-write (optional, default=1, max=40) | Maximum number of tuples to send in a single write |
| --max-parallel-requests (optional, default=4) | Maximum number of requests to send in parallel. Make it larger if you want to import a large number of tuples faster |
| --hide-imported-tuples (optional, default=false) | Hide successful imports from output, useful when importing large datasets |
The CLI returns a detailed JSON response that includes:
- `successful`: List of successfully written tuples (hidden when using `--hide-imported-tuples`)
- `failed`: List of tuples that failed to write, including the error reason
- `total_count`: Total number of tuples processed in this operation
- `successful_count`: Number of tuples successfully written
- `failed_count`: Number of tuples that failed to write
When using `--hide-imported-tuples`, the successful tuples are not included in the output, making it more practical when importing large datasets. Failed tuples are always shown to help identify and fix any issues. If you specify `--max-tuples-per-write` greater than one, an error in one of the tuples implies none of the tuples get written.
```
$ fga tuple write --file tuples.yaml
{
"successful": [
{
"object":"organization:acme",
"relation":"member",
"user":"user:anne"
}
],
"failed":null,
"total_count": 1,
"successful_count": 1,
"failed_count": 0
}
$ fga tuple write --file tuples.yaml
{
"successful":null,
"failed": [
{
"tuple_key": {
"object":"organization:acme",
"relation":"member",
"user":"user:anne"
},
"reason":"Write validation error for POST Write with body {\"code\":\"write_failed_due_to_invalid_input\",\"message\":\"cannot write a tuple which already exists: user: 'user:anne', relation: 'member', object: 'organization:acme': invalid write input\"}\n with error code write_failed_due_to_invalid_input error message: cannot write a tuple which already exists: user: 'user:anne', relation: 'member', object: 'organization:acme': invalid write input"
}
],
"total_count": 1,
"successful_count": 0,
"failed_count": 1
}
```
Below are examples of the different file formats the CLI accepts when writing tuples:
### yaml
```
- user: user:peter
relation: admin
object: organization:acme
condition:
name: non_expired_grant
context:
grant_time: '2024-02-01T00:00:00Z'
grant_duration: 1h
- user: user:anne
relation: member
object: organization:acme
```
### JSON
```
[
{
"user": "user:peter",
"relation": "admin",
"object": "organization:acme",
"condition": {
"context": {
"grant_duration": "1h",
"grant_time": "2024-02-01T00:00:00Z"
},
"name": "non_expired_grant"
}
},
{
"user": "user:anne",
"relation": "member",
"object": "organization:acme"
}
]
```
### CSV
```
user_type,user_id,user_relation,relation,object_type,object_id,condition_name,condition_context
user,anne,member,,organization,acme,,
user,peter1,admin,,organization,acme,non_expired_grant,"{""grant_duration"": ""1h"", ""grant_time"": ""2024-02-01T00:00:00Z""}"
```
When using the CSV format, you can omit certain headers, and you don’t need to specify the value for those fields.
## Delete Tuples
To delete a tuple, specify the user/relation/object you want to delete. To delete a group of tuples, specify a file that contains those tuples. Supported file formats are `json`, `yaml` and `csv`.
```
$ fga tuple delete --file tuples.yaml
{
"successful": [
{
"object":"organization:acme",
"relation":"admin",
"user":"user:peter"
},
{
"object":"organization:acme",
"relation":"member",
"user":"user:anne"
}
],
"failed":null
}
```
Delete all tuples from a store by reading all the tuples first and then deleting them:
```
# Reads all the tuples and outputs them in a json format that can be used by 'fga tuple delete' and 'fga tuple write'.
$ fga tuple read --output-format=simple-json --max-pages 0 > tuples.json
$ fga tuple delete --file tuples.json
```
## Import stores
The CLI can import an [FGA Test file](https://openfga.dev/docs/modeling/testing.md) in a store. It writes the model included and imports the tuples in the fga test file.
Given the following `.fga.yaml` file:
```
model: |
model
schema 1.1
type user
type organization
relations
define member : [user]
}
tuples:
# Anne is a member of the Acme organization
- user: user:anne
relation: member
object: organization:acme
```
The following command is used to import the file contents in a store:
```
$ fga store import --file .fga.yaml
```
Use the `fga model get` command is used to verify that the model was correctly written, and the `fga tuple read` command is used to verify that the tuples were properly imported.
## Related Sections
Check the following sections for more on how to learn how to write tests.
**Testing Models**
Learn how to test FGA models using the FGA CLI.
- [More](https://openfga.dev/docs/modeling/testing.md)
---
# Configure Authorization Model for a Store
This article explains how to configure an [authorization model](https://openfga.dev/docs/concepts.md#what-is-an-authorization-model) for a [store](https://openfga.dev/docs/concepts.md#what-is-a-store) in an OpenFGA server.
## Before you start
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md), [created the store](https://openfga.dev/docs/getting-started/create-store.md) and [setup the SDK client](https://openfga.dev/docs/getting-started/setup-sdk-client.md).
3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md), [created the store](https://openfga.dev/docs/getting-started/create-store.md) and [setup the SDK client](https://openfga.dev/docs/getting-started/setup-sdk-client.md).
3) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md), [created the store](https://openfga.dev/docs/getting-started/create-store.md) and [setup the SDK client](https://openfga.dev/docs/getting-started/setup-sdk-client.md).
3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md), [created the store](https://openfga.dev/docs/getting-started/create-store.md) and [setup the SDK client](https://openfga.dev/docs/getting-started/setup-sdk-client.md).
3) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md), [created the store](https://openfga.dev/docs/getting-started/create-store.md) and [setup the SDK client](https://openfga.dev/docs/getting-started/setup-sdk-client.md).
3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the CLI](https://openfga.dev/docs/getting-started/install-sdk.md), [created the store](https://openfga.dev/docs/getting-started/create-store.md) and [setup your environment variables](https://openfga.dev/docs/getting-started/setup-sdk-client.md).
3) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [created the store](https://openfga.dev/docs/getting-started/create-store.md) and have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
## Step by step
Assume that you want to configure your store with the following model.
```
model
schema 1.1
type user
type document
relations
define reader: [user]
define writer: [user]
define owner: [user]
```
To configure authorization model, we can invoke the [write authorization models API](https://openfga.dev/api/service#Authorization%20Models/WriteAuthorizationModel).
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
const { authorization_model_id: id } = await fgaClient.writeAuthorizationModel({
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"reader": {
"this": {}
},
"writer": {
"this": {}
},
"owner": {
"this": {}
}
},
"metadata": {
"relations": {
"reader": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"owner": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
});
// id = "01HVMMBCMGZNT3SED4Z17ECXCA"
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
var writeAuthorizationModelRequestString = "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"
var body WriteAuthorizationModelRequest
if err := json.Unmarshal([]byte(writeAuthorizationModelRequestString), &body); err != nil {
// .. Handle error
return
}
data, err := fgaClient.WriteAuthorizationModel(context.Background()).
Body(body).
Execute()
if err != nil {
// .. Handle error
}
// data.AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA"
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
var modelJson = "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}";
var body = JsonSerializer.Deserialize(modelJson);
var response = await fgaClient.WriteAuthorizationModel(body);
// response.AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA"
```
Initialize the SDK
```
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
# from openfga_sdk.models.write_authorization_model_request import WriteAuthorizationModelRequest
async def write_authorization_model():
body_string = "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"
response = await fga_client_instance.write_authorization_model(json.loads(body))
# response.authorization_model_id = "01HVMMBCMGZNT3SED4Z17ECXCA"
```
Initialize the SDK
```
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
// import com.fasterxml.jackson.databind.ObjectMapper;
// import dev.openfga.sdk.api.model.WriteAuthorizationModelRequest;
var mapper = new ObjectMapper().findAndRegisterModules();
var authorizationModel = fgaClient
.writeAuthorizationModel(mapper.readValue("{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}", WriteAuthorizationModelRequest.class))
.get();
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
fga model write --store-id=${FGA_STORE_ID} --format=json '{"schema_version":"1.1","type_definitions":[{"type":"user"},{"type":"document","relations":{"reader":{"this":{}},"writer":{"this":{}},"owner":{"this":{}}},"metadata":{"relations":{"reader":{"directly_related_user_types":[{"type":"user"}]},"writer":{"directly_related_user_types":[{"type":"user"}]},"owner":{"directly_related_user_types":[{"type":"user"}]}}}}]}'
```
Set FGA\_API\_URL according to the service you are using (e.g. https\://api.fga.example)
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/authorization-models \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"schema_version":"1.1","type_definitions":[{"type":"user"},{"type":"document","relations":{"reader":{"this":{}},"writer":{"this":{}},"owner":{"this":{}}},"metadata":{"relations":{"reader":{"directly_related_user_types":[{"type":"user"}]},"writer":{"directly_related_user_types":[{"type":"user"}]},"owner":{"directly_related_user_types":[{"type":"user"}]}}}}]}'
```
The API will then return the authorization model ID.
Note
The OpenFGA API only accepts an authorization model in the API's JSON syntax.
To convert between the API Syntax and the friendly DSL, you can use the [FGA CLI](https://github.com/openfga/cli/).
## Related Sections
Take a look at the following sections for more information on how to configure authorization model in your store.
**Getting Started with Modeling**
Read how to get started with modeling.
- [More](https://openfga.dev/docs/modeling/getting-started.md)
**Modeling: Direct Relationships**
Read the basics of modeling authorization and granting access to users.
- [More](https://openfga.dev/docs/modeling/direct-access.md)
---
# Configure SDK Client Telemetry
The OpenFGA SDK Client supports telemetry data collection using [OpenTelemetry](https://opentelemetry.io).
## Enabling Telemetry
1. [Install the OpenFGA SDK Client](https://openfga.dev/docs/getting-started/install-sdk.md)
2. [Setup OpenTelemetry](https://opentelemetry.io/docs/getting-started/)
3. Install the OpenTelemetry SDK dependencies for your application
4. Instantiate the OpenTelemetry SDK in your application
Once you have completed these steps, the OpenFGA SDK Client will automatically collect telemetry data using your application's OpenTelemetry configuration.
## Customizing Telemetry
The OpenFGA SDK Client will automatically use [a default configuration](#supported-metrics) for telemetry collection. You can provide your own configuration to include additional metrics or to exclude metrics that are not relevant to your application.
- Node.js
- Go
- .NET
- Python
- Java
```
import 'dotenv/config';
import { OpenFgaClient, TelemetryAttribute, TelemetryConfiguration, TelemetryMetric } from '@openfga/sdk';
const telemetryConfig = {
metrics: {
[TelemetryMetric.CounterCredentialsRequest]: {
attributes: new Set([
TelemetryAttribute.UrlScheme,
TelemetryAttribute.UserAgentOriginal,
TelemetryAttribute.HttpRequestMethod,
TelemetryAttribute.FgaClientRequestClientId,
TelemetryAttribute.FgaClientRequestStoreId,
TelemetryAttribute.FgaClientRequestModelId,
TelemetryAttribute.HttpRequestResendCount,
]),
},
[TelemetryMetric.HistogramRequestDuration]: {
attributes: new Set([
TelemetryAttribute.HttpResponseStatusCode,
TelemetryAttribute.UserAgentOriginal,
TelemetryAttribute.FgaClientRequestMethod,
TelemetryAttribute.FgaClientRequestClientId,
TelemetryAttribute.FgaClientRequestStoreId,
TelemetryAttribute.FgaClientRequestModelId,
TelemetryAttribute.HttpRequestResendCount,
]),
},
[TelemetryMetric.HistogramQueryDuration]: {
attributes: new Set([
TelemetryAttribute.HttpResponseStatusCode,
TelemetryAttribute.UserAgentOriginal,
TelemetryAttribute.FgaClientRequestMethod,
TelemetryAttribute.FgaClientRequestClientId,
TelemetryAttribute.FgaClientRequestStoreId,
TelemetryAttribute.FgaClientRequestModelId,
TelemetryAttribute.HttpRequestResendCount,
]),
},
},
};
const fgaClient = new OpenFgaClient({
telemetry: telemetryConfig,
// ...
});
```
```
import (
"github.com/openfga/go-sdk/client"
"github.com/openfga/go-sdk/telemetry"
)
otel := telemetry.Configuration{
Metrics: &telemetry.MetricsConfiguration{
METRIC_COUNTER_CREDENTIALS_REQUEST: &telemetry.MetricConfiguration{
ATTR_FGA_CLIENT_REQUEST_CLIENT_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_REQUEST_METHOD: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_REQUEST_MODEL_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_REQUEST_STORE_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_RESPONSE_MODEL_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_HOST: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_REQUEST_RESEND_COUNT: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_RESPONSE_STATUS_CODE: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_URL_FULL: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_URL_SCHEME: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_USER_AGENT_ORIGINAL: &telemetry.AttributeConfiguration{Enabled: true},
},
METRIC_HISTOGRAM_REQUEST_DURATION: &telemetry.MetricConfiguration{
ATTR_FGA_CLIENT_REQUEST_CLIENT_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_REQUEST_METHOD: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_REQUEST_MODEL_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_REQUEST_STORE_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_RESPONSE_MODEL_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_HOST: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_REQUEST_RESEND_COUNT: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_RESPONSE_STATUS_CODE: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_URL_FULL: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_URL_SCHEME: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_USER_AGENT_ORIGINAL: &telemetry.AttributeConfiguration{Enabled: true},
},
METRIC_HISTOGRAM_QUERY_DURATION: &telemetry.MetricConfiguration{
ATTR_FGA_CLIENT_REQUEST_CLIENT_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_REQUEST_METHOD: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_REQUEST_MODEL_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_REQUEST_STORE_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_FGA_CLIENT_RESPONSE_MODEL_ID: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_HOST: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_REQUEST_RESEND_COUNT: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_HTTP_RESPONSE_STATUS_CODE: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_URL_FULL: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_URL_SCHEME: &telemetry.AttributeConfiguration{Enabled: true},
ATTR_USER_AGENT_ORIGINAL: &telemetry.AttributeConfiguration{Enabled: true},
},
},
}
fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{
Telemetry: &otel,
// ...
})
```
```
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Configuration;
using OpenFga.Sdk.Telemetry;
TelemetryConfig telemetryConfig = new TelemetryConfig() {
Metrics = new Dictionary {
[TelemetryMeter.RequestDuration] = new () {
Attributes = new HashSet {
TelemetryAttribute.HttpStatus,
TelemetryAttribute.HttpUserAgent,
TelemetryAttribute.RequestMethod,
TelemetryAttribute.RequestClientId,
TelemetryAttribute.RequestStoreId,
TelemetryAttribute.RequestModelId,
TelemetryAttribute.RequestRetryCount,
},
},
},
};
var configuration = new ClientConfiguration {
Telemetry = telemetryConfig,
// ...
};
var fgaClient = new OpenFgaClient(configuration);
```
```
from openfga_sdk import (
ClientConfiguration,
OpenFgaClient,
)
telemetry_config: dict[str, dict[str, dict[str, bool]]] = {
"metrics": {
"fga-client.request.duration": {
"fga-client.request.model_id": False,
"fga-client.response.model_id": False,
"fga-client.user": True,
"http.client.request.duration": True,
"http.server.request.duration": True,
},
},
}
configuration = ClientConfiguration(
telemetry=telemetry_config,
// ...
)
with OpenFgaClient(configuration) as fga_client:
# ...
```
```
import dev.openfga.sdk.api.client.ApiClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
import dev.openfga.sdk.api.configuration.TelemetryConfiguration;
Map> attributes = new HashMap<>();
attributes.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, Optional.empty());
attributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, Optional.empty());
attributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, Optional.empty());
attributes.put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, Optional.empty());
attributes.put(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID, Optional.empty());
attributes.put(Attributes.HTTP_HOST, Optional.empty());
attributes.put(Attributes.HTTP_REQUEST_METHOD, Optional.empty());
attributes.put(Attributes.HTTP_REQUEST_RESEND_COUNT, Optional.empty());
attributes.put(Attributes.HTTP_RESPONSE_STATUS_CODE, Optional.empty());
attributes.put(Attributes.URL_FULL, Optional.empty());
attributes.put(Attributes.URL_SCHEME, Optional.empty());
attributes.put(Attributes.USER_AGENT, Optional.empty());
Map>> metrics = new HashMap<>();
metrics.put(Counters.CREDENTIALS_REQUEST, attributes);
metrics.put(Histograms.QUERY_DURATION, attributes);
metrics.put(Histograms.REQUEST_DURATION, attributes);
ClientConfiguration config = new ClientConfiguration()
// ...
.telemetryConfiguration(new TelemetryConfiguration(metrics);
OpenFgaClient fgaClient = new OpenFgaClient(config);
```
## Examples
We provide example applications for using telemetry with the OpenFGA SDK Client.
- [Node.js](https://github.com/openfga/js-sdk/tree/main/example/opentelemetry)
- [Go](https://github.com/openfga/go-sdk/tree/main/example/opentelemetry)
- [.NET](https://github.com/openfga/dotnet-sdk/tree/main/example/OpenTelemetryExample)
- [Python](https://github.com/openfga/python-sdk/tree/main/example/opentelemetry)
## Supported Metrics
The OpenFGA SDK Client can collect the following metrics:
| Metric Name | Type | Enabled by Default | Description |
| -------------------------------- | --------- | ------------------ | --------------------------------------------------------------------------------- |
| `fga-client.request.duration` | Histogram | Yes | Total request time for FGA requests, in milliseconds |
| `fga-client.query.duration` | Histogram | Yes | Time taken by the FGA server to process and evaluate the request, in milliseconds |
| `fga-client.credentials.request` | Counter | Yes | Total number of new token requests initiated using the Client Credentials flow |
## Supported Attributes
The OpenFGA SDK Client can collect the following attributes:
| Attribute Name | Type | Enabled by Default | Description |
| ------------------------------ | ------ | ------------------ | --------------------------------------------------------------------------------- |
| `fga-client.request.client_id` | string | Yes | Client ID associated with the request, if any |
| `fga-client.request.method` | string | Yes | FGA method/action that was performed (e.g., Check, ListObjects) in TitleCase |
| `fga-client.request.model_id` | string | Yes | Authorization model ID that was sent as part of the request, if any |
| `fga-client.request.store_id` | string | Yes | Store ID that was sent as part of the request |
| `fga-client.response.model_id` | string | Yes | Authorization model ID that the FGA server used |
| `fga-client.user` | string | No | User associated with the action of the request for check and list users |
| `http.client.request.duration` | int | No | Duration for the SDK to complete the request, in milliseconds |
| `http.host` | string | Yes | Host identifier of the origin the request was sent to |
| `http.request.method` | string | Yes | HTTP method for the request |
| `http.request.resend_count` | int | Yes | Number of retries attempted, if any |
| `http.response.status_code` | int | Yes | Status code of the response (e.g., `200` for success) |
| `http.server.request.duration` | int | No | Time taken by the FGA server to process and evaluate the request, in milliseconds |
| `url.scheme` | string | Yes | HTTP scheme of the request (`http`/`https`) |
| `url.full` | string | Yes | Full URL of the request |
| `user_agent.original` | string | Yes | User Agent used in the query |
## Tracing
OpenFGA [supports](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md) tracing with OpenTelemetry.
If your application uses OpenTelemetry tracing, traces will be propagated to OpenFGA, provided the traces are exported to the same address. This can be useful to help diagnose any suspected performance issues when using OpenFGA.
If your application does not already use tracing, OpenTelemetry offers [zero-code instrumentation](https://opentelemetry.io/docs/zero-code/) for several languages. For example, a TypeScript application can be configured with tracing by using one of the [OpenTelemetry JavaScript Instrumentation Libraries](https://opentelemetry.io/docs/languages/js/libraries/):
```
npm install --save @opentelemetry/auto-instrumentations-node
```
Create an initialization file to configure tracing:
```
// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
// registers all instrumentation packages, you may wish to change this
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
```
Run the application with the appropriate OTEL environment variables:
```
OTEL_SERVICE_NAME='YOUR-SERVICE-NAME' ts-node -r ./tracing.ts YOUR-APP.ts
```
See the [OpenTelemetry documentation](https://opentelemetry.io/docs/) for additional information to configure your application for tracing.
---
# Create a Store
A [store](https://openfga.dev/docs/concepts.md#what-is-a-store) is a OpenFGA entity that contains your authorization data. You will need to create a store in OpenFGA before adding an [authorization model](https://openfga.dev/docs/concepts.md#what-is-an-authorization-model) and [relationship tuples](https://openfga.dev/docs/concepts.md#what-is-a-relationship-tuple) to it.
This article explains how to set up an OpenFGA store.
## Step by step
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk';
const openFga = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
});
const { id: storeId } = await openFga.createStore({
name: "FGA Demo Store",
});
```
```
import (
"context"
"os"
. "github.com/openfga/go-sdk/client"
)
func main() {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for \`CreateStore\` and \`ListStores\`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
resp, err := fgaClient.CreateStore(context.Background()).Body(ClientCreateStoreRequest{Name: "FGA Demo"}).Execute()
if err != nil {
// .. Handle error
}
}
```
```
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL") ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for \`CreateStore\` and \`ListStores\`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
var store = await fgaClient.CreateStore(new ClientCreateStoreRequest(){Name = "FGA Demo Store"});
}
}
```
```
import asyncio
import os
import openfga_sdk
from openfga_sdk.client import OpenFgaClient
from openfga_sdk.models.create_store_request import CreateStoreRequest
async def main():
configuration = openfga_sdk.ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
)
async with OpenFgaClient(configuration) as fga_client:
body = CreateStoreRequest(
name = "FGA Demo Store",
)
response = await fga_client.create_store(body)
asyncio.run(main())
```
```
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
import dev.openfga.sdk.api.model.CreateStoreRequest;
public class Example {
public static void main(String[] args) {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
var body = new CreateStoreRequest().name("FGA Demo Store");
var store = fgaClient.createStore(body).get();
}
}
```
```
fga store create --name "FGA Demo Store"
# To create the store and directly put the Store ID into an env variable:
# export FGA_STORE_ID=$(fga store create --name "FGA Demo Store" | jq -r .store.id)
```
```
curl -X POST $FGA_API_URL/stores \
-H "content-type: application/json" \
-d '{"name": "FGA Demo Store"}'
```
---
# Integrate Within a Framework
This section will illustrate how to integrate OpenFGA within a framework, such as [Fastify](https://www.fastify.io/) or [Fiber](https://docs.gofiber.io/).
## Before you start
- Node.js
- Go
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the OpenFGA SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You know how to [perform a Check](https://openfga.dev/docs/getting-started/perform-check.md).
5. You have loaded `FGA_API_URL` and `FGA_STORE_ID` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the OpenFGA SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You know how to [perform a Check](https://openfga.dev/docs/getting-started/perform-check.md).
5) You have loaded `FGA_API_URL` and `FGA_STORE_ID` as environment variables.
## Step by step
Assume that you want to have a web service for `document`s using one of the frameworks mentioned above. The service will authenticate users via [JWT tokens](https://auth0.com/docs/secure/tokens/json-web-tokens), which contain the user ID.
Note
The reader should set up their own `login` method based on their OpenID connect provider's documentation.
Assume that you want to provide a route `GET /read/{document}` to return documents depending on whether the authenticated user has access to it.
### 01. Install and setup framework
The first step is to install the framework.
- Node.js
- Go
For the context of this example, we will use the [Fastify framework](https://www.fastify.io/). For that we need to install the following packages:
- the [`fastify`](https://github.com/fastify/fastify) package that provides the framework itself
- the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) package that allows integrating plugins with Fastify
- the [`fastify-jwt`](https://github.com/fastify/fastify-jwt) package for processing JWT tokens
Using [npm](https://www.npmjs.com):
```
npm install fastify fastify-plugin fastify-jwt
```
Using [yarn](https://yarnpkg.com):
```
yarn add fastify fastify-plugin fastify-jwt
```
Next, we setup the web service with the `GET /read/{document}` route in file `app.js`.
```
// Require the framework and instantiate it
const fastify = require('fastify')({ logger: true });
// Declare the route
fastify.get('/read/:document', async (request, reply) => {
return { read: request.params.document };
});
// Run the server
const start = async () => {
try {
await fastify.listen(3000);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
```
For the context of this example, we will use the [Fiber framework](https://docs.gofiber.io/). For that we need to install the following Go packages:
- the [`gofiber/fiber`](https://docs.gofiber.io/) package that provides the Fiber framework itself
- the [`gofiber/jwt`](https://github.com/gofiber/jwt) middleware authentication layer for JWT
- the [`golang-jwt`](https://github.com/golang-jwt/jwt) package that provides Go support for JWT
```
go get -u github.com/gofiber/fiber/v2 github.com/gofiber/jwt/v3 github.com/golang-jwt/jwt/v4
```
Next, we setup the web service with the `GET /read/{document}` route.
```
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/read/:document", read)
app.Listen(":3000")
}
func read(c *fiber.Ctx) error {
return c.SendString(c.Params("document"))
}
```
### 02. Authenticate and get user ID
Before we can call OpenFGA to protect the `/read/{document}` route, we need to validate the user's JWT.
- Node.js
- Go
The `fastify-jwt` package allows validation of JWT tokens, as well as providing access to the user's identity.
In `jwt-authenticate.js`:
```
const fp = require('fastify-plugin');
module.exports = fp(async function (fastify, opts) {
fastify.register(require('fastify-jwt'), {
secret: {
private: readFileSync(`${path.join(__dirname, 'certs')}/private.key`, 'utf8'),
public: readFileSync(`${path.join(__dirname, 'certs')}/public.key`, 'utf8'),
},
sign: { algorithm: 'RS256' },
});
fastify.decorate('authenticate', async function (request, reply) {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
});
```
Then, use the `preValidation` hook of a route to protect it and access the user information inside the JWT:
In `route-read.js`:
```
module.exports = async function (fastify, opts) {
fastify.get(
'/read/:document',
{
preValidation: [fastify.authenticate],
},
async function (request, reply) {
// the user's id is in request.user
return { read: request.params.document };
},
);
};
```
Finally, update `app.js` to register the newly added hooks.
```
const fastify = require('fastify')({ logger: true });
const jwtAuthenticate = require('./jwt-authenticate');
const routeread = require('./route-read');
fastify.register(jwtAuthenticate);
fastify.register(routeread);
// Run the server!
const start = async () => {
try {
await fastify.listen(3000);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
}
start();
```
We will now setup middleware to authenticate the incoming JWTs.
```
package main
import (
"crypto/rand"
"crypto/rsa"
"log"
"github.com/gofiber/fiber/v2"
jwtware "github.com/gofiber/jwt/v3"
"github.com/golang-jwt/jwt/v4"
)
var (
// Do not do this in production.
// In production, you would have the private key and public key pair generated
// in advance. NEVER add a private key to any GitHub repo.
privateKey *rsa.PrivateKey
)
func main() {
app := fiber.New()
// Just as a demo, generate a new private/public key pair on each run.
rng := rand.Reader
var err error
privateKey, err = rsa.GenerateKey(rng, 2048)
if err != nil {
log.Fatalf("rsa.GenerateKey: %v", err)
}
// JWT Middleware
app.Use(jwtware.New(jwtware.Config{
SigningMethod: "RS256",
SigningKey: privateKey.Public(),
}))
app.Get("/read/:document", read)
app.Listen(":3000")
}
func read(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
name := claims["name"].(string)
return c.SendString(name + " read " + c.Params("document"))
}
```
### 03. Integrate the OpenFGA check API into the service
- Node.js
- Go
First, we will create a decorator `preauthorize` to parse the incoming HTTP method as well as name of the document, and set the appropriate `relation` and `object` that we will call Check on.
In `preauthorize.js`:
```
const fp = require('fastify-plugin');
module.exports = fp(async function (fastify, opts) {
fastify.decorate('preauthorize', async function (request, reply) {
try {
switch (request.method) {
case 'GET':
request.relation = 'reader';
break;
case 'POST':
request.relation = 'writer';
break;
case 'DELETE':
default:
request.relation = 'owner';
break;
}
request.object = `document:${request.params.document}`;
} catch (err) {
reply.send(err);
}
});
});
```
Next, we will create a decorator called `authorize`. This decorator will invoke the [Check API](https://openfga.dev/docs/getting-started/perform-check.md) to see if the user has a relationship with the specified document.
In `authorize.js`:
```
const fp = require('fastify-plugin');
const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk';
module.exports = fp(async function (fastify, opts) {
fastify.decorate('authorize', async function (request, reply) {
try {
// configure the openfga api client
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
});
const { allowed } = await fgaClient.check({
user: request.user,
relation: request.relation,
object: request.object,
});
if (!allowed) {
reply.code(403).send(`forbidden`);
}
} catch (err) {
reply.send(err);
}
});
});
```
We can now update the `GET /read/{document}` route to check for user permissions.
In `route-read.js`:
```
module.exports = async function (fastify, opts) {
fastify.get(
'/read/:document',
{
preValidation: [fastify.authenticate, fastify.preauthorize, fastify.authorize],
},
async function (request, reply) {
// the user's id is in request.user
return { read: request.params.document };
},
);
};
```
Finally, we will register the new hooks in `app.js`:
```
const fastify = require('fastify')({ logger: true });
const jwtAuthenticate = require('./jwt-authenticate');
const preauthorize = require('./preauthorize');
const authorize = require('./authorize');
const routeread = require('./route-read');
fastify.register(jwtAuthenticate);
fastify.register(preauthorize);
fastify.register(authorize);
fastify.register(routeread);
const start = async () => {
try {
await fastify.listen(3000);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
}
start();
```
We will create two middlewares:
- `preauthorize` will parse the user's JWT and prepare variables needed to call Check API.
- `checkAuthorization` will call the [`Check API`](https://openfga.dev/docs/getting-started/perform-check.md) to see if the user has a relationship with the specified document.
```
package main
import (
"context"
"crypto/rand"
"crypto/rsa"
"log"
"os"
"github.com/gofiber/fiber/v2"
jwtware "github.com/gofiber/jwt/v3"
"github.com/golang-jwt/jwt/v4"
. "github.com/openfga/go-sdk/client"
)
var (
// Do not do this in production.
// In production, you would have the private key and public key pair generated
// in advance. NEVER add a private key to any GitHub repo.
privateKey *rsa.PrivateKey
)
func main() {
app := fiber.New()
// Just as a demo, generate a new private/public key pair on each run.
rng := rand.Reader
var err error
privateKey, err = rsa.GenerateKey(rng, 2048)
if err != nil {
log.Fatalf("rsa.GenerateKey: %v", err)
}
// JWT Middleware
app.Use(jwtware.New(jwtware.Config{
SigningMethod: "RS256",
SigningKey: privateKey.Public(),
}))
app.Use("/read/:document", preauthorize)
app.Use(checkAuthorization)
app.Get("/read/:document", read)
app.Listen(":3000")
}
func read(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
name := claims["name"].(string)
return c.SendString(name + " read " + c.Params("document"))
}
func preauthorize(c *fiber.Ctx) error {
// get the user name from JWT
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
name := claims["name"].(string)
c.Locals("username", name)
// parse the HTTP method
switch (c.Method()) {
case "GET":
c.Locals("relation", "reader")
case "POST":
c.Locals("relation", "writer")
case "DELETE":
c.Locals("relation", "owner")
default:
c.Locals("relation", "owner")
}
// get the object name and prepend with type name "document:"
c.Locals("object", "document:" + c.Params("document"))
return c.Next()
}
// Middleware to check whether user is authorized to access document
func checkAuthorization(c *fiber.Ctx) error {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for \`CreateStore\` and \`ListStores\`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // optional, can be overridden per request
})
if err != nil {
return fiber.NewError(fiber.StatusServiceUnavailable, "Unable to build OpenFGA client")
}
body := ClientCheckRequest{
User: c.Locals("username").(string),
Relation: c.Locals("relation").(string),
Object: c.Locals("object").(string),
}
data, err := fgaClient.Check(context.Background()).Body(body).Execute()
if err != nil {
return fiber.NewError(fiber.StatusServiceUnavailable, "Unable to check for authorization")
}
if !(*data.Allowed) {
return fiber.NewError(fiber.StatusForbidden, "Forbidden to access document")
}
// Go to the next middleware
return c.Next()
}
```
## Related Sections
Take a look at the following sections for examples that you can try when integrating with SDK.
**Entitlements**
Modeling Entitlements for a System in OpenFGA.
- [More](https://openfga.dev/docs/modeling/advanced/entitlements.md)
**IoT**
Modeling Fine-Grained Authorization for an IoT Security Camera System with OpenFGA.
- [More](https://openfga.dev/docs/modeling/advanced/iot.md)
**Slack**
Modeling Authorization for Slack with OpenFGA.
- [More](https://openfga.dev/docs/modeling/advanced/slack.md)
---
# Immutable Authorization Models
Authorization Models in OpenFGA are immutable, they are created once and then can no longer be deleted or modified. Each time you write an authorization model, a new version is created.
## Viewing all the authorization models
You can list all the authorization models for a store using the [ReadAuthorizationModels](https://openfga.dev/api/service#/Authorization%20Models/ReadAuthorizationModels) API. This endpoint returns the results sorted in reverse chronological order, as in the first model in the list is the latest model. By default, only the last 50 models are returned, but you can paginate across by passing in the appropriate `continuation_token`.
## How to target a particular model
Some endpoints relating to tuples ([Check](https://openfga.dev/api/service#/Relationship%20Queries/Check), [ListObjects](https://openfga.dev/api/service#/Relationship%20Queries/ListObjects), [ListUsers](https://openfga.dev/api/service#/Relationship%20Queries/ListUsers), [Expand](https://openfga.dev/api/service#/Relationship%20Queries/Expand), [Write](https://openfga.dev/api/service#/Relationship%20Tuples/Write)) accept an `authorization_model_id`, which we strongly recommend passing, especially in production.
In practice, you would pin the authorization model ID alongside the store ID in your configuration management system. Your services would read this value and use it in their requests to FGA. This helps you ensure that your services are using the same consistent ID across all your applications, and that rollouts can be seamless.
## Benefits of passing in an authorization model ID
Targeting a specific model ID would ensure that you don't accidentally break your authorization checks in production because a mistake was made when updating the authorization model. It would also slightly improve the latency on your check requests.
If that field is passed, evaluation and validation will happen for that particular authorization model ID. If this field is not passed, OpenFGA will use the last created Authorization Model for that store.
## Potential use-cases
### Complex model migrations
Certain model changes require adapting your application code and migrating tuples before rolling it out. For example, if you rename a relation, you need to change the application and copy the existing tuples to use the new relation name. This scenario requires the following steps:
- Update the authorization model with the renamed relation. A new model ID will be generated but it won't be used in production yet.
- Update the application to use the new relation name.
- Copy existing tuples to use the new relation name.
- Deploy the new application targeting the new model ID.
You can learn more about model migrations [here](https://openfga.dev/docs/modeling/migrating.md).
### Progresivelly rollout changes
Being able to target multiple versions of the authorization model enables you to progressively roll out model changes, which is something you should consider doing if the changes are significant. You could:
- Do shadow checks where you would perform checks against both your existing model and the new upcoming model you are hoping to replace it with.This will help you detect and resolve any accidental discrepancies you may be introducing, and ensure that your new model is at least as good as your old one.
- When you are confident with your model, you could implement gradual rollouts that would allow you to monitor and check if any users are having access issues before you go ahead and increase the rollout to 100% of your user base.
Getting an Authorization Model's Creation Date
The Authorization Model ID is a [ULID](https://github.com/ulid/spec) which includes the date created. You can extract the creation date using a library for your particular language.
For example, in JavaScript you can do the following:
```
import ulid = require('ulid');
const time = ulid.decodeTime(id);
```
## Related Sections
Learn more about modeling and production usage in OpenFGA.
**Configuration Language**
Learn about the OpenFGA Configuration Language.
- [More](https://openfga.dev/docs/configuration-language.md)
**Getting Started with Modeling**
Read how to get started with modeling.
- [More](https://openfga.dev/docs/modeling/getting-started.md)
**Data and API Best Practices**
Learn the best practices for managing data and invoking APIs in production environment
- [More](https://openfga.dev/docs/getting-started/tuples-api-best-practices.md)
---
# Install SDK Client
To get started, install the OpenFGA SDK packages.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
You can find the Node.js package on npm at: [@openfga/sdk](https://www.npmjs.com/package/@openfga/sdk).
Using [npm](https://www.npmjs.com/):
```
npm install @openfga/sdk
```
Using [yarn](https://yarnpkg.com):
```
yarn add @openfga/sdk
```
You can find the Go package on GitHub at: [@openfga/go-sdk](https://github.com/openfga/go-sdk).
To install:
```
go get -u github.com/openfga/go-sdk
```
In your code, import the module and use it:
```
import (
openfga "github.com/openfga/go-sdk"
)
func main() {
configuration, err := openfga.NewConfiguration(openfga.Configuration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
})
if err != nil {
// .. Handle error
}
}
```
You can then run
```
go mod tidy
```
to update `go.mod` and `go.sum` if you are using them.
The OpenFGA .NET SDK is available on [NuGet](https://www.nuget.org/packages/OpenFga.Sdk).
You can install it using:
- The [dotnet CLI](https://docs.microsoft.com/en-us/nuget/consume-packages/install-use-packages-dotnet-cli):
```
dotnet add package OpenFGA.Sdk
```
- The [Package Manager Console](https://docs.microsoft.com/en-us/nuget/consume-packages/install-use-packages-powershell) inside Visual Studio:
```
Install-Package OpenFGA.Sdk
```
- [Visual Studio](https://docs.microsoft.com/en-us/nuget/consume-packages/install-use-packages-visual-studio), [Visual Studio for Mac](https://docs.microsoft.com/en-us/visualstudio/mac/nuget-walkthrough) and [IntelliJ Rider](https://www.jetbrains.com/help/rider/Using_NuGet.html): Search for and install `OpenFGA.Sdk` in each of their respective package manager UIs.
The OpenFGA Python SDK is available on [PyPI](https://pypi.org/project/openfga-sdk).
To install:
```
pip3 install openfga_sdk
```
In your code, import the module and use it:
```
import openfga_sdk
```
You can find the Java package on [Maven Central](https://central.sonatype.com/artifact/dev.openfga/openfga-sdk).
Using [Maven](https://maven.apache.org/):
```
dev.openfga
openfga-sdk
0.3.1
```
Using [Gradle](https://gradle.org/):
```
implementation 'dev.openfga:openfga-sdk:0.3.1'
```
The OpenFGA CLI is available on [GitHub](https://github.com/openfga/cli).
To install:
### Brew
```
brew install openfga/tap/fga
```
### Linux (deb, rpm and apk) packages
Download the .deb, .rpm or .apk packages from the [releases page](https://github.com/openfga/cli/releases).
Debian:
```
sudo apt install ./fga__linux_.deb
```
Fedora:
```
sudo dnf install ./fga__linux_.rpm
```
Alpine Linux:
```
sudo apk add --allow-untrusted ./fga__linux_.apk
```
### Docker
```
docker pull openfga/cli; docker run -it openfga/cli
```
### Go
```
go install github.com/openfga/cli/cmd/fga@latest
```
### Manually
Download the pre-compiled binaries from the [releases page](https://github.com/openfga/cli/releases).
## Related Sections
Get OpenFGA's SDKs to add authorization to your API.
**OpenFGA Node.js SDK**
Install our Node.js & JavaScript SDK to get started.
- [More](https://www.npmjs.com/package/@openfga/sdk)
**OpenFGA Go SDK**
Use our Go SDK to easily connect your Go application to the OpenFGA API
- [More](https://github.com/openfga/go-sdk)
**OpenFGA .NET SDK**
Connect your .NET service with OpenFGA using our .NET SDK
- [More](https://github.com/openfga/dotnet-sdk)
**OpenFGA Python SDK**
Connect your Python service with OpenFGA using our Python SDK
- [More](https://github.com/openfga/python-sdk)
**OpenFGA Java SDK**
Connect your Java service with OpenFGA using our Java SDK
- [More](https://github.com/openfga/java-sdk)
---
# Perform a Check
This section will illustrate how to perform a [check](https://openfga.dev/docs/concepts.md#what-is-a-check-request) request to determine whether a [user](https://openfga.dev/docs/concepts.md#what-is-a-user) has a certain [relationship](https://openfga.dev/docs/concepts.md#what-is-a-relationship) with an [object](https://openfga.dev/docs/concepts.md#what-is-an-object).
## Before you start
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md).
3) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
## Step by step
Assume that you want to check whether user `anne` has relationship `reader` with object `document:Z`
### 01. Configure the OpenFGA API client
Before calling the check API, you will need to configure the API client.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
To obtain the [access token](https://auth0.com/docs/get-started/authentication-and-authorization-flow/call-your-api-using-the-client-credentials-flow):
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
### 02. Calling Check API
To check whether user `user:anne` has relationship `reader` with object `document:Z`
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'reader',
object: 'document:Z',
}, {
authorizationModelId: '01HVMMBCMGZNT3SED4Z17ECXCA',
});
// allowed = true
```
```
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "reader",
Object: "document:Z",
}
data, err := fgaClient.Check(context.Background()).
Body(body).
Options(options).
Execute()
// data = { allowed: true }
```
```
var options = new ClientCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA"
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "reader",
Object = "document:Z",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
}
body = ClientCheckRequest(
user="user:anne",
relation="reader",
object="document:Z",
)
response = await fga_client.check(body, options)
# response.allowed = true
```
```
var options = new ClientCheckOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientCheckRequest()
.user("user:anne")
.relation("reader")
._object("document:Z");
var response = fgaClient.check(body, options).get();
// response.getAllowed() = true
```
```
fga query check --store-id=$FGA_STORE_ID --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:anne reader document:Z
# Response: {"allowed":true}
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"tuple_key": {
"user": "user:anne",
"relation": "reader",
"object": "document:Z"
}
}'
# Response: {"allowed": true}
```
The result's `allowed` field will be:
- `true` if the relationship exists.
- `false` if the relation is defined in your model but no matching tuple exists.
If the relation is _not defined_ in your model, the API will return a `400 Bad Request` instead of `false`.
### 03. Calling Batch Check API
If you want to check multiple user-object-relationship combinations in a single request, you can use the [Batch Check](https://openfga.dev/api/service#Relationship%20Queries/BatchCheck) API endpoint. Batching authorization checks together in a single request significantly reduces overall network latency.
The BatchCheck endpoint requires a `correlation_id` parameter for each check. The `correlation_id` is used to "correlate" the check responses with the checks sent in the request, since `tuple_keys` and `contextual_tuples` are not returned in the response on purpose to reduce data transfer to improve network latency. A `correlation_id` can be composed of any string of alphanumeric characters or dashes between 1-36 characters in length. This means you can use:
- simple iterating integers `1,2,3,etc`
- UUID `e5fe049b-f252-40b3-b795-fe485d588279`
- ULID `01JBMD9YG0XH3B4GVA8A9D2PSN`
- or some other unique string
Each `correlation_id` within a request must be unique.
note
If you are using one of our SDKs:
- the `correlation_id` is inserted for you by default and automatically correlates the `allowed` response with the proper `tuple_key`
- if you pass in more checks than the server supports in a single call (default `50`, configurable on the server), the SDK will automatically split and batch the `BatchCheck` requests for you, how it does this can be configured using the `maxBatchSize` and `maxParallelRequests` options in the SDK.
To check whether user `user:anne` has multiple relationships `writer` and `reader` with object `document:Z`
- Node.js
- Go
- .NET
- Python
- Java
- curl
- Pseudocode
```
const body = {
checks: [
{
user: 'user:anne',
relation: 'writer',
object: 'document:Z',
correlationId: '886224f6-04ae-4b13-bd8e-559c7d3754e1'
},{
user: 'user:anne',
relation: 'reader',
object: 'document:Z',
correlationId: 'da452239-a4e0-4791-b5d1-fb3d451ac078'
}
],
}
const options = {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
maxBatchSize: 50, // optional, default is 50, can be used to limit the number of checks in a single server request
maxParallelRequests: 10, // optional, default is 10, can be used to limit the parallelization of the BatchCheck chunks
};
const { result } = await fgaClient.batchCheck(body, options);
/*
{
"results": [
{
"correlationId": '886224f6-04ae-4b13-bd8e-559c7d3754e1',
"allowed": false,
"request": {
"user": 'user:anne',
"relation": 'writer',
"object": 'document:Z'}
}, {
"correlationId": 'da452239-a4e0-4791-b5d1-fb3d451ac078',
"allowed": true,
"request": {
"user": 'user:anne',
"relation": 'reader',
"object": 'document:Z'}
}
],
}
*/
```
```
body := ClientBatchCheckRequest{
Checks: []ClientBatchCheckItem{
{
User: "user:anne",
Relation: "writer",
Object: "document:Z",
CorrelationId: "886224f6-04ae-4b13-bd8e-559c7d3754e1",
},
{
User: "user:anne",
Relation: "reader",
Object: "document:Z",
CorrelationId: "da452239-a4e0-4791-b5d1-fb3d451ac078",
},
},
}
options := BatchCheckOptions{
MaxBatchSize: openfga.PtrInt32(50), // optional, default is 50, can be used to limit the number of checks in a single server request
MaxParallelRequests: openfga.PtrInt32(10), // optional, default is 10, can be used to limit the parallelization of the BatchCheck chunks,
AuthorizationModelId: openfga.PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
data, err := fgaClient.BatchCheck(context.Background()).Body(body).Options(options).Execute()
/*
// Results are a map keyed by correlationId
// Example:
data.GetResult() = map[string]BatchCheckSingleResult{
"886224f6-04ae-4b13-bd8e-559c7d3754e1": {
Allowed: false,
Error: ,
},
"da452239-a4e0-4791-b5d1-fb3d451ac078": {
Allowed: true,
},
}
*/
```
```
var body = new ClientBatchCheckRequest {
Checks = new List {
new() {
User = "user:anne",
Relation = "writer",
Object = "document:Z",
CorrelationId = "886224f6-04ae-4b13-bd8e-559c7d3754e1",
new() {
User = "user:anne",
Relation = "reader",
Object = "document:Z",
CorrelationId = "da452239-a4e0-4791-b5d1-fb3d451ac078"
}
};
var options = new ClientBatchCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
MaxBatchSize = 50, // optional, default is 50
MaxParallelRequests = 10 // optional, default is 10
};
var response = await fgaClient.BatchCheck(body, options);
/*
response.Result = [{
CorrelationId = "886224f6-04ae-4b13-bd8e-559c7d3754e1",
Allowed = false,
Request = {
User = "user:anne",
Relation = "writer",
Object = "document:Z"
}
},
{
CorrelationId = "da452239-a4e0-4791-b5d1-fb3d451ac078",
Allowed = true,
Request = {
User = "user:anne",
Relation = "reader",
Object = "document:Z"
}
}]
*/
```
```
checks = [
ClientBatchCheckItem(
user="user:anne",
relation="writer",
object="document:Z",
correlation_id="886224f6-04ae-4b13-bd8e-559c7d3754e1"
),
ClientBatchCheckItem(
user="user:anne",
relation="reader",
object="document:Z",
correlation_id="da452239-a4e0-4791-b5d1-fb3d451ac078"
)
]
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"}
response = await fga_client.batch_check(ClientBatchCheckRequest(checks=checks), options)
# response.results = [{
# correlation_id: '886224f6-04ae-4b13-bd8e-559c7d3754e1',
# allowed: false,
# request: {
# user: 'user:anne',
# relation: 'writer',
# object: 'document:Z'}
#}, {
# correlation_id: 'da452239-a4e0-4791-b5d1-fb3d451ac078',
# allowed: true,
# request: {
# user: 'user:anne',
# relation: 'reader',
# object: 'document:Z'}
#}]
```
```
var request = new ClientBatchCheckRequest().checks(
List.of(
new ClientBatchCheckItem()
.user("user:anne")
.relation("writer")
._object("document:Z")
.correlationId("886224f6-04ae-4b13-bd8e-559c7d3754e1"),
new ClientBatchCheckItem()
.user("user:anne")
.relation("reader")
._object("document:Z")
.correlationId("da452239-a4e0-4791-b5d1-fb3d451ac078")
);
var options = new ClientBatchCheckOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA")
.maxBatchSize(50) // optional, default is 50, can be used to limit the number of checks in a single server request
.maxParallelRequests(10); // optional, default is 10, can be used to limit the parallelization of the BatchCheck chunks
var response = fgaClient.batchCheck(request, options).get();
/*
{
"result": [
{
"correlationId": '886224f6-04ae-4b13-bd8e-559c7d3754e1',
"allowed": false,
"request": {
"user": 'user:anne',
"relation": 'writer',
"_object": 'document:Z'}
}, {
"correlationId": 'da452239-a4e0-4791-b5d1-fb3d451ac078',
"allowed": true,
"request": {
"user": 'user:anne',
"relation": 'reader',
"_object": 'document:Z'}
}
],
}
*/
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/batch-check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"checks": [
{
"tuple_key": {
"user":"user:anne",
"relation":"writer",
"object":"document:Z"
},
"correlation_id": "886224f6-04ae-4b13-bd8e-559c7d3754e1"
},
{
"tuple_key": {
"user":"user:anne",
"relation":"reader",
"object":"document:Z"
},
"correlation_id": "da452239-a4e0-4791-b5d1-fb3d451ac078"
}
]
}'
# Response:
{
"results": {
{ "886224f6-04ae-4b13-bd8e-559c7d3754e1": { "allowed": false }}, # writer
{ "da452239-a4e0-4791-b5d1-fb3d451ac078": { "allowed": true }}, # reader
}
}
```
```
BatchCheck([
- user="user:anne", relation="writer", object="document:Z", correlation_id="886224f6-04ae-4b13-bd8e-559c7d3754e1"
- user="user:anne", relation="reader", object="document:Z", correlation_id="da452239-a4e0-4791-b5d1-fb3d451ac078"
])
Reply:
- correlation_id="886224f6-04ae-4b13-bd8e-559c7d3754e1": false
- correlation_id="da452239-a4e0-4791-b5d1-fb3d451ac078": true
```
The result will include an `allowed` field for each authorization check that will return `true` if the relationship exists and `false` if the relationship does not exist.
#### Configuring Batch Check
BatchCheck has two available configuration options:
1. Limit the number of checks allowed in a single BatchCheck request.
- Environment variable: `OPENFGA_MAX_CHECKS_PER_BATCH_CHECK`
- Command line flag: `--max-checks-per-batch-check`
- If more items are received in a single request than allowed by this limit, the API will return an error.
2. Limit the number of Checks which can be resolved concurrently
- Environment variable: `OPENFGA_MAX_CONCURRENT_CHECKS_PER_BATCH_CHECK`
- Command line flag: `--max-concurrent-checks-per-batch-check`
## Related Sections
Take a look at the following section for more on how to perform authorization checks in your system
**OpenFGA Check API**
Read the Check API documentation and see how it works.
- [More](https://openfga.dev/api/service#Relationship%20Queries/Check)
**OpenFGA Batch Check API**
Read the Batch Check API documentation and see how it works.
- [More](https://openfga.dev/api/service#Relationship%20Queries/BatchCheck)
---
# Perform a list objects call
This section describes how to perform a [list objects](https://openfga.dev/docs/concepts.md#what-is-a-list-objects-request) request. The List Objects API allows you to retrieve all [objects](https://openfga.dev/docs/concepts.md#what-is-an-object) of a specified [type](https://openfga.dev/docs/concepts.md#what-is-a-type) that a [user](https://openfga.dev/docs/concepts.md#what-is-a-user) has a given [relationship](https://openfga.dev/docs/concepts.md#what-is-a-relationship) with. This can be used in scenarios like displaying all documents a user can read or listing resources a user can manage.
## Before you start
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md).
3) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
## Step by step
Consider the following model which includes a `user` that can have a `reader` relationship with a `document`:
```
model
schema 1.1
type user
type document
relations
define reader: [user]
```
Assume that you want to list all objects of type document that user `anne` has `reader` relationship with:
### 01. Configure the OpenFGA API client
Before calling the check API, you will need to configure the API client.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
To obtain the [access token](https://auth0.com/docs/get-started/authentication-and-authorization-flow/call-your-api-using-the-client-credentials-flow):
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
### 02. Calling list objects API
To return all documents that user `user:anne` has relationship `reader` with:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
const response = await fgaClient.listObjects({
user: "user:anne",
relation: "reader",
type: "document",
}, {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA",
});
// response.objects = ["document:otherdoc", "document:planning"]
```
```
options := ClientListObjectsOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientListObjectsRequest{
User: "user:anne",
Relation: "reader",
Type: "document",
}
data, err := fgaClient.ListObjects(context.Background()).
Body(body).
Options(options).
Execute()
// data = { "objects": ["document:otherdoc", "document:planning"] }
```
```
var options = new ClientCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientListObjectsRequest {
User = "user:anne",
Relation = "reader",
Type = "document",
};
var response = await fgaClient.ListObjects(body, options);
// response.Objects = ["document:otherdoc", "document:planning"]
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
body = ClientListObjectsRequest(
user="user:anne",
relation="reader",
type="document",
)
response = await fga_client.list_objects(body, options)
# response.objects = ["document:otherdoc", "document:planning"]
```
```
var options = new ClientListObjectsOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientListObjectsRequest()
.user("user:anne")
.relation("reader")
.type("document");
var response = fgaClient.listObjects(body, options).get();
// response.getObjects() = ["document:otherdoc", "document:planning"]
```
```
fga query list-objects --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:anne reader document
# Response: {"objects": ["document:otherdoc", "document:planning"]}
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-objects \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"type": "document",
"relation": "reader",
"user":"user:anne"
}'
# Response: {"objects": ["document:otherdoc", "document:planning"]}
```
The result `document:otherdoc` and `document:planning` are the document objects that `user:anne` has `reader` relationship with.
Warning
The performance characteristics of the ListObjects endpoint vary drastically depending on the model complexity, number of tuples, and the relations it needs to evaluate. Relations with 'and' or 'but not' are more expensive to evaluate than relations with 'or'.
## Streamed List Objects
The Streamed ListObjects API is similar to the ListObjects API, with two key differences:
1. **Streaming Response**: Instead of collecting all objects before returning a response, it streams them to the client as they are collected.
2. **No Result Limit**: The number of results returned is only limited by the execution timeout specified in the flag `OPENFGA_LIST_OBJECTS_DEADLINE`, not by a fixed limit.
info
The streaming functionality is currently available in the **Node.js SDK**, **Go SDK**, **.NET SDK**, **Python SDK**, and **Java SDK**.
### Using Streamed List Objects
- Node.js
- Go
- .NET
- Python
- Java
```
const objects = [];
for await (const response of fgaClient.streamedListObjects(
{ user: "user:anne", relation: "reader", type: "document" }
)) {
objects.push(response.object);
}
// objects = ["document:otherdoc", "document:planning"]
```
```
objects := []string{}
err := fgaClient.StreamedListObjects(context.Background()).
Body(client.ClientListObjectsRequest{
User: "user:anne",
Relation: "reader",
Type: "document",
}).
Execute(func(response *client.ClientStreamedListObjectsResponse) error {
objects = append(objects, response.Object)
return nil
})
// objects = ["document:otherdoc", "document:planning"]
```
```
var objects = new List();
await foreach (var response in fgaClient.StreamedListObjects(
new ClientListObjectsRequest {
User = "user:anne",
Relation = "reader",
Type = "document"
})) {
objects.Add(response.Object);
}
// objects = ["document:otherdoc", "document:planning"]
```
```
objects = []
async for response in fga_client.streamed_list_objects(
ClientListObjectsRequest(
user="user:anne",
relation="reader",
type="document"
)
):
objects.append(response.object)
# objects = ["document:otherdoc", "document:planning"]
```
```
var objects = new ArrayList();
var request = new ClientListObjectsRequest()
.user("user:anne")
.relation("reader")
.type("document");
fgaClient.streamedListObjects(request, new ClientStreamedListObjectsOptions(), response -> {
objects.add(response.getObject());
}).get();
// objects = ["document:otherdoc", "document:planning"]
```
## Related Sections
Take a look at the following section for more on how to perform authorization checks in your system
**OpenFGA List Objects API**
Read the List Objects API documentation and see how it works.
- [More](https://openfga.dev/api/service#Relationship%20Queries/ListObjects)
**OpenFGA Streamed List Objects API**
Read the Streamed List Objects API documentation.
- [More](https://openfga.dev/api/service#Relationship%20Queries/StreamedListObjects)
---
# Perform a List Users call
This section will illustrate how to perform a [list users](https://openfga.dev/docs/concepts.md#what-is-a-list-users-request) request. The List Users call allows you to retrieve a list of [users](https://openfga.dev/docs/concepts.md#what-is-a-user) that have a specific [relationship](https://openfga.dev/docs/concepts.md#what-is-a-relationship) with a given [object](https://openfga.dev/docs/concepts.md#what-is-an-object). This can be used in scenarios such as retrieving users who have access to a resource or managing members in a group.
## Before You Start
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [installed the SDK](https://openfga.dev/docs/getting-started/install-sdk.md).
3. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1) Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2) You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md).
3) You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA\_STORE\_ID, FGA\_API\_URL and, if needed, FGA\_API\_TOKEN.
2. You have [configured the _authorization model_](https://openfga.dev/docs/getting-started/configure-model.md) and [updated the _relationship tuples_](https://openfga.dev/docs/getting-started/update-tuples.md).
3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables.
## Step by step
Consider the following model which includes a `user` that can have a `reader` relationship with a `document`:
```
model
schema 1.1
type user
type document
relations
define reader: [user]
```
Assume that you want to list all users of type `user` that have a `reader` relationship with `document:planning`:
### 01. Configure the OpenFGA API client
Before calling the List Users API, you will need to configure the API client.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
```
```
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
```
```
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
```
```
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
```
```
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
```
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
To obtain the [access token](https://auth0.com/docs/get-started/authentication-and-authorization-flow/call-your-api-using-the-client-credentials-flow):
```
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
```
### 02. Calling List Users API
To return all users of type `user` that have have the `reader` relationship with `document:planning`:
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
const response = await fgaClient.listUsers({
object: {
type: "document",
id: "planning"
},
user_filters: [{
type: "user"
}],
relation: "reader",
}, {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA",
});
// response.users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
```
options := ClientListUsersOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
userFilters := []openfga.UserTypeFilter{{ Type:"user" }}
body := ClientListUsersRequest{
Object: openfga.Object{
Type: "document",
Id: "planning",
},
Relation: "reader",
UserFilters: userFilters,
}
data, err := fgaClient.ListUsers(context.Background()).
Body(body).
Options(options).
Execute()
// data.Users = [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]
```
```
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientListUsersRequest {
Object = new FgaObject {
Type = "document",
Id = "planning"
},
Relation = "reader",
UserFilters = new List {
new() {
Type = "user"
}
}
};
var response = await fgaClient.ListUsers(body, options);
// response.Users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
```
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
userFilters = [
UserTypeFilter(type="user")
]
body = ClientListUsersRequest(
object=FgaObject(type="document",id="planning"),
relation="reader",
user_filters=userFilters,
)
response = await fga_client.list_users(body, options)
# response.users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
```
var options = new ClientListUsersOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var userFilters = new ArrayList() {
{
add(new UserTypeFilter().type("user"));
}
};
var body = new ClientListUsersRequest()
._object(new FgaObject().type("document").id("planning"))
.relation("reader")
.userFilters(userFilters);
var response = fgaClient.listUsers(body, options).get();
// response.getUsers() = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
```
```
fga query list-users --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA --object document:planning --relation reader --user-filter user
# Response: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]}
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-users \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"object": {
"type": "document",
"id": "planning",
},
"relation": "reader",
"user_filters": [
{
"type": "user"
}
]
}'
# Response: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]}
```
The result `user:anne` and `user:beth` are the `user` objects that have the `reader` relationship with `document:planning`.
Warning
The performance characteristics of the List Users endpoint vary drastically depending on the model complexity, number of tuples, and the relations it needs to evaluate. Relations with 'and' or 'but not' are particularly expensive to evaluate.
## Usersets
In the above example, only specific subjects of the `user` type were returned. However, groups of users, known as [usersets](https://openfga.dev/docs/modeling/building-blocks/usersets.md), can also be returned from the List Users API. This is done by specifying a `relation` field in the `user_filters` request object. Usersets will only expand to the underlying subjects if that `type` is specified as the user filter object.
Below is an example where usersets can be returned:
```
model
schema 1.1
type user
type group
relations
define member: [ user ]
type document
relations
define viewer: [ group#member ]
```
With the tuples:
| user | relation | object |
| ------------------------ | -------- | ----------------- |
| group:engineering#member | viewer | document:1 |
| group:product#member | viewer | document:1 |
| user:will | member | group:engineering |
Then calling the List Users API for `document:1` with relation `viewer` of type `group#member` will yield the below response. Note that the `user:will` is not returned, despite being a member of `group:engineering#member` because the `user_filters` does not target the `user` type.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
```
const response = await fgaClient.listUsers({
object: {
type: "document",
id: "1"
},
user_filters: [{
type: "group",
relation: "member"
}],
relation: "viewer",
}, {
authorizationModelId: "01HXHK5D1Z6SCG1SV7M3BVZVCV",
});
// response.users = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
```
```
options := ClientListUsersOptions{
AuthorizationModelId: PtrString("01HXHK5D1Z6SCG1SV7M3BVZVCV"),
}
userFilters := []openfga.UserTypeFilter{{ Type:"group",Relation:"member" }}
body := ClientListUsersRequest{
Object: openfga.Object{
Type: "document",
Id: "1",
},
Relation: "viewer",
UserFilters: userFilters,
}
data, err := fgaClient.ListUsers(context.Background()).
Body(body).
Options(options).
Execute()
// data.Users = [{"userset":{"id":"engineering","relation":"member","type":"group"}}, {"userset":{"id":"product","relation":"member","type":"group"}}]
```
```
var options = new ClientWriteOptions {
AuthorizationModelId = "01HXHK5D1Z6SCG1SV7M3BVZVCV",
};
var body = new ClientListUsersRequest {
Object = new FgaObject {
Type = "document",
Id = "1"
},
Relation = "viewer",
UserFilters = new List {
new() {
Type = "group"
Relation = "member"
}
}
};
var response = await fgaClient.ListUsers(body, options);
// response.Users = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
```
```
options = {
"authorization_model_id": "01HXHK5D1Z6SCG1SV7M3BVZVCV"
}
userFilters = [
UserTypeFilter(type="group",relation="member")
]
body = ClientListUsersRequest(
object=FgaObject(type="document",id="1"),
relation="viewer",
user_filters=userFilters,
)
response = await fga_client.list_users(body, options)
# response.users = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
```
```
var options = new ClientListUsersOptions()
.authorizationModelId("01HXHK5D1Z6SCG1SV7M3BVZVCV");
var userFilters = new ArrayList() {
{
add(new UserTypeFilter().type("group").relation("member"));
}
};
var body = new ClientListUsersRequest()
._object(new FgaObject().type("document").id("1"))
.relation("viewer")
.userFilters(userFilters);
var response = fgaClient.listUsers(body, options).get();
// response.getUsers() = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
```
```
fga query list-users --store-id=${FGA_STORE_ID} --model-id=01HXHK5D1Z6SCG1SV7M3BVZVCV --object document:1 --relation viewer --user-filter group#member
# Response: {"users": [{"userset":{"id":"engineering","relation":"member","type":"group"}}, {"userset":{"id":"product","relation":"member","type":"group"}}]}
```
```
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-users \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HXHK5D1Z6SCG1SV7M3BVZVCV",
"object": {
"type": "document",
"id": "1",
},
"relation": "viewer",
"user_filters": [
{
"type": "group",
"relation": "member"
}
]
}'
# Response: {"users": [{"userset":{"id":"engineering","relation":"member","type":"group"}}, {"userset":{"id":"product","relation":"member","type":"group"}}]}
```
## Type-bound public access
The List Users API supports tuples expressing public access via the wildcard syntax (e.g. `user:*`). Wildcard tuples that satisfy the query criteria will be returned with the `wildcard` root object property that will specify the type. A typed-bound public access result indicates that the object has a public relation but it doesn't necessarily indicate that all users of that type have that relation, it is possible that exclusions via the `but not` syntax exists. The API will not expand wildcard results further to any ID'd user object. Further, specific users that have been granted access will be returned in addition to any public access for that user's type.
caution
A List Users response with a type-bound public access result (e.g. `user:*`) doesn't necessarily indicate that all users of that type have access, it is possible that exclusions exist. It is recommended to [perform a Check](https://openfga.dev/docs/getting-started/perform-check.md) on specific users to ensure they have access to the target object.
Example response with type-bound public access:
```
{
"users": [
{
"wildcard": {
"type": "user"
}
},
{
"object": {
"type": "user",
"id": "anne"
}
}
]
}
```
## Related Sections
Take a look at the following section for more on how to perform list users in your system
**OpenFGA List Users API**
Read the List Users API documentation and see how it works.
- [More](https://openfga.dev/api/service#Relationship%20Queries/ListUsers)
---
# 🛡️Setup Access Control
In OpenFGA [v1.7.0](https://github.com/openfga/openfga/releases/tag/v1.7.0), we introduced an experimental built-in access control feature that allows you to control access to your OpenFGA server. It relies on a control store with its own model and tuples to authorize requests to the OpenFGA server itself.
Currently, there is no provided way to initialize that access control store and model, nor is there a way to bootstrap the client IDs that are supposed to be admins.
Warning
The built-in access control feature in OpenFGA is experimental and is not recommended for production use. We are looking for feedback on this, so if you do try it, please reach out on our [openfga Slack channel](https://openfga.dev/docs/community.md) in the CNCF community.
Read the following steps to enable access control.
## Requirements
- OIDC Provider: You need to have an OIDC provider [set up and configured](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md#oidc) in your OpenFGA server set up to use access control.
- A Client ID ready to be used: You need to have the initial (admin) client ID that you want to manage access to your OpenFGA server.
- The FGA CLI: While the CLI is not strictly required, you need to follow the steps below. You can install it by following the instructions [here](https://openfga.dev/docs/getting-started/cli.md). If you do not want to use the CLI, you can call the API with the equivalent SDK or REST calls.
## 01. Ensure the server is running (with access control disabled)
This is important. If you enable access control before setting up the store and model and grant your initial client ID access, you will lock yourself out of the server, and you will have to turn it back off.
## 02. Create the access control store and model
We will be using the following model to enable access control.
Customizing your access control model
You may choose to modify this model to suit your needs, however, keep in mind that configuring the model may not be supported in the future and you may be responsible for your own migrations at that point.
The required types and relations that need to be defined are marked in the model below.
```
model
schema 1.1
type system # required
relations
define admin: [application] # required
define can_call_create_stores: admin # required
define can_call_list_stores: [application, application:*] or admin # required
type application # required
type store # required
relations
define system: [system] # required
define admin: [application] or admin from system # required
define model_writer: [application] or admin
define reader: [application] or admin
define writer: [application] or admin
define can_call_delete_store: admin # required
define can_call_get_store: reader or writer or model_writer # required
define can_call_check: reader # required
define can_call_expand: reader # required
define can_call_list_objects: reader # required
define can_call_list_users: reader # required
define can_call_read: reader # required
define can_call_read_assertions: reader or model_writer # required
define can_call_read_authorization_models: reader or model_writer # required
define can_call_read_changes: reader # required
define can_call_write: writer # required
define can_call_write_assertions: model_writer # required
define can_call_write_authorization_models: model_writer # required
type module # required
relations
define store: [store] # required
define writer: [application]
define can_call_write: writer or writer from store # required
```
1. Place the model above in a file called `model.fga`.
2. Run the following command to create the store and model:
```
fga store create --name root-access-control --model ./model.fga
```
This prints a store ID and model ID. You will need these IDs in the following steps.
3. Grant your initial client ID access. You can do so by writing a tuple to the access control store you just created. The tuple should be of the type `application` and should have the `client_id` field set to the client ID of the client you want to grant access to. You can use the FGA CLI to do this:
```
fga tuple write --store-id "${ACCESS_CONTROL_STORE_ID}" "application:${FGA_ADMIN_CLIENT_ID}" admin "system:fga"
```
Replace `${ACCESS_CONTROL_STORE_ID}` with the store ID you received in the previous step; replace `${FGA_ADMIN_CLIENT_ID}` with the client ID you want to grant access to.
## 03. Enable access control
### i. Enable access control in the server
1. Enable the experimental support for access control by setting the environment variable `OPENFGA_EXPERIMENTALS` to `enable-access-control`.
2. Enable the access control feature by setting the environment variable `OPENFGA_ACCESS_CONTROL_ENABLED` to `true`.
3. Set the environment variable `OPENFGA_ACCESS_CONTROL_STORE_ID` to the store ID you received in the previous step.
4. Set the environment variable `OPENFGA_ACCESS_CONTROL_MODEL_ID` to the model ID you received in the previous step.
### ii. Customize what claim you want the API to use (optional)
By default, the API will use the following claims (in order) in the OIDC token to identify the client. If you want to use a different claim, you can set the environment variable `OPENFGA_AUTHN_OIDC_CLIENT_ID_CLAIMS` to the claim(s) you want to use.
If the claims are not set in the configuration, the following claims are used as default (in order):
1. `azp`: following [the OpenID standard](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)
2. `client_id` following [RFC9068](https://www.rfc-editor.org/rfc/rfc9068.html#name-data-structure)
That means that if the `azp` claim is present in the token, it will be used to identify the client. If not, the `client_id` claim will be used instead.
For example, you can set the environment variable `OPENFGA_AUTHN_OIDC_CLIENT_ID_CLAIMS` to `user_id,employee_id,client_id` to allow the OpenFGA server to authorize based on:
1. Use the `user_id` claim if present in the token.
2. If not try to use the `employee_id` claim if present.
3. If not try to use the `client_id` claim.
## iii. Restart the server
You now need to restart the OpenFGA server in order for the configuration changes above to take effect. Congrats, you now have access control enabled! 🎉🎉
## 04. Grant access to a store
You can now use the admin client ID to manage access to your OpenFGA server. We will call it `FGA_ADMIN_CLIENT_ID` in the following examples to differentiate it from the client ID (called `FGA_CLIENT_ID`) you are granting access to.
We will also use `ACCESS_CONTROL_STORE_ID` as the store ID of the access control store, and `STORE_ID` as the store ID you are granting the client access to.
1. Grant access to a store (based on the model above, your choices are `admin`, `model_writer`, `writer` and `reader`).
```
fga tuple write --store-id "${ACCESS_CONTROL_STORE_ID}" "application:${FGA_CLIENT_ID}" model_writer "store:${STORE_ID}" --client-id "${FGA_ADMIN_CLIENT_ID}" --client-secret ... --api-token-issuer ... --api-audience ...
```
2. Grant access to writing tuples of a certain module in a store.
In order to grant access to only write to relations in certain modules, you must have a model with modules. Refer to the [modular models documentation](https://openfga.dev/docs/modeling/modular-models.md) for more on that feature.
If you want to grant access to a module in a store, you must namespace the module ID with the store ID, so the object of the tuple will be of the form `module:|`.
```
fga tuple write --store-id "${ACCESS_CONTROL_STORE_ID}" "application:${FGA_CLIENT_ID}" writer "module:${STORE_ID}|" --client-id "${FGA_ADMIN_CLIENT_ID}" --client-secret ... --api-token-issuer ... --api-audience ...
```
Note
If you are calling `Write` with a credential that only has access to certain modules and not the store, you will not be able to send tuples for more than 1 module in a certain request or you will get the following error: `the principal cannot write tuples of more than 1 module(s) in a single request`
## Related Sections
Check the following sections for more on how to use OpenFGA.
**Setup OpenFGA**
Learn how to setup and configure an OpenFGA server
- [More](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md)
**Setup OIDC**
Learn how to setup and configure an OpenFGA server
- [More](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md#oidc)
**Running OpenFGA in Production**
Learn the best practices of running OpenFGA in a production environment
- [More](https://openfga.dev/docs/best-practices/running-in-production.md)
---
# OpenFGA Configuration Options
## Passing in the options
You can configure the OpenFGA server in three ways:
- Using a configuration file.
- Using environment variables.
- Using command line parameters.
If the same option is configured in multiple ways the command line parameters will take precedence over environment variables, which will take precedence over the configuration file.
- Configuration File
- Environment Variables
- Command Line Parameters (Flags)
You can configure the OpenFGA server with a `config.yaml` file, which can be specified in either:
- `/etc/openfga`
- `$HOME/.openfga`
- `.` (i.e., the current working directory).
The OpenFGA server will search for the configuration file in the above order.
Here is a sample configuration to run OpenFGA with a Postgres database and using a preshared key for authentication:
```
datastore:
engine: postgres
uri: postgres://user:password@localhost:5432/mydatabase
authn:
method: preshared
preshared:
keys: ["key1", "key2"]
playground:
enabled: false
```
The OpenFGA server supports **environment variables** for configuration, and they will take priority over your configuration file. Each variable must be prefixed with `OPENFGA_` and followed by your option in uppercase (`datastore.engine` becomes `OPENFGA_DATASTORE_ENGINE`), e.g.
```
# Running as a binary
export OPENFGA_DATASTORE_ENGINE=postgres
export OPENFGA_DATASTORE_URI='postgres://postgres:password@postgres:5432/postgres?sslmode=disable'
export OPENFGA_AUTHN_METHOD=preshared
export OPENFGA_AUTHN_PRESHARED_KEYS='key1,key2'
export OPENFGA_PLAYGROUND_ENABLED=false
openfga run
# Running in docker
docker run docker.io/openfga/openfga:latest \
-e OPENFGA_DATASTORE_ENGINE=postgres \
-e OPENFGA_DATASTORE_URI='postgres://postgres:password@postgres:5432/postgres?sslmode=disable' \
-e OPENFGA_AUTHN_METHOD=preshared \
-e OPENFGA_AUTHN_PRESHARED_KEYS='key1,key2' \
-e OPENFGA_PLAYGROUND_ENABLED=false \
run
```
Command line parameters take precedence over environment variables and options in the configuration file. They are prefixed with `--` (`OPENFGA_DATASTORE_ENGINE` becomes `--datastore-engine`), e.g.
```
# Running as a binary
openfga run \
--datastore-engine postgres \
--datastore-uri 'postgres://postgres:password@postgres:5432/postgres?sslmode=disable' \
--authn-method=preshared \
--authn-preshared-keys='key1,key2' \
--playground-enabled=false
# Running in docker
docker run docker.io/openfga/openfga:latest run \
--datastore-engine postgres \
--datastore-uri 'postgres://postgres:password@postgres:5432/postgres?sslmode=disable' \
--authn-method=preshared \
--authn-preshared-keys='key1,key2' \
--playground-enabled=false
```
## List of options
The following table lists the configuration options for the OpenFGA server [v1.18.3](https://github.com/openfga/openfga/releases/tag/v1.18.3), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.18.3/.config-schema.json).
| Config File | Env Var | Flag Name | Type | Description | Default Value |
| -------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `maxTuplesPerWrite` | `OPENFGA_MAX_TUPLES_PER_WRITE` | `max-tuples-per-write` | integer | The maximum allowed number of tuples per Write transaction. | `100` |
| `maxTypesPerAuthorizationModel` | `OPENFGA_MAX_TYPES_PER_AUTHORIZATION_MODEL` | `max-types-per-authorization-model` | integer | The maximum allowed number of type definitions per authorization model. | `100` |
| `maxAuthorizationModelSizeInBytes` | `OPENFGA_MAX_AUTHORIZATION_MODEL_SIZE_IN_BYTES` | `max-authorization-model-size-in-bytes` | integer | The maximum size in bytes allowed for persisting an Authorization Model (default is 256KB). | `262144` |
| `maxConcurrentReadsForCheck` | `OPENFGA_MAX_CONCURRENT_READS_FOR_CHECK` | `max-concurrent-reads-for-check` | integer | The maximum allowed number of concurrent reads in a single Check query (default is MaxUint32). | `4294967295` |
| `maxConcurrentReadsForListObjects` | `OPENFGA_MAX_CONCURRENT_READS_FOR_LIST_OBJECTS` | `max-concurrent-reads-for-list-objects` | integer | The maximum allowed number of concurrent reads in a single ListObjects query (default is MaxUint32). | `4294967295` |
| `maxConcurrentReadsForListUsers` | `OPENFGA_MAX_CONCURRENT_READS_FOR_LIST_USERS` | `max-concurrent-reads-for-list-users` | integer | The maximum allowed number of concurrent reads in a single ListUsers query (default is MaxUint32). | `4294967295` |
| `maxConcurrentChecksPerBatchCheck` | `OPENFGA_MAX_CONCURRENT_CHECKS_PER_BATCH_CHECK` | `max-concurrent-checks-per-batch-check` | integer | The maximum number of checks that can be processed concurrently in a batch check request. | `50` |
| `maxChecksPerBatchCheck` | `OPENFGA_MAX_CHECKS_PER_BATCH_CHECK` | `max-checks-per-batch-check` | integer | The maximum number of tuples allowed in a BatchCheck request. | `50` |
| `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. | `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`, `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. 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` |
| `authn.method` | `OPENFGA_AUTHN_METHOD` | `authn-method` | string (enum=\[`none`, `preshared`, `oidc`]) | The authentication method to use. | `none` |
| `authn.preshared.keys` | `OPENFGA_AUTHN_PRESHARED_KEYS` | `authn-preshared-keys` | \[]string | List of preshared keys used for authentication | |
| `authn.oidc.issuer` | `OPENFGA_AUTHN_OIDC_ISSUER` | `authn-oidc-issuer` | string | The OIDC issuer (authorization server) signing the tokens. | |
| `authn.oidc.audience` | `OPENFGA_AUTHN_OIDC_AUDIENCE` | `authn-oidc-audience` | string | The OIDC audience of the tokens being signed by the authorization server. | |
| `authn.oidc.issuerAliases` | `OPENFGA_AUTHN_OIDC_ISSUER_ALIASES` | `authn-oidc-issuer-aliases` | \[]string | the OIDC issuer DNS aliases that will be accepted as valid when verifying the `iss` field of the JWTs. | |
| `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. | |
| `http.enabled` | `OPENFGA_HTTP_ENABLED` | `http-enabled` | boolean | Enables or disables the OpenFGA HTTP server. If this is set to true then 'grpc.enabled' must be set to true. | `true` |
| `http.addr` | `OPENFGA_HTTP_ADDR` | `http-addr` | string | The host:port address to serve the HTTP server on. | `0.0.0.0:8080` |
| `http.tls.enabled` | `OPENFGA_HTTP_TLS_ENABLED` | `http-tls-enabled` | boolean | Enables or disables transport layer security (TLS). | `false` |
| `http.tls.cert` | `OPENFGA_HTTP_TLS_CERT` | `http-tls-cert` | string | The (absolute) file path of the certificate to use for the TLS connection. | |
| `http.tls.key` | `OPENFGA_HTTP_TLS_KEY` | `http-tls-key` | | The (absolute) file path of the TLS key that should be used for the TLS connection. | |
| `http.upstreamTimeout` | `OPENFGA_HTTP_UPSTREAM_TIMEOUT` | `http-upstream-timeout` | string | The timeout duration for proxying HTTP requests upstream to the grpc endpoint. | `3s` |
| `http.corsAllowedOrigins` | `OPENFGA_HTTP_CORS_ALLOWED_ORIGINS` | `http-cors-allowed-origins` | \[]string | List of allowed origins for CORS requests | `*` |
| `http.corsAllowedHeaders` | `OPENFGA_HTTP_CORS_ALLOWED_HEADERS` | `http-cors-allowed-headers` | \[]string | List of allowed headers for CORS requests | `*` |
| `log.format` | `OPENFGA_LOG_FORMAT` | `log-format` | string (enum=\[`text`, `json`]) | The log format to output logs in. For production we recommend 'json' format. | `text` |
| `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,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.sampler` | `OPENFGA_TRACE_SAMPLER,OTEL_TRACES_SAMPLER` | `trace-sampler,otel-traces-sampler` | string (enum=\[`always_on`, `always_off`, `traceidratio`, `parentbased_always_on`, `parentbased_always_off`, `parentbased_traceidratio`]) | The sampler to use for tracing. Controls how sampling decisions are made. Defaults to 'traceidratio' for backward compatibility (note: the OpenTelemetry spec default is 'parentbased\_always\_on'). Unrecognized values produce a warning (when tracing is enabled) and fall back to 'traceidratio'. | `traceidratio` |
| `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` |
| `checkCache.limit` | `OPENFGA_CHECK_CACHE_LIMIT` | `check-cache-limit` | integer | the size limit (in items) of the cache for Check (queries and iterators) | `10000` |
| `checkIteratorCache.enabled` | `OPENFGA_CHECK_ITERATOR_CACHE_ENABLED` | `check-iterator-cache-enabled` | boolean | enable caching of datastore iterators. 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` |
| `checkIteratorCache.maxResults` | `OPENFGA_CHECK_ITERATOR_CACHE_MAX_RESULTS` | `check-iterator-cache-max-results` | integer | if caching of datastore iterators of Check requests is enabled, this is the limit of tuples to cache per key | `10000` |
| `checkIteratorCache.ttl` | `OPENFGA_CHECK_ITERATOR_CACHE_TTL` | `check-iterator-cache-ttl` | string (duration) | if caching of datastore iterators of Check requests is enabled, this is the TTL of each value | `10s` |
| `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 | 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` |
| `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
Check the following sections for more on how to configure OpenFGA.
**Configuring OpenFGA**
Learn more about the different ways to configure OpenFGA
- [More](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md)
**Running OpenFGA in Production**
Learn the best practices of running OpenFGA in a production environment
- [More](https://openfga.dev/docs/best-practices/running-in-production.md)
---
# Configuring OpenFGA
Refer to the [OpenFGA Getting Started](https://github.com/openfga/openfga?tab=readme-ov-file#getting-started) for info on the various ways to install OpenFGA.
The instructions below assume OpenFGA is installed and that you have the `openfga` binary in your PATH. If you have built `openfga` as a binary, but not in your path, you can refer to it directly (e.g. replace `openfga` in the instructions below with `./openfga` or `/path/to/openfga`).
For a list of all the configuration options that the latest release of OpenFGA supports, see [Configuration Options](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md), or you can run `openfga --help` to see the ones specific to your version.
note
The instructions below are for configuring the standalone OpenFGA server. If you are using OpenFGA as a library, you can refer to the [GoDoc](https://pkg.go.dev/github.com/openfga/openfga) for more information.
## Configuring data storage
OpenFGA supports multiple storage engine options, including:
- `memory` - A memory storage engine, which is the default. Data is lost between server restarts.
- `postgres` - A Postgres storage engine.
- `mysql` - A MySQL storage engine.
- `sqlite` - A SQLite storage engine.
The first time you run OpenFGA, or when you install a new version, you need to run the `openfga migrate` command. This will create the required database tables or perform the database migration required for a new version.
### Postgres
```
openfga migrate \
--datastore-engine postgres \
--datastore-uri 'postgres://postgres:password@postgres:5432/postgres?sslmode=disable'
openfga run \
--datastore-engine postgres \
--datastore-uri 'postgres://postgres:password@postgres:5432/postgres?sslmode=disable'
```
#### PostgreSQL Read Replicas Configuration
OpenFGA supports configuring separate read and write datastores for PostgreSQL to improve performance and scalability. This feature allows you to distribute read operations across read replicas while directing write operations to the primary database.
##### Setting Up Read Replicas
To use read replicas, you need to configure both a primary datastore (for writes) and a secondary datastore (for reads):
```
openfga run \
--datastore-engine postgres \
--datastore-uri 'postgres://postgres:password@primary:5432/postgres?sslmode=disable' \
--datastore-secondary-uri 'postgres://postgres:password@replica:5432/postgres?sslmode=disable'
```
**Important considerations:**
- The `--datastore-uri` parameter specifies the primary database (used for writes and high-consistency reads)
- The `--datastore-secondary-uri` parameter specifies the read replica (used for regular read operations)
- Both databases must have the same schema and should be kept in sync through PostgreSQL replication
##### Synchronous vs Asynchronous Replication
The choice between synchronous and asynchronous replication affects data consistency and performance:
**Synchronous Replication:**
- **Pros:** Guarantees data consistency across primary and replica
- **Cons:** Higher latency for write operations
- **Use case:** When data consistency is critical and you can tolerate slower writes
- **PostgreSQL config:** `synchronous_commit = on` and `synchronous_standby_names = 'replica_name'`
**Asynchronous Replication:**
- **Pros:** Better write performance, lower latency
- **Cons:** Potential for read-after-write inconsistencies (replica lag)
- **Use case:** When write performance is prioritized and slight delays in read consistency are acceptable
- **PostgreSQL config:** `synchronous_commit = off` (default)
##### Consistency Preferences
OpenFGA provides consistency controls to handle read-after-write scenarios:
**Higher Consistency Mode:** When using `HIGHER_CONSISTENCY` preference in read operations, OpenFGA will automatically route the query to the primary database instead of the read replica, ensuring you get the most up-to-date data.
```
// Example: Reading with higher consistency
const { allowed } = await fgaClient.check(
{ user: "user:anne", relation: "can_view", object: "document:roadmap"},
{ consistency: ConsistencyPreference.HigherConsistency }
);
```
**Default Consistency Mode:** Regular read operations without the `HIGHER_CONSISTENCY` flag will be routed to the read replica for better performance.
##### Best Practices
1. **Monitor Replica Lag:** Set up monitoring for replication lag between primary and replica
2. **Use Higher Consistency Sparingly:** Only use `HIGHER_CONSISTENCY` when you need immediate read-after-write consistency
3. **Connection Pooling:** Configure appropriate connection pools for both primary and replica connections
##### Example PostgreSQL Replication Setup
Here's a basic example of setting up PostgreSQL streaming replication:
**Primary server configuration (postgresql.conf):**
```
wal_level = replica
max_wal_senders = 3
wal_keep_size = 64MB
synchronous_commit = on # for synchronous replication
synchronous_standby_names = 'replica1' # for synchronous replication
```
**Primary server authentication (pg\_hba.conf):**
```
host replication replicator replica_ip/32 md5
```
**Replica server configuration (postgresql.conf):**
```
hot_standby = on
```
**Replica server recovery configuration:**
```
standby_mode = 'on'
primary_conninfo = 'host=primary_ip port=5432 user=replicator'
```
note
This is a simplified example. For production setups, refer to the [PostgreSQL documentation on replication](https://www.postgresql.org/docs/current/runtime-config-replication.html) for comprehensive configuration guidelines.
Warning
When using asynchronous replication, be aware that read replicas might have slightly outdated data due to replication lag. Use the `HIGHER_CONSISTENCY` preference for operations that require the most recent data.
To learn how to run in Docker, check our [Docker documentation](https://openfga.dev/docs/getting-started/setup-openfga/docker.md#using-postgres).
### MySQL
The MySQL datastore has stricter limits for the max length of some fields for tuples compared to other datastore engines, in particular:
- object type is at most 128 characters (down from 256)
- object id is at most 255 characters (down from 256)
- user is at most 256 characters (down from 512)
The connection URI needs to specify the query `parseTime=true`.
```
openfga migrate \
--datastore-engine mysql \
--datastore-uri 'root:secret@tcp(mysql:3306)/openfga?parseTime=true'
openfga run \
--datastore-engine mysql \
--datastore-uri 'root:secret@tcp(mysql:3306)/openfga?parseTime=true'
```
To learn how to run in Docker, check our [Docker documentation](https://openfga.dev/docs/getting-started/setup-openfga/docker.md#using-mysql).
### SQLite
```
openfga migrate
--datastore-engine sqlite \
--datastore-uri 'file:/path/to/openfga.db'
openfga run
--datastore-engine sqlite \
--datastore-uri 'file:/path/to/openfga.db'
```
To learn how to run in Docker, check our [Docker documentation](https://openfga.dev/docs/getting-started/setup-openfga/docker.md#using-sqlite).
## Configuring authentication
You can configure authentication in three ways:
- no authentication (default)
- pre-shared key authentication
- OIDC
### Pre-shared key authentication
If using **Pre-shared key authentication**, you will configure OpenFGA with one or more secret keys and your application calling OpenFGA will have to set an `Authorization: Bearer ` header.
Warning
If you are going to use this setup in production, you should enable HTTP TLS in your OpenFGA server. You will need to configure the TLS certificate and key.
- Configuration File
- Environment Variables
Update the config.yaml file to
```
authn:
method: preshared
preshared:
keys: ["key1", "key2"]
http:
tls:
enabled: true
cert: /Users/myuser/key/server.crt
key: /Users/myuser/key/server.key
```
1. Configure the authentication method to preshared: `export OPENFGA_AUTHN_METHOD=preshared`.
2. Configure the authentication keys: `export OPENFGA_AUTHN_PRESHARED_KEYS=key1,key2`
3. Enable the HTTP TLS configuration: `export OPENFGA_HTTP_TLS_ENABLED=true`
4. Configure the HTTP TLS certificate location: `export OPENFGA_HTTP_TLS_CERT=/Users/myuser/key/server.crt`
5. Configure the HTTP TLS key location: `export OPENFGA_HTTP_TLS_KEY=/Users/myuser/key/server.key`
To learn how to run in Docker, check our [Docker documentation](https://openfga.dev/docs/getting-started/setup-openfga/docker.md#pre-shared-key-authentication).
### OIDC
To configure with OIDC authentication, you will first need to obtain the OIDC issuer and audience from your provider.
Warning
If you are going to use this setup in production, you should enable HTTP TLS in your OpenFGA server. You will need to configure the TLS certificate and key.
- Configuration File
- Environment Variables
Update the config.yaml file to
```
authn:
method: oidc
oidc:
issuer: "oidc-issuer" # required
issuerAliases: "oidc-issuer-1", "oidc-issuer-2" # optional
audience: "oidc-audience" # required
subjects: "valid-subject-1", "valid-subject-2" # optional
http:
tls:
enabled: true
cert: /Users/myuser/key/server.crt
key: /Users/myuser/key/server.key
```
1. Configure the authentication method to OIDC: `export OPENFGA_AUTHN_METHOD=oidc`.
2. Configure the valid issuer (required): `export OPENFGA_AUTHN_OIDC_ISSUER=oidc-issuer`
3. Configure the valid issuer aliases (optional): `export OPENFGA_AUTHN_OIDC_ISSUER_ALIASES=oidc-issuer-1,oidc-issuer-2`
4. Configure the valid audience (required): `export OPENFGA_AUTHN_OIDC_AUDIENCE=oidc-audience`
5. Configure the valid subjects (optional): `export OPENFGA_AUTHN_OIDC_SUBJECTS=oidc-subject-1,oidc-subject-2`
6. Enable the HTTP TLS configuration: `export OPENFGA_HTTP_TLS_ENABLED=true`
7. Configure the HTTP TLS certificate location: `export OPENFGA_HTTP_TLS_CERT=/Users/myuser/key/server.crt`
8. Configure the HTTP TLS key location: `export OPENFGA_HTTP_TLS_KEY=/Users/myuser/key/server.key`
To learn how to run in Docker, check our [Docker documentation](https://openfga.dev/docs/getting-started/setup-openfga/docker.md#oidc-authentication).
## Profiler (pprof)
Warning
Continuous profiling can be used in production deployments, but we recommend disabling it unless it is needed to troubleshoot specific performance or memory problems.
Profiling through [`pprof`](https://github.com/google/pprof/blob/main/doc/README.md) can be enabled on the OpenFGA server by providing the `--profiler-enabled` flag. For example:
```
openfga run --profiler-enabled
```
If you need to serve the profiler on a different port than the default `3001`, you can do so by specifying the `--profiler-addr` flag. For example:
```
openfga run --profiler-enabled --profiler-addr :3002
```
If you want to run it in docker:
```
docker run -p 8080:8080 -p 8081:8081 -p 3000:3000 -p 3002:3002 openfga/openfga run --profiler-enabled --profiler-addr :3002
```
## Health check
OpenFGA is configured with an HTTP health check endpoint `/healthz` and a gRPC health check `grpc.health.v1.Health/Check`, which is wired to datastore testing. Possible response values are
- UNKNOWN
- SERVING
- NOT\_SERVING
- SERVICE\_UNKNOWN
* cURL
* gRPC
```
curl -X GET $FGA_API_URL/healthz
# {"status":"SERVING"}
```
```
# See https://github.com/fullstorydev/grpcurl#installation
grpcurl -plaintext $FGA_API_URL grpc.health.v1.Health/Check
# {"status":"SERVING"}
```
## Experimental features
Various releases of OpenFGA may have experimental features that can be enabled by providing the [`--experimentals`](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md#OPENFGA_EXPERIMENTALS) flag or the `experimentals` config.
```
openfga run --experimentals="feature1, feature2"
```
or if you're using environment variables,
```
openfga -e OPENFGA_EXPERIMENTALS="feature1, feature2" run
```
The following table enumerates the experimental flags, a description of what they do, and the versions of OpenFGA the flag is supported in:
| Name | Description | OpenFGA Version |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| otel-metrics | Enables support for exposing OpenFGA metrics through OpenTelemetry | `0.3.2 <= v < 0.3.5` |
| list-objects | Enables ListObjects API | `0.2.0 <= v < 0.3.3` |
| check-query-cache | Enables caching of check subproblem result | `1.3.1 <= v < 1.3.6` |
| enable-conditions | Enables conditional relationship tuples | `1.3.8 <= v < 1.4.0` |
| enable-modular-models | Enables modular authorization modules | `1.5.1 <= v < 1.5.3` |
| enable-list-users | Enables new ListUsers API | `1.5.4 <= v < 1.5.6` |
| enable-consistency-params | Enables consistency options | `1.5.6 <= v < 1.6.0` |
| enable-check-optimizations | Enables performance optimization on Check | `1.6.2 <= v` |
| enable-access-control | Enables the ability to configure and setup [access control](https://openfga.dev/docs/getting-started/setup-openfga/access-control.md) | `1.7.0 <= v` |
Warning
Experimental features are not guaranteed to be stable and may lead to server instabilities. It is not recommended to enable experimental features for anything other than experimentation.
Experimental feature flags are also not considered part of API compatibility and are subject to change, so please refer to each OpenFGA specific release for a list of the experimental feature flags that can be enabled for that release.
## Telemetry
OpenFGA telemetry data is collected by default starting on version `v0.3.5`. The telemetry information that is captured includes Metrics, Traces, and Logs.
note
Please refer to the [docker-compose.yaml](https://github.com/openfga/openfga/blob/main/docker-compose.yaml) file as an example of how to collect Metrics and Tracing in OpenFGA in a Docker environment using the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/). This should serve as a good example that you can adjust for your specific deployment scenario.
## Metrics
OpenFGA metrics are collected with the [Prometheus data format](https://prometheus.io/docs/concepts/data_model/) and exposed on address `0.0.0.0:2112/metrics`.
Metrics are exposed by default, but you can disable this with `--metrics-enabled=false` (or `OPENFGA_METRICS_ENABLED=false` environment variable).
To set an alternative address, you can provide the `--metrics-addr` flag (`OPENFGA_METRICS_ADDR` environment variable). For example:
```
openfga run --metrics-addr=0.0.0.0:2114
```
To see the request latency per endpoint of your OpenFGA deployment, you can provide the `--metrics-enable-rpc-histograms` flag (`OPENFGA_METRICS_ENABLE_RPC_HISTOGRAMS` environment variable).
## Tracing
OpenFGA traces can be collected with the [OTLP data format](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md).
Tracing is disabled by default, but you can enable this with the `--trace-enabled=true` (`OPENFGA_TRACE_ENABLED=true` environment variable). Traces will be exported by default to address `0.0.0.0:4317`. You can change this address with the `--trace-otlp-endpoint` flag (`OPENFGA_TRACE_OTLP_ENDPOINT` environment variable). In order to just correlate `trace_id` in logs if you are propagating tracing contexts into OpenFGA, exporter can be disabled by providing `none` as endpoint value.
To increase or decrease the trace sampling ratio, you can provide the `--trace-sample-ratio` flag (`OPENFGA_TRACE_SAMPLE_RATIO` env variable).
Tracing by default uses a insecure connection. You can enable TLS by using `--trace-otlp-tls-enabled=true` flag or the environment variable `OPENFGA_TRACE_OTLP_TLS_ENABLED`.
Warning
It is not recommended to sample all traces (e.g. `--trace-sample-ratio=1`). You will need to adjust your sampling ratio based on the amount of traffic your deployment receives. Higher traffic will require less sampling and lower traffic can tolerate higher sampling ratios.
## Logging
OpenFGA generates structured logs by default, and it can be configured with the following flags:
- `--log-format`: sets the log format. Today we support `text` and `json` format.
- `--log-level`: sets the minimum log level (defaults to `info`). It can be set to `none` to turn off logging.
Warning
It is highly recommended to enable logging in production environments. Disabling logging (`--log-level=none`) can mask important operations and hinder the ability to detect and diagnose issues, including potential security incidents. Ensure that logs are enabled and properly monitored to maintain visibility into the application's behavior and security.
## Related Sections
Check the following sections for more on how to use OpenFGA.
**Configuration Options**
Find out all the different flags and options that OpenFGA accepts
- [More](https://openfga.dev/docs/getting-started/setup-openfga/configuration.md)
**Running OpenFGA in Production**
Learn the best practices of running OpenFGA in a production environment
- [More](https://openfga.dev/docs/best-practices/running-in-production.md)
---
# 🐳 Setup OpenFGA with Docker
This article explains how to run your own OpenFGA server using Docker. To learn the different ways to configure OpenFGA check [Configuring OpenFGA](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md).
## Step by step
If you want to run OpenFGA locally as a Docker container, follow these steps:
1. [Install Docker](https://docs.docker.com/get-docker/) (if not already installed).
2. Run `docker pull openfga/openfga` to get the latest docker image.
3. Run `docker run -p 8080:8080 -p 8081:8081 -p 3000:3000 openfga/openfga run`.
This will start an HTTP server and gRPC server with the default configuration options. Port 8080 is used to serve the HTTP API, 8081 is used to serve the gRPC API, and 3000 is used for the [Playground](https://openfga.dev/docs/getting-started/setup-openfga/playground.md).
## Using Postgres
- Docker
- Docker Compose
To run OpenFGA and Postgres in containers, you can create a new network to make communication between containers simpler:
```
docker network create openfga
```
You can then start Postgres in the network you created above:
```
docker run -d --name postgres --network=openfga -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password postgres:17
```
You should now have Postgres running in a container in the `openfga` network. However, it will not have the tables required for running OpenFGA. You can use the `migrate` command to create the tables. Using the OpenFGA container, this will look like:
```
docker run --rm --network=openfga openfga/openfga migrate \
--datastore-engine postgres \
--datastore-uri "postgres://postgres:password@postgres:5432/postgres?sslmode=disable"
```
Finally, start OpenFGA:
```
docker run --name openfga --network=openfga -p 3000:3000 -p 8080:8080 -p 8081:8081 openfga/openfga run \
--datastore-engine postgres \
--datastore-uri 'postgres://postgres:password@postgres:5432/postgres?sslmode=disable'
```
Copy the below code block into a local file named: `docker-compose.yaml`
```
networks:
openfga:
services:
postgres:
image: postgres:17
container_name: postgres
networks:
- openfga
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 5s
timeout: 5s
retries: 5
migrate:
depends_on:
postgres:
condition: service_healthy
image: openfga/openfga:latest
container_name: migrate
command: migrate
environment:
- OPENFGA_DATASTORE_ENGINE=postgres
- OPENFGA_DATASTORE_URI=postgres://postgres:password@postgres:5432/postgres?sslmode=disable
networks:
- openfga
openfga:
depends_on:
migrate:
condition: service_completed_successfully
image: openfga/openfga:latest
container_name: openfga
environment:
- OPENFGA_DATASTORE_ENGINE=postgres
- OPENFGA_DATASTORE_URI=postgres://postgres:password@postgres:5432/postgres?sslmode=disable
- OPENFGA_LOG_FORMAT=json
command: run
networks:
- openfga
ports:
# Needed for the http server
- "8080:8080"
# Needed for the grpc server (if used)
- "8081:8081"
# Needed for the playground (Do not enable in prod!)
- "3000:3000"
```
In a terminal, navigate to that directory and run:
```
docker-compose up
```
This will start the Postgres database, run `openfga migrate` to configure the database and finally start the OpenFGA server.
## Using MySQL
- Docker
- Docker Compose
We first make a network:
```
docker network create openfga
```
Then, start MySQL in the network you created above:
```
docker run -d --name mysql --network=openfga -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=openfga mysql:8
```
You should now have MySQL running in a container in the `openfga` network. But we still have to migrate all the tables to be able to run OpenFGA. You can use the `migrate` command to create the tables. Using the OpenFGA container, this will look like:
```
docker run --rm --network=openfga openfga/openfga migrate \
--datastore-engine mysql \
--datastore-uri 'root:secret@tcp(mysql:3306)/openfga?parseTime=true'
```
Finally, start OpenFGA:
```
docker run --name openfga --network=openfga -p 3000:3000 -p 8080:8080 -p 8081:8081 openfga/openfga run \
--datastore-engine mysql \
--datastore-uri 'root:secret@tcp(mysql:3306)/openfga?parseTime=true'
```
Copy the below code block into a local file named: `docker-compose.yaml`
```
networks:
openfga:
services:
mysql:
image: mysql:8
container_name: mysql
networks:
- openfga
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=openfga
healthcheck:
test: ["CMD", 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-p$$MYSQL_ROOT_PASSWORD' ]
timeout: 20s
retries: 5
migrate:
depends_on:
mysql:
condition: service_healthy
image: openfga/openfga:latest
container_name: migrate
command: migrate
environment:
- OPENFGA_DATASTORE_ENGINE=mysql
- OPENFGA_DATASTORE_URI=root:secret@tcp(mysql:3306)/openfga?parseTime=true
networks:
- openfga
openfga:
depends_on:
migrate:
condition: service_completed_successfully
image: openfga/openfga:latest
container_name: openfga
environment:
- OPENFGA_DATASTORE_ENGINE=mysql
- OPENFGA_DATASTORE_URI=root:secret@tcp(mysql:3306)/openfga?parseTime=true
- OPENFGA_LOG_FORMAT=json
command: run
networks:
- openfga
ports:
# Needed for the http server
- "8080:8080"
# Needed for the grpc server (if used)
- "8081:8081"
# Needed for the playground (Do not enable in prod!)
- "3000:3000"
```
In a terminal, navigate to that directory and run:
```
docker-compose up
```
This will start the MySQL database, run `openfga migrate` to configure the database and finally start the OpenFGA server.
## Using SQLite
- Docker
- Docker Compose
We first make a network:
```
docker network create openfga
```
Then, create a volume to hold the openfga database:
```
docker volume create openfga
```
Next you have to migrate all the tables to be able to run OpenFGA. You can use the `migrate` command to create the tables. Using the OpenFGA container, this will look like:
```
docker run --rm --network=openfga \
-v openfga:/home/nonroot \
-u nonroot \
openfga/openfga migrate \
--datastore-engine sqlite \
--datastore-uri 'file:/home/nonroot/openfga.db'
```
Finally, start OpenFGA:
```
docker run --name openfga --network=openfga \
-p 3000:3000 -p 8080:8080 -p 8081:8081 \
-v openfga:/home/nonroot \
-u nonroot \
openfga/openfga run \
--datastore-engine sqlite \
--datastore-uri 'file:/home/nonroot/openfga.db'
```
Copy the below code block into a local file named: `docker-compose.yaml`
```
networks:
openfga:
volumes:
openfga:
services:
migrate:
image: openfga/openfga:latest
container_name: migrate
command: migrate
user: nonroot
environment:
- OPENFGA_DATASTORE_ENGINE=sqlite
- OPENFGA_DATASTORE_URI=file:/home/nonroot/openfga.db
networks:
- openfga
volumes:
- openfga:/home/nonroot
openfga:
depends_on:
migrate:
condition: service_completed_successfully
image: openfga/openfga:latest
container_name: openfga
user: nonroot
environment:
- OPENFGA_DATASTORE_ENGINE=sqlite
- OPENFGA_DATASTORE_URI=file:/home/nonroot/openfga.db
- OPENFGA_LOG_FORMAT=json
command: run
networks:
- openfga
volumes:
- openfga:/home/nonroot
ports:
# Needed for the http server
- "8080:8080"
# Needed for the grpc server (if used)
- "8081:8081"
# Needed for the playground (Do not enable in prod!)
- "3000:3000"
```
In a terminal, navigate to that directory and run:
```
docker-compose up
```
This will create a new `openfga` volume to store the SQLite database, run `openfga migrate` to configure the database and finally start the OpenFGA server.
## Pre-shared key authentication
To configure with pre-shared authentication and enabling TLS in http server with Docker.
1. Copy the certificate and key files to your Docker container.
2. Run with the following command:
```
docker run --name openfga --network=openfga -p 3000:3000 -p 8080:8080 -p 8081:8081 openfga/openfga run \
--authn-method=preshared \
--authn-preshared-keys="key1,key2" \
--http-tls-enabled=true \
--http-tls-cert="/Users/myuser/key/server.crt" \
--http-tls-key="/Users/myuser/key/server.key"
```
## OIDC authentication
To configure with OIDC authentication and enabling TLS in http server with Docker.
1. Copy the certificate and key files to your docker container.
2. Run the following command
```
docker run --name openfga --network=openfga -p 3000:3000 -p 8080:8080 -p 8081:8081 openfga/openfga run \
--authn-method=oidc \
--authn-oidc-issuer="oidc-issuer" \
--authn-oidc-audience="oidc-audience" \
--http-tls-enabled=true \
--http-tls-cert="/Users/myuser/key/server.crt" \
--http-tls-key="/Users/myuser/key/server.key"
```
## Enabling profiling
If you are enabling profiling, make sure you enable the corresponding port in docker. The default port is `3001`, but if you need to serve the profiler on a different port, you can do so by specifying the `--profiler-addr` flag. For example:
```
docker run -p 8080:8080 -p 8081:8081 -p 3000:3000 -p 3002:3002 openfga/openfga run --profiler-enabled --profiler-addr :3002
```
## Related sections
Check the following sections for more on how to use OpenFGA.
**Running OpenFGA in Production**
Learn the best practices of running OpenFGA in a production environment
- [More](https://openfga.dev/docs/best-practices/running-in-production.md)
---
# ☸️ Setup OpenFGA with Kubernetes
To deploy OpenFGA into a Kubernetes environment you can use the official [OpenFGA Helm chart](https://artifacthub.io/packages/helm/openfga/openfga). Please refer to the official documentation on Artifact Hub for the Helm chart for more instructions.
---
# Setup OpenFGA
Follow the guides below to set up an OpenFGA server.
**Configure an OpenFGA Server**
How to setup an OpenFGA server.
- [Configure an OpenFGA Server](https://openfga.dev/docs/getting-started/setup-openfga/configure-openfga.md)
**Docker Setup Guide**
How to setup an OpenFGA server with Docker.
- [Docker Setup Guide](https://openfga.dev/docs/getting-started/setup-openfga/docker.md)
**Kubernetes Setup Guide**
How to setup an OpenFGA server with Kubernetes.
- [Kubernetes Setup Guide](https://openfga.dev/docs/getting-started/setup-openfga/kubernetes.md)
**Setup Access Control**
How to enable and setup the built-in access control OpenFGA server (experimental).
- [Setup Access Control](https://openfga.dev/docs/getting-started/setup-openfga/access-control.md)
---
# Using the OpenFGA Playground
The Playground facilitates rapid development by allowing you to visualize and model your application's authorization models and manage relationship tuples with a locally running OpenFGA instance.
It is enabled on port 3000 by default and accessible at .
The Playground is designed for early prototyping and learning. It has several limitations:
- It works by embedding the public [Playground website](https://play.fga.dev) in an `