Define An FGA Authorization Model

Published on September 29, 2023
Learning GoalLearn how to build an authorization model in OpenFGA to add fine-grained access control to your service.

In this lab, you will:

  • Learn about OpenFGA and Okta FGA and how they differ from Role-Based Access Control (RBAC) systems
  • Understand how to model your service's permission system in OpenFGA
  • Test your FGA model to ensure that it is working as expected

Fine Grained Authorization, OpenFGA and Okta FGA

OpenFGA is an open source solution to Fine-Grained Authorization that applies the concept of ReBAC. It was created by the Okta FGA team and was inspired by Zanzibar. It was designed for reliability and low latency at a high scale.

OpenFGA offers HTTP and GRPC APIs and has SDKs for several programming languages, including JavaScript, Go, .NET, Python, Java with Rust and Ruby coming soon. It also offers a CLI and a VS Code extension to help with building an authorization model.

Okta FGA is a fully managed fine grained authorization SaaS provided by Okta. It offers everything from OpenFGA in addition to high-availability, multi-region active-active support, caching and automatic routing to the nearest region to minimize latency and a management dashboard to allow collaboration on the authorization model and issuing credentials. Okta FGA has backups, replication and around the clock on-call.

OpenFGA is an authorization tool, and does not deal with authentication, for which you would have to use a different tool or service, such as Auth0 by Okta.

Authentication (or AuthN) is a process that ensures a user's identity. Authorization (or AuthZ) means determining if a user can perform a certain action on a particular resource.

For example, when logging in to Twitter, the process in which Twitter verifies the user's username and password are correct is Authentication. The process of checking if that user can view or reply to a certain Tweet is Authorization.

Why Fine-Grained Authorization?

Fine-Grained Authorization is being able to grant individual users access to specific objects or resources in a system. FGA at scale means being able to do so in a system that can have millions of objects, users and relations, and has a high change rate - objects are regularly added and access permissions are constantly being updated. A good example of this is Google Drive, where access can be granted either to documents, or folders; it can be granted to users individually or as a group. Access regularly changes as new documents are created and shared with specific users, whether inside the same company or outside.

In Role-Based Access Control (RBAC), permissions are assigned to users based on their role in a system (e.g. a user would need an editor role to edit posts). This does not scale well when users can have different roles per individual item. In Google Drive for example, a user may be an owner of a few folders and documents, a member of an organization which grants them view access to even more resources, and a collaborator on items shared with them from people outside the organization.

OpenFGA allows modeling cases like those of Google Drive, GitHub and more where the more traditional Role-Based Access Controls (RBAC) mechanisms fall short.

Intro to OpenFGA Concepts

In order for OpenFGA to reach a decision on whether a user is authorized to access a resource, it needs two things:

  1. A static Authorization Model that defines the object types and the possible relations that another object or group of objects can have with them. The Authorization Model is rarely modified, usually only when you are introducing new products or features.

  2. The current system state, which is represented by a series of relationship tuples, each containing:

    You will be creating and deleting these tuples as objects are being shared across your services.

OpenFGA has a concept of a "Store" to encapsulate authorization models and tuples so that it can service multiple use-cases and models.

OpenFGA would then use the authorization model and the relationship tuples to traverse the relationship graph and find a path between the user and the object. Then it can answer, either: "Yes, the user is authorized to access this resource", if it can find a path, or "No the user is not authorized".

You would typically integrate with OpenFGA by using one of the supported SDKs or issuing a POST request to the Check API endpoint with a user, a relation and an object, and it would return { "allowed": "true" } if the user is authorized, and false if they are not.

Lab Setup

For this lab, you will be working with the FGA Playground to get started quickly as it does not require any setup or account. Alternatively you may choose to follow on:

  • The Okta FGA Dashboard: This is part of the managed Okta FGA service which allows you to create an account, collaborate with team members and issue credentials to the FGA API. You can get started for free and all you need is a modern browser. Checkout Introduction to the Okta FGA Dashboard for instructions.
  • OpenFGA with the FGA Playground: This is the same as the Okta FGA Playground, but running against your local OpenFGA installation. See Setup OpenFGA for more info.

To get started, you need to create a new FGA store. Head to https://play.fga.dev (feel free to follow or skip the tutorial there), then:

  1. Click the NEW STORE button on the top right hand corner of the screen.
Image showing the FGA Playground New Store button
  1. Type in a store name in the text box. Note that the store name may only contain letters, numbers and '-'.
