A cross-platform CLI to interact with an OpenFGA server
- About OpenFGA
- Resources
- Installation
- Building from Source
- Usage
- Contributing
- License
OpenFGA is an open source Fine-Grained Authorization solution inspired by Google's Zanzibar paper. It was created by the FGA team at Auth0/Okta based on Okta Fine-Grained Authorization (FGA), available under a permissive license (Apache-2) and welcomes community contributions.
OpenFGA is designed to make it easy for application builders to model their permission layer, and to add and integrate fine-grained authorization into their applications. OpenFGA’s design is optimized for reliability and low latency at a high scale.
- OpenFGA Documentation
- OpenFGA API Documentation
- OpenFGA Community
- Zanzibar Academy
- Google's Zanzibar Paper (2019)
brew install openfga/tap/fga
Download the .deb, .rpm or .apk packages from the releases page.
Debian:
sudo apt install ./fga_<version>_linux_<arch>.deb
Fedora:
sudo dnf install ./fga_<version>_linux_<arch>.rpm
Alpine Linux:
sudo apk add --allow-untrusted ./fga_<version>_linux_<arch>.apk
via Scoop
scoop install openfga
docker pull openfga/cli; docker run -it openfga/cli
go install github.com/openfga/cli/cmd/fga@latest
Download the pre-compiled binaries from the releases page.
Make sure you have Go 1.20 or later installed. See the Go downloads page.
-
Clone the repo to a local directory, and navigate to that directory:
git clone https://github.com/openfga/cli.git && cd cli
-
Then use the build command:
go build -o ./dist/fga ./cmd/fga/main.go
or if you have
make
installed, just run:make build
-
Run the OpenFGA CLI with:
./dist/fga
For any command that interacts with an OpenFGA server, these configuration values can be passed (where applicable)
Name | Flag | CLI | ~/.fga.yaml |
---|---|---|---|
API Url | --api-url |
FGA_API_URL |
api-url |
Shared Secret | --api-token |
FGA_API_TOKEN |
api-token |
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 |
Store ID | --store-id |
FGA_STORE_ID |
store-id |
Authorization Model ID | --model-id |
FGA_MODEL_ID |
model-id |
If you are authenticating with a shared secret, you should specify the API Token value. If you are authenticating using OAuth, you should specify the Client ID, Client Secret, API Audience and Token Issuer. For example:
# Note: This example is for Okta FGA
api-url: https://api.us1.fga.dev
client-id: 4Zb..UYjaHreLKOJuU8
client-secret: J3...2pBwiauD
api-audience: https://api.us1.fga.dev/
api-token-issuer: auth.fga.dev
store-id: 01H0H015178Y2V4CX10C2KGHF4
Description | command | parameters | example |
---|---|---|---|
Create a Store | create |
--name |
fga store create --name="FGA Demo Store" |
Import a Store | import |
--file |
fga store import --file store.fga.yaml |
Export a Store | export |
--store-id |
fga store export --store-id=01H0H015178Y2V4CX10C2KGHF4 |
List Stores | list |
fga store list |
|
Get a Store | get |
--store-id |
fga store get --store-id=01H0H015178Y2V4CX10C2KGHF4 |
Delete a Store | delete |
--store-id |
fga store delete --store-id=01H0H015178Y2V4CX10C2KGHF4 |
fga store create
--name
: Name of the store to be created. If themodel
parameter is specified, the model file name will be used as the default store name.--model
: File with the authorization model. Can be in JSON, OpenFGA format, or fga.mod file (optional).--format
: Authorization model input format. Can be "fga", "json", or "modular" (optional, defaults to the model file extension if present).
fga store create --name "FGA Demo Store"
{
"id": "01H0H015178Y2V4CX10C2KGHF4",
"name": "FGA Demo Store",
"created_at": "2023-05-19T16:10:07.637585677Z",
"updated_at": "2023-05-19T16:10:07.637585677Z"
}
fga store create --model Model.fga
{
"store": {
"id":"01H6H9CNQRP2TVCFR7899XGNY8",
"name":"Model",
"created_at":"2023-07-29T16:58:28.984402Z",
"updated_at":"2023-07-29T16:58:28.984402Z"
},
"model": {
"authorization_model_id":"01H6H9CNQV36Y9WS1RJGRN8D06"
}
}
To automatically set the created store id as an environment variable that will then be used by the CLI, you can use the following command:
export FGA_STORE_ID=$(fga store create --model model.fga | jq -r .store.id)
fga store import
--file
: File containing the store.--store-id
: Specifies the store id to import into--max-tuples-per-write
: Max tuples to send in a single write (optional, default=1)--max-parallel-requests
: Max requests to send in parallel (optional, default=4)
fga store import --file model.fga.yaml
{}
fga store export
--store-id
: Specifies the store to export--output-file
: The file to output the store to (optional, writes to the terminal if omitted)--model-id
: Specifies the model to export (optional, exports the latest model if omitted)--max-tuples
: Specifies the max number of tuples to include in the output (option, defaults to 100)
fga store export --store-id=01H0H015178Y2V4CX10C2KGHF4
name: Test
model: |+
model
schema 1.1
type user
type group
relations
define member: [user]
define moderator: [user]
tuples:
- user: user:1
relation: member
object: group:admins
- user: user:1
relation: member
object: group:employees
- user: user:2
relation: member
object: group:employees
- user: user:1
relation: moderator
object: group:employees
tests:
- name: Tests
check:
- user: user:1
object: group:admins
assertions:
member: true
- user: user:2
object: group:admins
assertions:
member: false
- user: user:1
object: group:employees
assertions:
member: true
moderator: true
- user: user:2
object: group:employees
assertions:
member: true
moderator: false
If using output-file
, the response will be written to the specified file on disk. If the desired file already exists, you will be prompted to overwrite the file.
fga store list
--max-pages
: Max number of pages to retrieve (default: 20)
fga store list
{
"stores": [{
"id": "..",
"name": "..",
"created_at": "",
"updated_at": "",
"deleted_at": ""
}, { .. }]
}
fga store get
--store-id
: Specifies the store id to get
fga store get --store-id=01H0H015178Y2V4CX10C2KGHF4
{
"id": "01H0H015178Y2V4CX10C2KGHF4",
"name": "FGA Demo Store",
"created_at": "2023-05-19T16:10:07.637585677Z",
"updated_at": "2023-05-19T16:10:07.637585677Z"
}
fga store delete
--store-id
: Specifies the store id to delete
fga store delete --store-id=01H0H015178Y2V4CX10C2KGHF4
{}
model
Description | command | parameters | example |
---|---|---|---|
Read Authorization Models | list |
--store-id |
fga model list --store-id=01H0H015178Y2V4CX10C2KGHF4 |
Write Authorization Model | write |
--store-id , --file |
fga model write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file model.fga |
Read a Single Authorization Model | get |
--store-id , --model-id |
fga model get --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1 |
Validate an Authorization Model | validate |
--file , --format |
fga model validate --file model.fga |
Run Tests on an Authorization Model | test |
--tests , --verbose |
fga model test --tests tests.fga.yaml |
Transform an Authorization Model | transform |
--file , --input-format |
fga model transform --file model.json |
List all authorization models for a store, in descending order by creation date. The first model in the list is the latest one.
fga model list
--store-id
: Specifies the store id--max-pages
: Max number of pages to retrieve (default: 20)--field
: Fields to display. Choices are: id, created_at and model. Default are id, created_at.
fga model list --store-id=01H0H015178Y2V4CX10C2KGHF4
{
"authorization_models": [
{
"id":"01H6H9XH1G5Q6DK6PFMGDZNH9S",
"created_at":"2023-07-29T17:07:41Z"
},
{
"id":"01H6H9PPR6C3P45R75X55ZFP46",
"created_at":"2023-07-29T17:03:57Z"
}
]
}
fga model write
--store-id
: Specifies the store id--file
: File containing the authorization model.--format
: Authorization model input format. Can be "fga", "json", or "modular". Defaults to the file extension if provided (optional)
fga model write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file=model.fga
fga model write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file=fga.mod
fga model write --store-id=01H0H015178Y2V4CX10C2KGHF4 '{"type_definitions": [ { "type": "user" }, { "type": "document", "relations": { "can_view": { "this": {} } }, "metadata": { "relations": { "can_view": { "directly_related_user_types": [ { "type": "user" } ] }}}} ], "schema_version": "1.1"}' --format json
{
"authorization_model_id":"01GXSA8YR785C4FYS3C0RTG7B1"
}
fga model get
--store-id
: Specifies the store id--model-id
: Specifies the model id--format
: Authorization model output format. Can be "fga" or "json" (default fga).--field
: Fields to display, choices are:id
,created_at
andmodel
. Default ismodel
.
fga model get --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1
model
schema 1.1
type user
type document
relations
define can_view: [user]
If model-id
is not specified when using the get
command, the latest authorization model will be returned.
fga model get
--store-id
: Specifies the store id
fga model get --store-id=01H0H015178Y2V4CX10C2KGHF4
model
schema 1.1
type user
type document
relations
define can_view: [user]
fga model validate
--file
: File containing the authorization model.--format
: Authorization model input format. Can be "fga", "json", or "modular". Defaults to the file extension if provided (optional)
fga model validate --file model.json
- Valid model with an ID
{"id":"01GPGWB8R33HWXS3KK6YG4ETGH","created_at":"2023-01-11T16:59:22Z","is_valid":true}
- Valid model without an ID
{"is_valid":true}
- Invalid model with an ID
{"id":"01GPGTVEH5NYTQ19RYFQKE0Q4Z","created_at":"2023-01-11T16:33:15Z","is_valid":false,"error":"invalid schema version"}
- Invalid model without an ID
{"is_valid":false,"error":"the relation type 'employee' on 'member' in object type 'group' is not valid"}
Given a model, and a set of tests (tuples, check and list objects requests, and expected results) report back on any tests that do not return the same results as expected.
fga model test
--tests
: Name of the tests file. Must be in yaml format (see below)--verbose
: Outputs the results in JSON
If a model is provided, the test will run in a built-in OpenFGA instance (you do not need a separate server). Otherwise, the test will be run against the configured store of your OpenFGA instance. When running against a remote instance, the tuples will be sent as contextual tuples, and will have to abide by the OpenFGA server limits (20 contextual tuples per request).
The tests file should be in yaml and have the following format:
---
name: Store Name # store name, optional
# model_file: ./model.fga # a global model that would apply to all tests, optional
# model can be used instead of model_file, optional
model: |
model
schema 1.1
type user
type folder
relations
define owner: [user]
define parent: [folder]
define can_view: owner or can_view from parent
define can_write: owner or can_write from parent
define can_share: owner
# tuple_file: ./tuples.yaml # global tuples that would apply to all tests, optional
tuples: # global tuples that would apply to all tests, optional
- user: folder:1
relation: parent
object: folder:2
tests: # required
- name: test-1
description: testing that the model works # optional
# tuple_file: ./tuples.json # tuples that would apply per test
tuples:
- user: user:anne
relation: owner
object: folder:1
check: # a set of checks to run
- user: user:anne
object: folder:1
assertions:
# a set of expected results for each relation
can_view: true
can_write: true
can_share: false
list_objects: # a set of list objects to run
- user: user:anne
type: folder
assertions:
# a set of expected results for each relation
can_view:
- folder:1
- folder:2
can_write:
- folder:1
- folder:2
- name: test-2
description: another test
tuples:
- user: user:anne
relation: owner
object: folder:1
check:
- user: user:anne
object: folder:1
assertions:
# a set of expected results for each relation
can_view: true
list_objects:
- user: user:anne
type: folder
assertions:
# a set of expected results for each relation
can_view:
- folder:1
- folder:2
fga model test --tests tests.fga.yaml
For more examples of .fga.yaml
files, check the sample-stores repository/
(FAILING) test-1: Checks (2/3 passing) | ListObjects (2/2 passing)
â…ą Check(user=user:anne,relation=can_share,object=folder:1): expected=false, got=true
---
# Test Summary #
Tests 1/2 passing
Checks 3/4 passing
ListObjects 3/3 passing
fga model transform
--file
: File containing the authorization model--input-format
: Authorization model input format. Can be "fga", "json", or "modular". Defaults to the file extension if provided (optional)--output-format
: Authorization model output format. Can be "fga" or "json". If not specified, it will default tofga
if the input format isjson
and tojson
otherwise (optional)
fga model transform --file model.json
model
schema 1.1
type user
type document
relations
define can_view: [user]
tuple
Description | command | parameters | example |
---|---|---|---|
Write Relationship Tuples | write |
--store-id , --model-id |
fga tuple write user:anne can_view document:roadmap --store-id=01H0H015178Y2V4CX10C2KGHF4 |
Delete Relationship Tuples | delete |
--store-id , --model-id |
fga tuple delete user:anne can_view document:roadmap --store-id=01H0H015178Y2V4CX10C2KGHF4 |
Read Relationship Tuples | read |
--store-id , --model-id |
fga tuple read --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1 |
Read Relationship Tuple Changes (Watch) | changes |
--store-id , --type , --continuation-token , |
fga tuple changes --store-id=01H0H015178Y2V4CX10C2KGHF4 --type=document --continuation-token=M3w= |
Import Relationship Tuples | import |
--store-id , --model-id , --file |
fga tuple import --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1 --file tuples.json |
fga tuple write --store-id=
<user>
: User<relation>
: Relation<object>
: Object--condition-name
: Condition name (optional)--condition-context
: Condition context (optional)--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--file
: Specifies the file name,json
,yaml
andcsv
files are supported--max-tuples-per-write
: Max tuples to send in a single write (optional, default=1)--max-parallel-requests
: Max requests to send in parallel (optional, default=4)
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap --condition-name inOffice --condition-context '{"office_ip":"10.0.1.10"}'
{
"successful": [
{
"object":"document:roadmap",
"relation":"writer",
"user":"user:annie"
}
],
}
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.json
If using a csv
file, the format should be:
user_type,user_id,user_relation,relation,object_type,object_id,condition_name,condition_context
folder,product,,parent,folder,product-2021,inOfficeIP,"{""ip_addr"":""10.0.0.1""}"
If using a yaml
file, the format should be:
- user: folder:5
relation: parent
object: folder:product-2021
- user: folder:product-2021
relation: parent
object: folder:product-2021Q1
If using a json
file, the format should be:
[
{
"user": "user:anne",
"relation": "owner",
"object": "folder:product"
},
{
"user": "folder:product",
"relation": "parent",
"object": "folder:product-2021"
},
{
"user": "user:beth",
"relation": "viewer",
"object": "folder:product-2021"
}
]
{
"successful": [
{
"object":"document:roadmap",
"relation":"writer",
"user":"user:annie"
}
],
"failed": [
{
"tuple_key": {
"object":"document:roadmap",
"relation":"writer",
"user":"carl"
},
"reason":"Write validation error ..."
}
]
}
fga tuple delete --store-id=
<user>
: User<relation>
: Relation<object>
: Object--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--file
: Specifies the file name,yaml
andjson
files are supported--max-tuples-per-write
: Max tuples to send in a single write (optional, default=1)--max-parallel-requests
: Max requests to send in parallel (optional, default=4)
fga tuple delete --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap
{}
fga tuple delete --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.json
{
"successful": [
{
"object":"document:roadmap",
"relation":"writer",
"user":"user:annie"
}
],
"failed": [
{
"tuple_key": {
"object":"document:roadmap",
"relation":"writer",
"user":"carl"
},
"reason":"Write validation error ..."
}
]
}
If you want to delete all the tuples in a store, you can use the following code:
fga tuple read --output-format=simple-json --max-pages=0 > tuples.json
fga tuple delete --file tuples.json
fga tuple read [--user=] [--relation=] [--object=] --store-id=
--store-id
: Specifies the store id--user
: User--relation
: Relation--object
: Object--max-pages
: Max number of pages to get. (default 20)--output-format
: Can becsv
,yaml
,json
orsimple-json
. Usesimple-json
for a simpler json format that can be piped to the write and delete commands
fga tuple read --store-id=01H0H015178Y2V4CX10C2KGHF4 --user user:anne --relation can_view --object document:roadmap
{
"tuples": [
{
"key": {
"object": "document:roadmap",
"relation": "can_view",
"user": "user:anne"
},
"timestamp": "2023-07-06T15:12:55.080666875Z"
}
]
}
[
{
"object": "document:roadmap",
"relation": "can_view",
"user": "user:anne"
}
]
If you want to transform this output in a way that can be then imported using the fga tuple import
you can run
fga tuple read --output-format=simple-json --max-pages 0 > tuples.json
fga tuple import --file tuples.json
fga tuple changes --type --store-id=
--store-id
: Specifies the store id--type
: Restrict to a specific type (optional)--max-pages
: Max number of pages to retrieve (default: 20)--continuation-token
: Continuation token to start changes from
fga tuple changes --store-id=01H0H015178Y2V4CX10C2KGHF4 --type=document --continuation-token=M3w=
{
"changes": [
{
"operation": "TUPLE_OPERATION_WRITE",
"timestamp": "2023-07-06T15:12:40.294950382Z",
"tuple_key": {
"object": "document:roadmap",
"relation": "can_view",
"user": "user:anne"
}
}
],
"continuation_token":"NHw="
}
fga tuple import --store-id= [--model-id=] --file [--max-tuples-per-write=] [--max-parallel-requests=]
--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--file
: Specifies the file name,yaml
andjson
files are supported--max-tuples-per-write
: Max tuples to send in a single write (optional, default=1)--max-parallel-requests
: Max requests to send in parallel (optional, default=4)
File format should be: In YAML:
- user: user:anne
relation: can_view
object: document:roadmap
- user: user:beth
relation: can_view
object: document:roadmap
In JSON:
[{
"user": "user:anne",
"relation": "can_view",
"object": "document:roadmap"
}, {
"user": "user:beth",
"relation": "can_view",
"object": "document:roadmap"
}]
fga tuple import --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.json
{
"successful": [
{
"object":"document:roadmap",
"relation":"writer",
"user":"user:annie"
}
],
"failed": [
{
"tuple_key": {
"object":"document:roadmap",
"relation":"writer",
"user":"carl"
},
"reason":"Write validation error ..."
}
]
}
query
Description | command | parameters | example |
---|---|---|---|
Check | check |
--store-id , --model-id |
fga query check --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap |
List Objects | list-objects |
--store-id , --model-id |
fga query list-objects --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document |
List Relations | list-relations |
--store-id , --model-id |
fga query list-relations --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne document |
Expand | expand |
--store-id , --model-id |
fga query expand --store-id=01H0H015178Y2V4CX10C2KGHF4 can_view document:roadmap |
fga query check [--condition] [--contextual-tuple "<user> <relation> <object>"]* --store-id= [--model-id=]
--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--contextual-tuple
: Contextual tuples (optional)--context
: Condition context (optional)--consistency
: Consistency preference (optional)
fga query check --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap --contextual-tuple "user:anne can_view folder:product" --contextual-tuple "folder:product parent document:roadmap"
fga query check --store-id="01H4P8Z95KTXXEP6Z03T75Q984" user:anne can_view document:roadmap --context '{"ip_address":"127.0.0.1"}' --consistency="HIGHER_CONSISTENCY"
{
"allowed": true,
}
fga query list-objects <object_type> [--contextual-tuple " "]* --store-id= [--model-id=]
--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--contextual-tuple
: Contextual tuples (optional) (can be multiple)--context
: Condition context (optional)--consistency
: Consistency preference (optional)
fga query list-objects --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document --contextual-tuple "user:anne can_view folder:product" --contextual-tuple "folder:product parent document:roadmap"
fga query list-objects --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document --context '{"ip_address":"127.0.0.1"} --consistency="HIGHER_CONSISTENCY"
{
"objects": [
"document:roadmap",
"document:budget"
],
}
fga query list-relations [--relation ]* [--contextual-tuple " "]* --store-id= [--model-id=]
--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--contextual-tuple
: Contextual tuples (optional) (can be multiple)--context
: Condition context (optional)--consistency
: Consistency preference (optional)
fga query list-relations --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne document:roadmap --relation can_view
fga query list-relations --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne document:roadmap --relation can_view --contextual-tuple "user:anne can_view folder:product"
fga query list-relations --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne document:roadmap --relation can_view --context '{"ip_address":"127.0.0.1"} --consistency="HIGHER_CONSISTENCY"
{
"relations": [
"can_view"
],
}
fga query expand --store-id= [--model-id=]
--store-id
: Specifies the store id--model-id
: Specifies the model id to target (optional)--consistency
: Consistency preference (optional)
fga query expand --store-id=01H0H015178Y2V4CX10C2KGHF4 can_view document:roadmap
{
"tree": {
"root": {
"name": "repo:openfga/openfga#reader",
"union": {
"nodes": [{
"leaf": {
"users": {
"users": ["user:anne"]
}
},
"name": "repo:openfga/openfga#reader"
}]
}
}
}
}
fga query list-users --object --relation --user-filter [--contextual-tuple " "]* --store-id= [--model-id=]
--store-id
: Specifies the store id--object
: Specifies the object to list users for--relation
: Specifies the relation to search on--user-filter
: Specifies the type or userset to filter with--model-id
: Specifies the model id to target (optional)--contextual-tuple
: Contextual tuples (optional) (can be multiple)--context
: Condition context (optional)--consistency
: Consistency preference (optional)
fga query list-users --store-id=01H0H015178Y2V4CX10C2KGHF4 --object document:roadmap --relation can_view --user-filter user
fga query list-users --store-id=01H0H015178Y2V4CX10C2KGHF4 --object document:roadmap --relation can_view --user-filter user --contextual-tuple "user:anne can_view folder:product"
fga query list-users --store-id=01H0H015178Y2V4CX10C2KGHF4 --object document:roadmap --relation can_view --user-filter group#member --context '{"ip_address":"127.0.0.1"} --consistency="HIGHER_CONSISTENCY"
{
{
"users": [
{
"object": {
"type": "user",
"id": "anne"
}
}
]
}
}
See CONTRIBUTING.
This project is licensed under the Apache-2.0 license. See the LICENSE file for more info.