OpenFGA: An exellent tool for access control authorization

OpenFGA is a robust authorization solution with fast and flexible processing capabilities. In this article, we uncover OpenFGA and its strengths when being built on the relationship-based access control (ReBAC) model.

Bài viết này có phiên bản Tiếng Việt

OpenFGA poster

ReBAC

In digital security, authorization models play an important role in determining "who" has "what rights" over "which resources". Traditional models such as Role-based Access Control (RBAC) and Attribute-based Access Control (ABAC) have been popular solutions for many years. However, as the complexity and scale of systems increase, they cause a greater need for flexibility and granularity. In this context, a new access authorization called Relationship-Based Access Control (ReBAC) has emerged.

Authorization models

Authorization models

ReBAC is an authorization model that considers the relationship between subjects and objects when determining access rights. Unlike the "attribute" in ABAC or "role" in RBAC, the ReBAC model focuses on the "relationship" of users within an organization, society, hierarchy, in addition to common relationships between users and resources. For example, in the case of granting managers access to the data of their subordinates within their group, or allowing project members to access resources related to the project, ReBAC can provide a flexible access control method that better reflects the relational structure in the real world and helps organizations enforce effective security policies.

Google Zanzibar is a prime example of ReBAC. Zanzibar is a system used by Google to handle access control for hundreds of services and products such as YouTube, Drive, Calendar, Cloud, Maps, etc. With the ability to handle complex access control policies at a global scale, Zanzibar is capable of processing billions of queries per second.

OpenFGA

Being inspired by Zanzibar, OpenFGA is introduced as a powerful yet user-friendly access control model for engineers. OpenFGA provides fast and flexible processing capabilities, making it suitable for projects of any scale.

Fine-Grained Authorization (FGA) refers to the ability to specify access rights to specific resources or objects in the system for each user. Fine-Grained Authorization is particularly suitable for large-scale systems with millions of objects, users, and relationships whose access rights are frequently updated. An example of a system using Fine-Grained Authorization is Google Drive, where access rights often change when sharing documents or folders.

OpenFGA addresses access control checks by considering the relationship between the requesting user and the object. The check relies on the system's authorization model and the relationship tuples available at the time to make decisions.

Some concepts in OpenFGA:

Some concepts in OpenFGA

Some concepts in OpenFGA

  • Store: Each Store contains one or more versions of the Authorization Model and Relationship Tuples. When performing access checks, the StoreID needs to be provided to specify the store containing the model and tuples to be checked. Data in one store cannot be shared with other stores.
  • Authorization model:

OpenFGA introduces two syntaxes for defining authorization models: JSON and DSL. Here is an example of an authorization model defined using OpenFGA DSL:

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