Image showing the FGA Playground new store prompt
  1. Click CREATE button.
Image showing the FGA Playground new store create button

Defining Your Authorization Model

One of the first steps to take when adopting OpenFGA is to draft an Authorization Model that represents your system's permission.

Collecting the Requirements

Before you can start writing your authorization model, you first need to understand how your permission system currently works, and come up with example scenarios that would ensure it is working as you expect. If you were to start building a model for your own use case, the Getting Started with Modeling Guide would be very helpful to get your on your way.

For the sake of this lab, you will take a use case that is a simplified version of Google Drive.

Requirements

In this simplified Google Drive use-case, let's assume we have:

  • Users
  • Folders:
    • can be created by users
    • can be shared with other users with the following roles: editor, viewer
    • can contain other folders and documents, in which case someone who has editor or viewer permission on the parent folder automatically gets it on the child folder
    • a folder's creator can delete and share that folder as well as edit and view both the folder and all its children
    • a folder's editors can both edit and view that folder and its children
    • a folder's viewers can view that folder and its children
  • Documents:
    • can be created by users
    • can be shared with other users with the following roles: editor, viewer
    • a document's creator can perform all actions on that document: delete, share, edit and view
    • a document's editors can both edit and view that document
    • a document's viewers can view that document

Example Scenarios

By the end of this lab, in order to validate that our model in the end is correct, we will use the following scenarios:

  • Anne has created a folder called "Product", inside which she created another folder called "Planning"
  • Anne has shared the "Planning" folder with Beth, making Beth an editor on that folder
  • Beth has created a document called "Roadmap" and placed it in the "Planning" folder

You expect that:

  • Anne can share, delete, edit and view both the "Product" and the "Planning" folders
  • Anne can edit and view the "Roadmap" document
  • Beth can edit and view the "Planning" folder, and can perform no actions on the "Product" folder
  • Beth can share, delete, edit and view the "Roadmap" document

Defining Your Model: Step 1 - Adding the Types

Extracting the Types from the Requirements

The types in OpenFGA represent the types of objects in your system. A good way to easily grok the types you should have is to take a look at your requirements for anything that sounds like actor or something that is acted on. In our requirements above, you can notice phrases such as:

  • folders can be owned by users (actor type = user, acted upon type = folder)
  • a document's viewers can view that document (actor type = document's viewers (users), acted upon type = document)

So the types to be represented are:

  • user
  • folder
  • document

Adding the Types

All OpenFGA models start with the following header (schema 1.1 is currently the only supported schema):

model
schema 1.1

Then you can add the types for the model to become:

model
schema 1.1
type user
type folder
type document

Before continuing with defining the model, you will learn how to save it using the FGA Playground

  1. On the FGA Playground, paste the Authorization Model above in the types panel located on the upper left part of the screen (replacing existing data).
Image showing the FGA Playground types panel
  1. After the changes are made, click SAVE.
Image showing the FGA Playground types save button
  1. After the authorization model is saved, the Types Previewer will be updated with the new authorization model preview.
Image showing the FGA Playground type preview
  1. After the authorization model is saved, the SAVE button is no longer active.
Image showing the FGA Playground type save button after save
Playground will only save if there are no syntax errors. Syntax errors will be highlighted in red. Hovering the mouse over the error will provide additional details.
You can also choose to directly call the API, use the SDKs or use the FGA CLI to write your models.

Defining Your Model: Step 2 - Specifying the Relations

The next step is to list the relations needed for each type. Relations include: Roles users can have on objects, permissions users can have on objects, relations between objects (such as object A is a parent of object B). The relations are in reference to the object, so fall under the object type being acted on.

Looking at the requirements, notice how "user" is always the actor, never the object being acted on, this is OK. It means it's a type that has no relation.

Now take a look at the "folder" type. Go back to the requirements and list all the other types that can be related to them:

  • Created by users
  • Users can have editor or viewer roles
  • Users can share, delete, edit and view a folder
  • Folders can contain other folders
model
schema 1.1
type user
type folder
relations
# A folder can have another folder as its parent (a relation between objects)
define parent: [folder]
# A folder can have a user who is its creator (a relation between objects)
define creator: [user]
# A user can be an editor on a folder (role)
define editor: [user]
# A user can be a viewer on a folder (role)
define viewer: [user]
# A user can share a folder (permission)
define can_share: [user]
# A user can delete a folder (permission)
define can_delete: [user]
# A user can edit a folder (permission)
define can_edit: [user]
# A user can view a folder (permission)
define can_view: [user]
type document

