How we build granular access control and authorization system.

9 min read Original article ↗

Authorization is usually something overlooked. It does not sound like something hard, isn’t it?

  • You can build a hacked solution.

  • Modeling the early use cases is pretty straightforward. (Admin or User, that’s all.)

For instance; adding roles table to our database, and linking to users’ database potentially solve your problem.

Yet, uses cases and features multiplies. By the time you need a new solution, shit got real! Your existing solutions fell short, users get more demanding, access control is nested in your business logic…

Every new feature, every new request becomes devastating for any developer. I heard no developer say “ Hurray, I’m building access control. ”, Neither heard saying “Hurray”.

But you get the point.

How did we know?

Because “Shit got real” for us several times. My co-founders and I have been building things together since high school. Almost a year ago, we started building an project management system for a client.

We start simple but things got complicated pretty fast, as the project grew clients need new access control features such as;

  • Only x can see that button or data

  • Only senior management can edit that data etc.

We have to update our access control several times, even after the production. Because their needs have changed.

Multiple times, as they grow…

And we have seen this scenario various times for startups, client projects, products that we have built, or been part of.

So, building a solution for authorizations is no easy task.

But decoupling your access control layer from your business logic might be a great first step.

Access control is something that usually has several shareholders, but just developers. That means you can’t just solve this problem with “if…else” statements that is embedded in your code.

Because one way or another; your Product, HR, and Design teams will need to interact with it. And if they’re interacting through the engineering team.

Oh boy…

You can build a decoupled system, which manages access control outside of your main code. This creates flexibility to improve your access control over time.

But building a future-proof external service for access control is a heavy load. It can’t be usually prioritized while having dozens of core product-related tickets.

Another great solution is to use Policy engines. They usually cover most of the needs in terms of flexibility and complexity.

One of the most popular policy engines is OPA or Open Policy Agent. OPA is an open-source, and general source policy engine.

It basically decouples policy decisions from other responsibilities of an application, in other terms business logic.

You might ask what’s the catch?

Yeah, OPA is damn hard to learn. It could cover more than what you need. But with a longer learning curve.

It might take weeks, or even months to implement OPA. Which means less time or resources for your core product.

That’s why we built Permify.

We wanted to build access control as fast as possible without dealing with the complexity, and learning curve.

So, We have spent weeks learning how to build this thing instead of building just simple access control. So that you can set up complex and flexible authorizations in under 30 mins.

Why because we’re developers.

Permify is a plug-&-play authorization API, that makes you build and deploy access control solutions in 30mins.

You can easily create complex and flexible RBAC and ABAC solutions without dealing with a heavy learning curve.

We have defined 3 concepts for the authorizations process. Respectively; Policy, Option, and Rule.

Image 1

Policies are access control layers that consist of rules and options. You can define who, and in which circumstances can access certain resources, actions, or data points.

To do that, you can create different combinations of;

  • User roles

  • User Attributes

  • Resource attributes

And make comparisons between them. Policies turn you with an access check response by using these rule sets, options, and attributes.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

Option is a cluster that works as an “and” operator between Rules. And you can create “or” relations between different rulesets by using Options.

For instance, as you can see in “Image 1”; Senior Manager and Resource Owner are two separate options nested in a policy.

According to that policy, users can edit tasks either if they’re senior manager “or” resource owner.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

Rules are functions in which you can compare user roles, user attributes, and resource attributes.

As seen in the “Image 1”; “is senior”, “is manager” and “is resource owner” are examples of rules.

the use senior? (ABAC)

user.attributes.tenure > 8

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

Is the user manager? (RBAC)

"manager" in user.roles

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

Is the user the owner of the resource? (ABAC)

user.id == resource.attributes.owner_id

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

{
"name": "task edit policy",
"guard_name": "task-edit-policy",
"description": "",
"options": [
{
"name": "senior manager",
"guard_name": "senior-manager",
"rules": [
{
"name": "is senior",
"guard_name": "is-senior",
"description": "",
"conditions": [
"user.attributes.tenure > 8"
]
},
{
"name": "is manager",
"guard_name": "is-manager",
"description": "",
"conditions": [
"\"manager\" in user.roles"
]
}
]
},
{
"name": "resource owner",
"guard_name": "resource owner",
"rules": [
{
"name": "is resource owner",
"guard_name": "is-resource-owner",
"description": "",
"conditions": [
"user.id == resource.attributes.owner_id"
]
}
]
}
]
}

Check Authorization is the function that you can use both on the server and client-side.\

You can find out if the user is authorized to perform the action by sending the user ID, policy name, resource (optional) to Permify.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

PolicyName: "task-edit-policy",
UserID: "1",
ResourceID: permify.String("1"),
ResourceType: permify.String("task"),
})

Using client-side authorization can help you protect your app even further while also making it easier to control access on various UI components, Layers, Pages, etc.

isAuthorized is a helper function that returns a Promise which turns true if the user is authorized for action with the given parameters, if not false.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

PermifyComponent is a wrapper that controls components or UI Layers that should only be accessible to authorized users.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

<PermifyComponent
policyName='post-edit'
type='hide'
>
<button type="button"> Edit Post </button>
</PermifyComponent>

We have just separated our access control from the business logic. But the main challenge of decoupling access control occurs after this.

Because decisions are consists of both authorization and application inputs. (e.g. which role the user has and who created the role.) It is hard to sync your users and resources access control attributes in an external service.

For that reason, we have created following API calls for users and resources;

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

response, err := api.User.Create(user.Create{
ID: "1",
Name: "tolga",
GroupID: "1",
RoleNames: []string{"manager"},
Attributes: map[string]interface{}{
"tenure": 7,
},
})

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

response, err := client.Resource.Create(resource.Create{
ID: "1",
Type: "task",
GroupID: "1",
Attributes: map[string]interface{}{
"owner_id": "1",
},
})

While creating resources and users, you can add the attributes you want. These attributes can be later used in the rules and policies to make access control decisions.

In short; even it seems easy to build and manage simple authorizations, it gets hard and unmanageable over time with different customer requests.

On the other hand, it’s not feasible to build a future-proof system from scratch. Because it’s not usually a core product feature.

If you feel the same as we did, Permify helps you build this complex and future-proof system in 30mins. So you don’t have to worry about any development effort.

For more information, you can check out our docs from this link.

And if you want to use Permify you can signup from this link.

If you have further questions, feel free to reach out at tolga@permify.co

Thank you for reading Firat's Codex. This post is public so feel free to share it.

Share

Discussion about this post

Ready for more?