In today's dynamic digital world, most apps get really overwhelmed with the complexity around data access. Especially when access is made by different user types.
Why do you even need access control?
Access controls help define your apps in a way that look and feel in a very user-centric way. It applies context to the user.
May be you have a subscription based app where you want to differentiate premium users from free users.
May be you are the admin of your app and want to see everything thats happening.
You want access control for the frontend. May be you want access control for the backend.
Use cases are just too many. The gist is, access control is ubiquitous in modern apps.
What approach to take?
So, may be you are convinced that, indeed some level of protection is required for your app. But would you write this access control logic and perhaps create a libary that you could reuse in your other projects?
Certainly not a bad idea. But, its quite cumbersome to do this for every single project. You would need to create almost a pusedo language to take care of minor details. For me, I always wished there was a nice way to declare things in a file and let that file do the magic for you. That file could a yaml, json, hcl or whatnot, not so relevant. If I could source control such behavior, would you think that could help relieve a lot of the pain that could arise from the problems of maintainance and management of such required complexity?
Enter OpenFGA.
OpenFGA is the open source version of FGA (Fined Grained Control) originally created by Auth0 (now merged with Okta). Yes, the company that manages users on global scale. Both versions are one and the same.
OpenFGA is a fantastic toolkit which relies on creating type definitions and their relations with other types where the toolkit creates an implied and trasitive relationships making it very powerful.
OpenFGA, which itself is written in Go, is a standalone server (a binary / docker container) which comes with 3 flavors of storage engine: PostgreSQL, MySQL and Memory.
You really do not need this blog post, instead theirs docs are more recommended instead. I am putting this as cummulative form of blueprint for myself from the future, so that I don't need to re-read their mammoth docs 😇
OpenFGA allows you to declare a file that you can source control. This file will contain your custom defined types and their relations. This file has its own DSL which has somewhat yaml'sh feeling to it. Although you can use JSON format if required. You can have a glimpse of the DSL on their awesome playground
With OpenFGA, type can be users or objects. Objects are like those things that are accessed by users. They, in a sense, do not mean much until you apply them. relations are part of every type that defines some kind of, well as the name says, relations.
These declarations are hollistic in nature and are what creates powerful transitive effect within the FGA system.
Lets look at the basic primitive:
type
model
schema 1.1
type user
type app
type premium_group
type super_group
type basic_feature
type premium_feature
Here, I create 6 random, but relevant, types. These types in on themselves do not mean anything and its only meaninglful once you start adding relations to them. In a very simplistic form, a type can be either a user type (not to conflate with type user) or an object type. Its really upto us which types are objects and which are users. The names I have used above are something that are meaningful to my usecase. They are certainly not canonical.
Ok, let look into relations
...
type app
relations
define used_by: [user, app#used_by]
...
Above, I extend the type app with used_by attribute (relation).
The used_by relation:
is making a direct relation with type user we already defined earlier.
is also referencing to itself
When a user is created, it must be added to the used_by relations of type app and member. Nothing fancy so far, neither does it mean much. But lets use the playground and add our first user in the Tuples section.
This one now gets added into the OpenFGA's database.
Similarly, add an assertion like so
user:user1
used_by
app:myapp
You will notice, OpenFGA uses a convention when adding tuple. They are prefixed with the type name followed by a colon. In the above case we say that user1 is added as a used_by relation to myapp which is of type app. So now when use any of the available SDKs for OpenFGA to query for user1 it should give you some connection to myapp. For now, lets use the playground to test out the assertion and you will notice a valid connection is established from the user:user1 to the app:myapp.
Hmm, ok but it does not do much. What if I want to enforce whether the user is allowed to use the app? Lets add a new relation to the app called disallowed and update the used_by relation
...
type app
relations
define used_by: [user, app#used_by] but not disallowed
define disallowed: [user]
...
And use the playground to add another tuple like so:
user:user1
disallowed
app:myapp
So the DSL, so far, looks like
model
schema 1.1
type user
type app
relations
define used_by: [user, app#used_by] but not disallowed
define disallowed: [user]
Now rerun the assertion we created above and witness the output. This time around, OpenFGA fails to make connection from user1 to myapp. We have standardized the way, access controls are accounted for. Isn't that cool?
Now, you must be thinking.
Jeez, I could have hardcoded all this logic by myself, and all the additional cognitive and resourceful burden from the DSL is not worth using OpenFGA.
And you could be right, if the window frame of thought is restricted to small logic. But if you want to extend and replicate the same logic in other projects, tool like OpenFGA makes this a little more standardized and easy to reason about. And the DSL isn't all that heavy. Well, I mean it could be in some advanced cases but so would the actual implementation be as well. In the long run, OpenFGA could potentially amortize the complexity required to manage access control in a nice DSL formatted file over a period of certain level of experience.
What do you guys think? Yes, this post might feel little incomplete, but stay tuned for the next one for more.
Addendum
In the futures section, I would like to go through some of the interesting tid bits for working with OpenFGA using the Go SDK.