Notice that for each relation you are defining, you are specifying a type (called a type restriction) that it can be. This is not always required, and you'll come back to it in the next step.

Now do the same exercise for the "document" type by going back to the requirements.

  • Created by users
  • Users can have editor or viewer roles
  • Users can share, delete, edit and view a document
  • Folders can contain documents

You'll notice this happens to be very similar to the situation with folders, and you should end up with something similar to:

model
schema 1.1
type user
type folder
relations
define parent: [folder]
define creator: [user]
define editor: [user]
define viewer: [user]
define can_share: [user]
define can_delete: [user]
define can_edit: [user]
define can_view: [user]
type document
relations
define parent: [folder]
define creator: [user]
define editor: [user]
define viewer: [user]
define can_share: [user]
define can_delete: [user]
define can_edit: [user]
define can_view: [user]

Go ahead and paste this into the FGA Playground and save it. Notice how the graph got updated to reflect your changes.

Defining Your Model: Step 3 - Updating the Relation Definitions

In OpenFGA, relations can be: assignable (such as roles or status between object), inferred from other relations (such as permissions) or a mixture of both.

Taking the requirements into account, they mention that:

  • a folder's creator can delete and share that folder as well as edit and view both the folder and all its children.
  • a folder's editors can both edit and view that folder and its children.
  • a folder's viewers can view that folder and its children.

Update the folder section to indicate that:

type folder
relations
define parent: [folder]
define creator: [user]
define editor: [user]
define viewer: [user]
# Only a folder's creator can share it
define can_share: [user] or creator
# Only a folder's creator can delete it
define can_delete: [user] or creator
# A folder's creator, its editors and anyone who is a creator or editor on its parent can edit it
define can_edit: [user] or creator or editor or creator from parent or editor from parent
# A folder's creator, its editors, its viewers and anyone who is a creator or editor or viewer on its parent can edit it
define can_view: [user] or creator or editor or viewer or creator from parent or editor from parent or viewer from parent

Here you are using three new concepts from OpenFGA:

  • Union, made possible by the or operator that specifies that the relation is true if any of the sub-sections are true.
  • Referencing other relations on the same object, for example when referencing creator in can_share, you are saying that can_share is true if creator is true.
  • Referencing relations on related objects, made possible by the from keyword. For example, the sub-section editor from parent on the can_edit relation means that if a user is an editor on an object that is a parent of this folder, then they can edit this folder.

We mentioned before that some relations can be assignable roles (e.g. a user assigned an editor role on a document), while others cannot be assigned and are always inferred (such as permission).

In order to specify that a relation is not assignable, remove the type restriction from the relation definition. Do this for the four non-assignable permissions (can_share, can_delete, can_edit and can_view) so that the folder type definition becomes:

type folder
relations
define parent: [folder]
define creator: [user]
define editor: [user]
define viewer: [user]
define can_share: creator
define can_delete: creator
define can_edit: creator or editor or creator from parent or editor from parent
define can_view: creator or editor or viewer or creator from parent or editor from parent or viewer from parent

Looking at the type definition of the folder above, it is a bit repetitive. Instead of iterating over all the combinations in the permission, you can make it so an editor role is automatically granted to creators and a viewer role is automatically granted to editors.

type folder
relations
define parent: [folder]
define creator: [user]
# Now, creators are also editors
define editor: [user] or creator
define viewer: [user] or editor
define can_share: creator
define can_delete: creator
# Anyone who is an editor on this folder or on any of its parent can edit it (this includes the creator of this folder, and the creator of its parent)
define can_edit: editor or editor from parent
define can_view: viewer or viewer from parent

There is still one more thing missing. When you define editor as [user] or creator, it is implying that the editor role itself is not inherited from its parents. This causes an issue, because now editor from parent only references the editor on the direct parent and does not traverse up all the way through all the folder's parents.

In order to fix that let's change the editor and viewer relation definitions yet again:

type folder
relations
define parent: [folder]
define creator: [user]
# Anyone who is: directly assigned an editor, is a creator, or is an editor of a parent is an editor of this folder
# - the creator of this folder
# - the editor or creator of its parent(s)
define editor: [user] or creator or editor from parent
define viewer: [user] or editor or viewer from parent
define can_share: creator
define can_delete: creator
define can_edit: editor
define can_view: viewer

