Define An FGA Authorization Model
Published on September 29, 2023In 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:
-
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.
-
The current system state, which is represented by a series of relationship tuples, each containing:
- a user (which can be an object or group of objects)
- a relation
- an object
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:
- Click the NEW STORE button on the top right hand corner of the screen.
- Type in a store name in the text box. Note that the store name may only contain letters, numbers and '-'.
- Click 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):
modelschema 1.1
Then you can add the types for the model to become:
modelschema 1.1type usertype foldertype document
Before continuing with defining the model, you will learn how to save it using the FGA Playground
- 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).
- After the changes are made, click SAVE.
- After the authorization model is saved, the Types Previewer will be updated with the new authorization model preview.
- After the authorization model is saved, the SAVE button is no longer active.
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
modelschema 1.1type usertype folderrelations# 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:
modelschema 1.1type usertype folderrelationsdefine 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 documentrelationsdefine 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 folderrelationsdefine parent: [folder]define creator: [user]define editor: [user]define viewer: [user]# Only a folder's creator can share itdefine can_share: [user] or creator# Only a folder's creator can delete itdefine can_delete: [user] or creator# A folder's creator, its editors and anyone who is a creator or editor on its parent can edit itdefine 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 itdefine 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
incan_share
, you are saying thatcan_share
is true ifcreator
is true. - Referencing relations on related objects, made possible by the
from
keyword. For example, the sub-sectioneditor from parent
on thecan_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 folderrelationsdefine parent: [folder]define creator: [user]define editor: [user]define viewer: [user]define can_share: creatordefine can_delete: creatordefine can_edit: creator or editor or creator from parent or editor from parentdefine 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 folderrelationsdefine parent: [folder]define creator: [user]# Now, creators are also editorsdefine editor: [user] or creatordefine viewer: [user] or editordefine can_share: creatordefine 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 parentdefine 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 folderrelationsdefine 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 parentdefine viewer: [user] or editor or viewer from parentdefine can_share: creatordefine can_delete: creatordefine can_edit: editordefine 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 documentrelationsdefine parent: [folder]define creator: [user]define editor: [user] or creator or editor from parentdefine viewer: [user] or editor or viewer from parentdefine can_share: creatordefine can_delete: creatordefine can_edit: editordefine can_view: viewer
Final Model
Congrats, you have defined your first OpenFGA model! Putting it all together, you have:
modelschema 1.1type usertype folderrelationsdefine parent: [folder]define creator: [user]define editor: [user] or creator or editor from parentdefine viewer: [user] or editor or viewer from parentdefine can_share: creatordefine can_delete: creatordefine can_edit: editordefine can_view: viewertype documentrelationsdefine parent: [folder]define creator: [user]define editor: [user] or creator or editor from parentdefine viewer: [user] or editor or viewer from parentdefine can_share: creatordefine can_delete: creatordefine can_edit: editordefine 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:annerelation: creatorobject: folder:product# Anne is the creator of the Planning folder- user: user:annerelation: creatorobject: folder:planning# The Product folder contains the Planning folder- user: folder:productrelation: parentobject: folder:planning# The Planning folder has been shared with Beth as an editor- user: user:bethrelation: editorobject: folder:planning# Anne is the creator of the Roadmap document- user: user:bethrelation: creatorobject: document:roadmap# The Planning folder contains the Roadmap document- user: folder:planningrelation: parentobject: document:roadmap
In order to do add a relationship tuple:
- You can add a relationship tuple in the relationship tuples panel located on the lower left part of the screen.
- Click ADD TUPLE to add new relationship tuples.
- This will bring up the text boxes for User, Relation and Object. Type in the values seen above.
- Click SAVE button.
- The added relationship tuples will be shown in the relationship tuples panel.
Repeat this process until you have added all the tuples above.
Validating the Authorization Model
Now that you have an authorization model defined and some data in your store, you can start running some checks.
- 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.
- 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:
is user:anne related to document:roadmap as can_view?
- An unsuccessful query will be denoted with a red box in the TUPLE QUERIES panel.
For example, copy and paste the following query:
is user:beth related to folder:product as can_view?
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:annerelation: can_deleteobject: folder:productexpectation: true# Anne should be able to share the Product folder- user: user:annerelation: can_shareobject: folder:productexpectation: true# Anne should be able to edit the Product folder- user: user:annerelation: can_editobject: folder:productexpectation: true# Anne should be able to view the Product folder- user: user:annerelation: can_viewobject: folder:productexpectation: true# Anne should be able to delete the Planning folder- user: user:annerelation: can_deleteobject: folder:planningexpectation: true# Anne should be able to share the Planning folder- user: user:annerelation: can_shareobject: folder:planningexpectation: true# Anne should be able to edit the Planning folder- user: user:annerelation: can_editobject: folder:planningexpectation: true# Anne should be able to view the Planning folder- user: user:annerelation: can_viewobject: folder:planningexpectation: true# Anne should NOT be able to delete the Roadmap document (Not an owner)- user: user:annerelation: can_deleteobject: document:roadmapexpectation: false# Anne should NOT be able to share the Roadmap document (Not an owner)- user: user:annerelation: can_shareobject: document:roadmapexpectation: false# Anne should be able to edit the Roadmap document (Owner of the parent, therefore an editor)- user: user:annerelation: can_editobject: document:roadmapexpectation: true# Anne should be able to view the Roadmap document (Owner of the parent, therefore an editor)- user: user:annerelation: can_viewobject: document:roadmapexpectation: true# Beth should NOT be able to delete the Product folder- user: user:bethrelation: can_deleteobject: folder:productexpectation: false# Beth should NOT be able to share the Product folder- user: user:bethrelation: can_shareobject: folder:productexpectation: false# Beth should NOT be able to edit the Product folder- user: user:bethrelation: can_editobject: folder:productexpectation: false# Beth should NOT be able to view the Product folder- user: user:bethrelation: can_viewobject: folder:productexpectation: false# Beth should NOT be able to delete the Planning folder (Beth is only an editor on this folder)- user: user:bethrelation: can_deleteobject: folder:planningexpectation: false# Beth should NOT be able to share the Planning folder (Beth is only an editor on this folder)- user: user:bethrelation: can_shareobject: folder:planningexpectation: false# Beth should be able to edit the Planning folder (Beth is an editor on the folder)- user: user:bethrelation: can_editobject: folder:planningexpectation: true# Beth should be able to view the Planning folder (Beth is an editor on the folder)- user: user:bethrelation: can_viewobject: folder:planningexpectation: true# Beth should be able to delete the Roadmap document (Beth is an owner)- user: user:bethrelation: can_deleteobject: document:roadmapexpectation: true# Beth should be able to share the Roadmap document (Beth is an owner)- user: user:bethrelation: can_shareobject: document:roadmapexpectation: true# Beth should be able to edit the Roadmap document (Beth is an owner)- user: user:bethrelation: can_editobject: document:roadmapexpectation: true# Beth should be able to view the Roadmap document (Beth is an owner)- user: user:bethrelation: can_viewobject: document:roadmapexpectation: true
To add assertions on the FGA Playground:
- To add new assertions, click Assertions tab in the relationship tuples panel located on the lower left part of the screen.
- After Assertions tab is selected, click ADD ASSERTION to add new assertions.
- 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.
- Click SAVE button to add the assertion.
- To run all tests, click the Run all assertions button.
- 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.
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.