Each authorization model is defined by:

  • Type: Defines a set of similar varieties (e.g., document, repository, user, admin, etc.).

  • Relations: Defines the relationships between users and objects.

    • User-to-object

      type document
            relations
              define viewer: [user:hungtgq1]
      
    • Object-to-object

       type document
              relations
                  define editor: [application:myapp]a
      
    • Userset-to-object

       type document
              relations
                  define editor: [organization:myteam#member]
      
    • Everyone-to-object

       type document
              relations
                  define editor: [user:*]
      
  • Relationship Tuple: A relationship tuple includes the users, relations, and objects stored in OpenFGA. The collection of Relationship Tuples represents the relationships between specific individuals and objects in the system and serves as the basis for OpenFGA's authentication process.

    [
          {
            "user": "user:hungtgq1",
            "relation": "owner",
            "object": "board:myfirstboard",
          },
        ]
    

OpenFGA Playground

OpenFGA provides a Playground to allow users to familiarize themselves with ReBAC and OpenFGA.

With the Playground, you can define an authorization using DSL and copy the corresponding JSON version to use in your source code. Currently, the OpenFGA server only supports models in JSON format. To use DSL, you need to use a parser or convert it through the playground.

OpenFGA Playground

OpenFGA Playground

Users can access the Playground at OpenFGA Playground or use the integrated Playground in a self-deployed server. We will explore the process of installing OpenFGA through the next demo.

Demo

Installing OpenFGA

OpenFGA supports quick and easy installation using Docker and Kubernetes. You can see more information in the Setup OpenFGA guide. In this article, we will use the provided Docker Compose template to install OpenFGA.

After running the Docker Compose as instructed, you can check the result by accessing the /healthz endpoint.

OpenFGA installation result

OpenFGA installation result

Problem Description

The Architecture Diagram Platform is a platform that allows users to create architectural diagrams and share them with other users. The diagrams can be organized into folders. Only the creator of a diagram has permission to edit and share it, while shared users can only view the diagrams.

With the given problem, we can identify the following objects and relationships:

  • User

  • Board

    • Owner
    • Viewer
    • Parent (parent folder)
    • can_view (view permission of the creator and shared users)
    • can_share (share permission of the creator)
    • can_edit (edit permission of the creator)
  • Folder

    • Owner
    • can_create (board create permission of the folder's creator)

The corresponding authorization model is as follows:

Authorization model of Architecture Diagram Platform problem

Authorization model of Architecture Diagram Platform problem

go
model
  schema 1.1
type user
type folder
  relations
    define can_create: owner
    define owner: [user]
type board
  relations
    define can_view: viewer or owner
    define can_share: owner
    define can_edit: owner
    define owner: [user]
    define parent: [folder]
    define viewer: [user]

Implementation

Currently, OpenFGA provides SDKs for Node.js, Go, .NET, and Python. In this demo, we will use Go. To install the Golang SDK, follow the instructions in the Install SDK Client guide.

We will perform the following steps: Create Store → Add Authorization Model → Add Relationship Tuples → Perform access checks.

  • Create Store

To create a Store, we need to create a configuration object containing ApiScheme and ApiHost pointing to the local OpenFGA server as follows:

    configuration, err := openfga.NewConfiguration(openfga.Configuration{
        ApiScheme: os.Getenv("FGA_API_SCHEME"), // "http"
        ApiHost:   os.Getenv("FGA_API_HOST"),   // "localhost:8080"
    })

Then, create the Store using the CreateStore function:

apiClient := openfga.NewAPIClient(configuration)

resp, _, err := apiClient.OpenFgaApi.CreateStore(context.Background()).Body(
    openfga.CreateStoreRequest{
        Name: "Architecture Diagram Platform",
    }).Execute()

When executed successfully, a new Store is created, as shown in the picture below:

Upload image

We will record the id to use as the Store Id (FGA_STORE_ID) in the following steps.

  • Add Authorization Model

To add an Authorization Model, we need to configure the StoreId with the value obtained from the previous step and add it to the configuration object:

StoreId:   os.Getenv("FGA_STORE_ID"),

We will use the playground to convert the model to JSON and use it in the source code as follows:

var writeAuthorizationModelRequestString = "{\"type_definitions\":[{\"type\":\"user\",\"relations\":{}},{\"type\":\"folder\",\"relations\":{\"can_create\":{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"can_create\":{\"directly_related_user_types\":[]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}},{\"type\":\"board\",\"relations\":{\"can_view\":{\"union\":{\"child\":[{\"computedUserset\":{\"object\":\"\",\"relation\":\"viewer\"}},{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}}]}},\"can_share\":{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}},\"can_edit\":{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}},\"owner\":{\"this\":{}},\"parent\":{\"this\":{}},\"viewer\":{\"this\":{}}},\"metadata\":{\"relations\":{\"can_view\":{\"directly_related_user_types\":[]},\"can_share\":{\"directly_related_user_types\":[]},\"can_edit\":{\"directly_related_user_types\":[]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"parent\":{\"directly_related_user_types\":[{\"type\":\"folder\"}]},\"viewer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}],\"schema_version\":\"1.1\"}"
    var body openfga.WriteAuthorizationModelRequest
    if err := json.Unmarshal([]byte(writeAuthorizationModelRequestString), &body); err != nil {
        // ...
    }
    data, response, err := apiClient.OpenFgaApi.WriteAuthorizationModel(context.Background()).Body(body).Execute()
...

When executed successfully, a new Authorization Model is created as shown in the picture below:

Upload image

We will record the authorization_model_id to use as the Authorization Model Id (FGA_AUTH_MODEL_ID) in the following steps.

  • Add Relationship Tuples

To add Relationship Tuples, we use the following code:

body := openfga.WriteRequest{
Writes: &openfga.TupleKeys{
    TupleKeys: []openfga.TupleKey{
        {
            User:     openfga.PtrString("user:hungtgq1"),
            Relation: openfga.PtrString("owner"),
            Object:   openfga.PtrString("folder:myfirstfolder"),
        },
    },
},
AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_AUTH_MODEL_ID"))}

_, response, err := apiClient.OpenFgaApi.Write(context.Background()).Body(body).Execute()

The result is as shown in the picture below:

Upload image

In this step, we will add the following tuples:

  • User "hungtgq1" creates a folder "myfirstfolder"
  • User "hungtgq1" creates a board "myfirstboard" inside "myfirstfolder"
  • User "hungtgq1" shares the board with user "manlm1"

After adding the tuples, we have the following data in the OpenFGA database like this:

Upload image

  • Perform Access Checks

To perform access checks, we use the following code:

body := openfga.CheckRequest{
    AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_AUTH_MODEL_ID")),
    TupleKey: openfga.TupleKey{
        User:     openfga.PtrString("user:hungtgq1"),
        Relation: openfga.PtrString("can_view"),
        Object:   openfga.PtrString("board:myfirstboard"),
    },
}
data, _, err := apiClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()

// Print for demo
fmt.Println(*data.Allowed)

We will get a result of either True or False. In the above code, the user "hungtgq1" is the owner of the board "myfirstboard," so he has the "can_view" permission as shown in the picture below:

Upload image

Similarly, when performing the following checks:

  • User "manlm1" can_view permission: True
  • User "manlm1" can_edit permission: False
  • User "hungtgq1" can_share permission: True

Considerations when using OpenFGA

OpenFGA provides some best practices for deploying it in a production environment. Some notable points are as follows:

Regarding implementation:

  • Avoid storing sensitive data or personal information in Relationship Tuples.
  • Always declare the Authorization Model ID in requests to improve OpenFGA's processing speed and ensure system consistency during model changes (OpenFGA defaults to finding the latest model if the Model ID is not specified).

Regarding deployment:

  • Configure Authentication: Besides no-authentication (default), you can configure OpenFGA to use authentication methods such as:

    • Pre-shared Key Authentication: Use a secret key and set the "Authorization" header in requests to OpenFGA.
    • OIDC: Configure the issuer and audience from the provider.

Regarding the database:

  • Use a separate database that is not shared with other applications to allow independent scaling and avoid conflicts with other application databases.
  • The database should be set up and managed using the "openfga migrate" tool to ensure necessary indexes for any version of OpenFGA

Conclusion

OpenFGA has emerged as a powerful access control solution, driving the concept of Relationship-Based Access Control. Deploying OpenFGA allows systems to scale well with low latency, making it a reliable choice for complex systems. OpenFGA has notable strengths in managing relationships between objects to provide flexible and adaptable access control in modern applications.

Atekco - Home for Authentic Technical Consultants