You're done updating the folder type definition, now repeat the same steps for the relations on the document.

In this case, because documents are very similar to folders, you will end up with the same type definition:

type document
relations
define parent: [folder]
define creator: [user]
define editor: [user] or creator or editor from parent
define viewer: [user] or editor or viewer from parent
define can_share: creator
define can_delete: creator
define can_edit: editor
define can_view: viewer

Final Model

Congrats, you have defined your first OpenFGA model! Putting it all together, you have:

MODEL
model
schema 1.1
type user
type folder
relations
define parent: [folder]
define creator: [user]
define editor: [user] or creator or editor from parent
define viewer: [user] or editor or viewer from parent
define can_share: creator
define can_delete: creator
define can_edit: editor
define can_view: viewer
type document
relations
define parent: [folder]
define creator: [user]
define editor: [user] or creator or editor from parent
define viewer: [user] or editor or viewer from parent
define can_share: creator
define can_delete: creator
define can_edit: editor
define can_view: viewer

Before proceeding, go to the FGA Playground, paste this in the model section and click save.

Adding Some Data

Now that you have written the authorization model, you can start writing some relationship tuples to represent the state of your system.

In production, these tuples will be constantly written and deleted as users interact with your service.

In this example, you will add the following tuples to represent the scenarios you have above:

# Anne is the creator of the Product folder
- user: user:anne
relation: creator
object: folder:product
# Anne is the creator of the Planning folder
- user: user:anne
relation: creator
object: folder:planning
# The Product folder contains the Planning folder
- user: folder:product
relation: parent
object: folder:planning
# The Planning folder has been shared with Beth as an editor
- user: user:beth
relation: editor
object: folder:planning
# Anne is the creator of the Roadmap document
- user: user:beth
relation: creator
object: document:roadmap
# The Planning folder contains the Roadmap document
- user: folder:planning
relation: parent
object: document:roadmap

In order to do add a relationship tuple:

  1. You can add a relationship tuple in the relationship tuples panel located on the lower left part of the screen.
Image showing the FGA Playground relationship tuples panel
  1. Click ADD TUPLE to add new relationship tuples.
Image showing the FGA Playground add tuples button
  1. This will bring up the text boxes for User, Relation and Object. Type in the values seen above.
Image showing the FGA Playground add tuples screen
  1. Click SAVE button.
Image showing the FGA Playground add tuples save button
  1. The added relationship tuples will be shown in the relationship tuples panel.
Image showing the FGA Playground relationship tuples added

Repeat this process until you have added all the tuples above.

Relationship tuples may not be added if the corresponding authentication model has not yet been saved/updated. This can be verified by having an active SAVE button in the types panel.

Validating the Authorization Model

Now that you have an authorization model defined and some data in your store, you can start running some checks.

  1. In the query box, type a query of the form "is <user_type>:<user_id> related to <object_type>:<object_id> as < relation>?" and type Enter.
Image showing the FGA Playground is related query
  1. A successful query will show visualization on how the relationship is established in the TUPLE QUERIES panel.

For example, copy and paste the following query:

QUERY
is user:anne related to document:roadmap as can_view?
Image showing the FGA Playground successful how query
  1. An unsuccessful query will be denoted with a red box in the TUPLE QUERIES panel.

For example, copy and paste the following query:

QUERY
is user:beth related to folder:product as can_view?
Image showing the FGA Playground unsuccessful query

When you ask is user:anne related to document:roadmap as can_view?, underneath the hood the FGA Playground will issue a POST request to the OpenFGA API of the form:

POST /stores/<store_id>/check
{
"tuple_key": {
"user": "user:anne",
"relation": "can_view",
"object": "document:roadmap"
}
}

And the server's response will be of the form:

{ "allowed": true }

Auth0 FGA allows for specifying a set of assertions that apply to each model. Each assertion allows you to specify a check request and an expected result, allowing them to function as a sort of unit tests for your model.

The FGA Playground allows you to define a set of assertions to run in order to validate that your model is working.

Try following the steps below to add the following assertions to the FGA Playground and ensure that your model is working:

