Ruby SDK for OpenFGA
This is community-driven Ruby SDK for OpenFGA. It provides a wrapper around the OpenFGA API definition.
Table of Contents
- About OpenFGA
- Resources
- Installation
- Getting Started
- Initializing the API Client
- Custom Headers
- Get your Store ID
- Calling the API
- Stores
- List All Stores
- Create a Store
- Get a Store
- Delete a Store
- Authorization Models
- Read Authorization Models
- Write Authorization Model
- Read a Single Authorization Model
- Read the Latest Authorization Model
- Relationship Tuples
- Read Relationship Tuple Changes (Watch)
- Read Relationship Tuples
- Write (Create and Delete) Relationship Tuples
- Relationship Queries
- Check
- Batch Check
- Client Batch Check
- Expand
- List Objects
- List Relations
- List Users
- Assertions
- Read Assertions
- Write Assertions
- Stores
- Retries
- API Endpoints
- Models
- Contributing
- Issues
- Pull Requests
- License
About
OpenFGA is an open source Fine-Grained Authorization solution inspired by Google's Zanzibar paper. It was created by the FGA team at Auth0 based on Auth0 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.
Resources
- OpenFGA Documentation
- OpenFGA API Documentation
- X
- OpenFGA Community
- Zanzibar Academy
- Google's Zanzibar Paper (2019)
Installation
To install:
gem install openfga
Alternatively, you can add it to your Gemfile:
gem 'openfga'
Then run bundle install to install the gem.
To use in your code, require the gem and create the configuration:
require 'openfga'
sdk_config = {
api_url: 'http://localhost:8080'
}
Getting Started
Initializing the API Client
Learn how to initialize your SDK
We strongly recommend you initialize the SdkClient only once and then re-use it
throughout your app, otherwise you will incur the cost of having to re-initialize
multiple times or at every request, the cost of reduced connection pooling and
re-use, and would be particularly costly in the client credentials flow,
as that flow will be performed on every request.
No Credentials
require 'openfga'
def main
# Initialize the fga_client
fga_client = OpenFga::SdkClient.new(
api_url: ENV['FGA_API_URL'], # required
store_id: ENV['FGA_STORE_ID'], # optional, not needed when calling `create_store` or `list_stores`
authorization_model_id: ENV['FGA_MODEL_ID'] # optional, can be overridden per request
)
api_response = fga_client.read_authorization_models()
return api_response
endAPI Token
require 'openfga'
def main
# Initialize the fga_client
fga_client = OpenFga::SdkClient.new(
api_url: ENV['FGA_API_URL'], # required
store_id: ENV['FGA_STORE_ID'], # optional, not needed when calling `create_store` or `list_stores`
authorization_model_id: ENV['FGA_MODEL_ID'], # optional, can be overridden per request
credentials: {
method: :api_token,
api_token: ENV['FGA_API_TOKEN']
}
)
api_response = fga_client.read_authorization_models()
return api_response
endClient Credentials
require 'openfga'
def main
# Initialize the fga_client
fga_client = OpenFga::SdkClient.new(
api_url: ENV['FGA_API_URL'], # required
store_id: ENV['FGA_STORE_ID'], # optional, not needed when calling `create_store` or `list_stores`
authorization_model_id: ENV['FGA_MODEL_ID'], # optional, can be overridden per request
credentials: {
method: :client_credentials,
api_token_issuer: ENV['FGA_API_TOKEN_ISSUER'],
api_audience: ENV['FGA_API_AUDIENCE'],
client_id: ENV['FGA_CLIENT_ID'],
client_secret: ENV['FGA_CLIENT_SECRET']
}
)
api_response = fga_client.read_authorization_models()
return api_response
endCustom Headers
Per-Request Headers
You can send custom headers on a per-request basis by using the opts parameter. Per-request headers will override any default headers with the same name.
require 'openfga'
def main
# Initialize the fga_client
fga_client = OpenFga::SdkClient.new(
api_url: ENV['FGA_API_URL'],
store_id: ENV['FGA_STORE_ID'],
authorization_model_id: ENV['FGA_MODEL_ID']
)
# Add custom headers to a specific request
result = fga_client.check(
user: "user:anne",
relation: "viewer",
object: "document:roadmap",
opts: {
header_params: {
"X-Request-ID": "123e4567-e89b-12d3-a456-426614174000",
"X-Custom-Header": "custom-value"
}
}
)
return result
endGet your Store ID
You need your store id to call the OpenFGA API (unless it is to call the CreateStore or ListStores methods).
If your server is configured with authentication enabled, you also need to have your credentials ready.
Calling the API
OpenFga Client Methods
Stores
List Stores
Get a paginated list of stores.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
options = { page_size: 25, continuation_token: "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" }
response = fga_client.list_stores(options)
# response = ListStoresResponse(...)
# response.stores = [Store(id: "01FQH7V8BEG3GPQW93KTRFR8JB", name: "FGA Demo Store", created_at: "2022-01-01T00:00:00.000Z", updated_at: "2022-01-01T00:00:00.000Z")]Create Store
Create and initialize a store.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
name: "FGA Demo Store"
}
response = fga_client.create_store(body)
# response.id = "01FQH7V8BEG3GPQW93KTRFR8JB"Get Store
Get information about the current store.
Requires a client initialized with a store_id
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
response = fga_client.get_store()
# response = OpenFga::GetStoreResponse(id: "01FQH7V8BEG3GPQW93KTRFR8JB", name: "FGA Demo Store", created_at: "2022-01-01T00:00:00.000Z", updated_at: "2022-01-01T00:00:00.000Z")Delete Store
Delete a store.
Requires a client initialized with a store_id
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
response = fga_client.delete_store()
# nil Authorization Models
Read Authorization Models
Read all authorization models in the store.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
options = { page_size: 25, continuation_token: "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" }
response = fga_client.read_authorization_models(options)
# response.authorization_models = [OpenFga::AuthorizationModel(id: '01GXSA8YR785C4FYS3C0RTG7B1', schema_version: '1.1', type_definitions: type_definitions[...]), OpenFga::AuthorizationModel(id: '01GXSBM5PVYHCJNRNKXMB4QZTW', schema_version: '1.1', type_definitions: type_definitions[...])]Write Authorization Model
Create a new authorization model.
Note: To learn how to build your authorization model, check the Docs at https://openfga.dev/docs.
Learn more about the OpenFGA configuration language.
You can use the OpenFGA Syntax Transformer to convert between the friendly DSL and the JSON authorization model.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
type_definitions = [
{
type: "user"
},
{
type: "document",
relations: {
writer: { this: {} },
viewer: {
union: {
child: [
{ this: {} },
{
computed_userset: {
object: "",
relation: "writer"
}
}
]
}
}
},
metadata: {
relations: {
writer: {
directly_related_user_types: [
{ type: "user" },
{ type: "user", condition: "ViewCountLessThan200" }
]
},
viewer: {
directly_related_user_types: [
{ type: "user" }
]
}
}
}
}
]
conditions = {
ViewCountLessThan200: {
name: "ViewCountLessThan200",
expression: "ViewCount < 200",
parameters: {
ViewCount: {
type_name: "TYPE_NAME_INT"
},
Type: {
type_name: "TYPE_NAME_STRING"
},
Name: {
type_name: "TYPE_NAME_STRING"
}
}
}
}
body = {
schema_version: "1.1",
type_definitions: type_definitions,
conditions: conditions
}
response = fga_client.write_authorization_model(body)
# response.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1"Read a Single Authorization Model
Read a particular authorization model.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
options = {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
response = fga_client.read_authorization_model(options)
# response.authorization_model = OpenFga::AuthorizationModel(id: '01GXSA8YR785C4FYS3C0RTG7B1', schema_version: '1.1', type_definitions: type_definitions[...])Relationship Tuples
Read Relationship Tuple Changes (Watch)
Reads the list of historical relationship tuple writes and deletes.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
options = {
page_size: 25,
continuation_token: "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="
}
response = fga_client.read_changes(options)
# response.continuation_token = ...
# response.changes = [TupleChange(tuple_key: TupleKey(object: "...", relation: "...", user: "..."), operation: TupleOperation("TUPLE_OPERATION_WRITE"), timestamp: ...)]Read Relationship Tuples
Reads the relationship tuples stored in the database. It does not evaluate nor exclude invalid tuples according to the authorization model.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
# Find if a relationship tuple stating that a certain user is a viewer of certain document
body = {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
}
response = fga_client.read(body)
# response = ReadResponse(tuples: [Tuple(key: TupleKey(user: "...", relation: "...", object: "..."), timestamp: ...)])# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
# Find all relationship tuples where a certain user has a relationship as any relation to a certain document
body = {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
}
response = fga_client.read(body)
# response = ReadResponse(tuples: [Tuple(key: TupleKey(user: "...", relation: "...", object: "..."), timestamp: ...)])# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
# Find all relationship tuples where a certain user is a viewer of any document
body = {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
object: "document:"
}
response = fga_client.read(body)
# response = ReadResponse(tuples: [Tuple(key: TupleKey(user: "...", relation: "...", object: "..."), timestamp: ...)])# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
# Find all relationship tuples where any user has a relationship as any relation with a particular document
body = {
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
}
response = fga_client.read(body)
# response = ReadResponse(tuples: [Tuple(key: TupleKey(user: "...", relation: "...", object: "..."), timestamp: ...)])# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
# Read all stored relationship tuples
body = {}
response = fga_client.read(body)
# response = ReadResponse(tuples: [Tuple(key: TupleKey(user: "...", relation: "...", object: "..."), timestamp: ...)])Write (Create and Delete) Relationship Tuples
Create and/or delete relationship tuples to update the system state.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
writes: {
tuple_keys: [
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
object: "doc:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
},
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
object: "doc:0192ab2d-d36e-7cb3-a4a8-5d1d67a300c5"
}
]
},
deletes: {
tuple_keys: [
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "writer",
object: "doc:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
}
]
},
opts: {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
}
response = fga_client.write(body)Relationship Queries
Check
Check if a user has a particular relation with an object.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "writer",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
opts: {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1",
context: {
ViewCount: 100
}
}
}
response = fga_client.check(body)
# response.allowed = trueBatch Check
Performs multiple relationship checks in a single batch request.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
checks: [
{
tuple_key: {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
},
correlation_id: "check-1",
contextual_tuples: {
tuple_keys: [
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "editor",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
}
]
},
context: {
ViewCount: 100
}
},
{
tuple_key: {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "admin",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
},
correlation_id: "check-2"
}
],
opts: {
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1",
max_parallel_requests: 10,
max_batch_size: 50
}
}
response = fga_client.batch_check(body)
# response.result contains the results mapped by correlation_idExpand
Expands the relationships in userset tree format.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
relation: "viewer",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
opts: {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
}
response = fga_client.expand(body)
# response = ExpandResponse(tree: UsersetTree(root: Node(name: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a#viewer", leaf: Leaf(users: Users(users: ["user:81684243-9356-4421-8fbf-a4f8d36aa31b", "user:f52a4f7a-054d-47ff-bb6e-3ac81269988f"])))))List Objects
List the objects of a particular type a user has access to.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
type: "document",
contextual_tuples: {
tuple_keys: [
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "writer",
object: "document:0192ab2d-d36e-7cb3-a4a8-5d1d67a300c5"
}
]
},
context: {
ViewCount: 100
},
opts: {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
}
response = fga_client.list_objects(body)
# response.objects = ["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]List Users
List the users who have a certain relation to a particular type.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
relation: "can_read",
object: "document:2021-budget",
user_filters: [
{ type: "user" },
{ type: "team", relation: "member" }
],
contextual_tuples: [
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "editor",
object: "folder:product"
},
{
user: "folder:product",
relation: "parent",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"
}
],
context: {},
opts: {
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
}
response = fga_client.list_users(body)
# response.users = [{object: {type: "user", id: "81684243-9356-4421-8fbf-a4f8d36aa31b"}}, {userset: {type: "user"}}, ...]Assertions
Read Assertions
Read assertions for a particular authorization model.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
options = {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
response = fga_client.read_assertions(options)Write Assertions
Update the assertions for a particular authorization model.
# Initialize the fga_client
# fga_client = OpenFga::SdkClient.new(configuration)
body = {
assertions: [
{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
expectation: true
}
],
opts: {
# You can rely on the model id set in the configuration or override it for this specific request
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"
}
}
response = fga_client.write_assertions(body)Retries
API Endpoints
| Class | Method | HTTP request | Description |
|---|---|---|---|
| OpenFgaApi | batch_check | POST /stores/{store_id}/batch-check | Send a list of `check` operations in a single request |
| OpenFgaApi | check | POST /stores/{store_id}/check | Check whether a user is authorized to access an object |
| OpenFgaApi | create_store | POST /stores | Create a store |
| OpenFgaApi | delete_store | DELETE /stores/{store_id} | Delete a store |
| OpenFgaApi | expand | POST /stores/{store_id}/expand | Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship |
| OpenFgaApi | get_store | GET /stores/{store_id} | Get a store |
| OpenFgaApi | list_objects | POST /stores/{store_id}/list-objects | List all objects of the given type that the user has a relation with |
| OpenFgaApi | list_stores | GET /stores | List all stores |
| OpenFgaApi | list_users | POST /stores/{store_id}/list-users | List the users matching the provided filter who have a certain relation to a particular type. |
| OpenFgaApi | read | POST /stores/{store_id}/read | Get tuples from the store that matches a query, without following userset rewrite rules |
| OpenFgaApi | read_assertions | GET /stores/{store_id}/assertions/{authorization_model_id} | Read assertions for an authorization model ID |
| OpenFgaApi | read_authorization_model | GET /stores/{store_id}/authorization-models/{id} | Return a particular version of an authorization model |
| OpenFgaApi | read_authorization_models | GET /stores/{store_id}/authorization-models | Return all the authorization models for a particular store |
| OpenFgaApi | read_changes | GET /stores/{store_id}/changes | Return a list of all the tuple changes |
| OpenFgaApi | write | POST /stores/{store_id}/write | Add or delete tuples from the store |
| OpenFgaApi | write_assertions | PUT /stores/{store_id}/assertions/{authorization_model_id} | Upsert assertions for an authorization model ID |
| OpenFgaApi | write_authorization_model | POST /stores/{store_id}/authorization-models | Create a new authorization model |
Models
- AbortedMessageResponse
- Any
- Assertion
- AssertionTupleKey
- AuthErrorCode
- AuthorizationModel
- BatchCheckItem
- BatchCheckRequest
- BatchCheckResponse
- BatchCheckSingleResult
- CheckError
- CheckRequest
- CheckRequestTupleKey
- CheckResponse
- Computed
- Condition
- ConditionMetadata
- ConditionParamTypeRef
- ConsistencyPreference
- ContextualTupleKeys
- CreateStoreRequest
- CreateStoreResponse
- Difference
- ErrorCode
- ExpandRequest
- ExpandRequestTupleKey
- ExpandResponse
- FgaObject
- ForbiddenResponse
- GetStoreResponse
- InternalErrorCode
- InternalErrorMessageResponse
- Leaf
- ListObjectsRequest
- ListObjectsResponse
- ListStoresResponse
- ListUsersRequest
- ListUsersResponse
- Metadata
- Node
- Nodes
- NotFoundErrorCode
- NullValue
- ObjectRelation
- PathUnknownErrorMessageResponse
- ReadAssertionsResponse
- ReadAuthorizationModelResponse
- ReadAuthorizationModelsResponse
- ReadChangesResponse
- ReadRequest
- ReadRequestTupleKey
- ReadResponse
- RelationMetadata
- RelationReference
- RelationshipCondition
- SourceInfo
- Status
- Store
- Tuple
- TupleChange
- TupleKey
- TupleKeyWithoutCondition
- TupleOperation
- TupleToUserset
- TypeDefinition
- TypeName
- TypedWildcard
- UnauthenticatedResponse
- UnprocessableContentErrorCode
- UnprocessableContentMessageResponse
- User
- UserTypeFilter
- Users
- Userset
- UsersetTree
- UsersetTreeDifference
- UsersetTreeTupleToUserset
- UsersetUser
- Usersets
- ValidationErrorMessageResponse
- WriteAssertionsRequest
- WriteAuthorizationModelRequest
- WriteAuthorizationModelResponse
- WriteRequest
- WriteRequestDeletes
- WriteRequestWrites
Contributing
See CONTRIBUTING for details.
Author
License
This project is licensed under the Apache-2.0 license. See the LICENSE file for more info.