# Anne should be able to delete the Product folder
- user: user:anne
relation: can_delete
object: folder:product
expectation: true
# Anne should be able to share the Product folder
- user: user:anne
relation: can_share
object: folder:product
expectation: true
# Anne should be able to edit the Product folder
- user: user:anne
relation: can_edit
object: folder:product
expectation: true
# Anne should be able to view the Product folder
- user: user:anne
relation: can_view
object: folder:product
expectation: true
# Anne should be able to delete the Planning folder
- user: user:anne
relation: can_delete
object: folder:planning
expectation: true
# Anne should be able to share the Planning folder
- user: user:anne
relation: can_share
object: folder:planning
expectation: true
# Anne should be able to edit the Planning folder
- user: user:anne
relation: can_edit
object: folder:planning
expectation: true
# Anne should be able to view the Planning folder
- user: user:anne
relation: can_view
object: folder:planning
expectation: true
# Anne should NOT be able to delete the Roadmap document (Not an owner)
- user: user:anne
relation: can_delete
object: document:roadmap
expectation: false
# Anne should NOT be able to share the Roadmap document (Not an owner)
- user: user:anne
relation: can_share
object: document:roadmap
expectation: false
# Anne should be able to edit the Roadmap document (Owner of the parent, therefore an editor)
- user: user:anne
relation: can_edit
object: document:roadmap
expectation: true
# Anne should be able to view the Roadmap document (Owner of the parent, therefore an editor)
- user: user:anne
relation: can_view
object: document:roadmap
expectation: true
# Beth should NOT be able to delete the Product folder
- user: user:beth
relation: can_delete
object: folder:product
expectation: false
# Beth should NOT be able to share the Product folder
- user: user:beth
relation: can_share
object: folder:product
expectation: false
# Beth should NOT be able to edit the Product folder
- user: user:beth
relation: can_edit
object: folder:product
expectation: false
# Beth should NOT be able to view the Product folder
- user: user:beth
relation: can_view
object: folder:product
expectation: false
# Beth should NOT be able to delete the Planning folder (Beth is only an editor on this folder)
- user: user:beth
relation: can_delete
object: folder:planning
expectation: false
# Beth should NOT be able to share the Planning folder (Beth is only an editor on this folder)
- user: user:beth
relation: can_share
object: folder:planning
expectation: false
# Beth should be able to edit the Planning folder (Beth is an editor on the folder)
- user: user:beth
relation: can_edit
object: folder:planning
expectation: true
# Beth should be able to view the Planning folder (Beth is an editor on the folder)
- user: user:beth
relation: can_view
object: folder:planning
expectation: true
# Beth should be able to delete the Roadmap document (Beth is an owner)
- user: user:beth
relation: can_delete
object: document:roadmap
expectation: true
# Beth should be able to share the Roadmap document (Beth is an owner)
- user: user:beth
relation: can_share
object: document:roadmap
expectation: true
# Beth should be able to edit the Roadmap document (Beth is an owner)
- user: user:beth
relation: can_edit
object: document:roadmap
expectation: true
# Beth should be able to view the Roadmap document (Beth is an owner)
- user: user:beth
relation: can_view
object: document:roadmap
expectation: true

To add assertions on the FGA Playground:

  1. To add new assertions, click Assertions tab in the relationship tuples panel located on the lower left part of the screen.
Image showing the FGA Playground assertions tab
  1. After Assertions tab is selected, click ADD ASSERTION to add new assertions.
Image showing the FGA Playground add assertion button
  1. This will bring up the text for User, Relation and Object. Type in the values for each. The Allowed selection is TRUE if you want to assert the relationship exists. Otherwise, Allowed selection is FALSE if you want to assert the relationship does not exist.
Image showing the FGA Playground assertion true relationship
  1. Click SAVE button to add the assertion.
Image showing the FGA Playground assertion being saved
  1. To run all tests, click the Run all assertions button.
Image showing the FGA Playground assertion run all tests button
  1. The assertion test results are indicated in the assertion panels. The blue experiment box shows the number of tests. The green check box indicates the number of passing assertions. The red slash box indicates the number of failed assertions.
Image showing the FGA Playground assertion results

Recap

In this lab, you were introduced to OpenFGA and Fine-Grained Authorization. You learned how to define an OpenFGA authorization model that represents your service's permissions, and how to validate that is correct.

Next Steps

  • Take a look at the "Add FGA Authorization To An API" Lab to learn how to integrate OpenFGA with your service (coming soon!).
  • Checkout the Modeling Guides ins OpenFGA.
  • Set up your account on the Okta FGA Dashboard to get started with the managed Okta FGA Developer Preview.
  • Join the Auth0 Lab Discord Community to chat with the Okta FGA and Auth0 Lab teams.
  • Star the OpenFGA project on GitHub and follow OpenFGA on Twitter for updates.