04/04/24

AuthZ for small teams

Practical patterns for implementing authorization

6 Min Read

We all realize the importance of security, yet it is still really hard to reason about.

Intuitively, we all know that users should only have the minimum permissions they need — whether this is a customer or a customer support agent — because it minimizes the risk of unauthorized access and enhances overall system security.

This concept, known as authorization (authZ), is often confused with its close relative, authentication (authN). However, while authentication confirms your identity, authorization determines your access rights.

For smaller teams, especially those developing products with limited resources, understanding and implementing effective authorization mechanisms can be challenging and it is very easy to kick the can down the road. However, retro-fitting RBAC/ABAC can be really difficult, so it's a good idea to tackle it head on before your system grows.

In this blog post, we'll explore the main use case for authZ; ensuring users of our product have the permissions they should have. With a focus on smaller teams, we'll look at practical patterns for implementing authorization, strategies for auditing, and considerations for scaling. I'll also include some real-world examples of everything we discuss to try and make it more concrete.

Authorization (AuthZ)

As we briefly discussed, authorization determines what you can do, given who you are. For example, in a project management tool such as Jira, any signed-in user might be able to view a list of projects, but only product managers may have permission to re-order the backlog.

This distinction between authentication and authorization is crucial. Misunderstandings can lead to security loopholes where users gain access to data or actions they shouldn't. By clearly defining and enforcing authorization from the outset, you can build a more secure, scalable, and user-friendly product.

That's very easy to say, but how do you do it?

Patterns for Authorization

Several patterns exist for managing authorization, and as with all hard problems in Software, which one you use depends on your use case and what you want to achieve.

Role-Based Access Control (RBAC): Perhaps the most common, RBAC assigns permissions to roles rather than individual users. Users are then assigned roles, simplifying management. For small teams, RBAC can be an efficient way to handle permissions without getting bogged down in complexity.

If you have ever logged into Okta, Auth0 or even Kubernetes, you have likely interacted with an RBAC system, even if you didn't realize it.

We gave the Jira example above, but this approach isn't limited to customer-facing products. Let's dig into an infrastructure example too. In Kubernetes, we can define the following:

apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: pod-reader rules: - apiGroups: [""] # "" indicates the core API group resources: ["pods"] verbs: ["get", "watch", "list"]

Which defines a role called pod-reader which has limited permission to get,watch and list pods. Whoever has this role has no ability to destroy pods.

We can then assign this to a group called developers like this:

apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: pod-reader-binding namespace: default subjects: - kind: Group name: developers # Name of the group apiGroup: rbac.authorization.k8s.io roleRef: kind: Role # Refers to a Role name: pod-reader # Name of the Role to bind apiGroup: rbac.authorization.k8s.io

This is a simple example, but perhaps could be useful for someone who is ramping up to join the on-call rota?

Generally, I think RBAC is very easy to reason about and scales pretty well. Most companies should probably start with RBAC and evolve towards ABAC as their needs evolve.

Attribute-Based Access Control (ABAC): ABAC uses policies that evaluate attributes (user, resource, environment) to make authorization decisions. This flexibility supports complex scenarios but requires more upfront design and ongoing management.

That's quite a mouthful, so let's give a concrete example. I'm currently writing this document in Google Docs. I would like to invite some folks from Twitter to read it and review it for me by leaving comments. I'd also like the folks at encore to review it but I'll allow them to edit it directly. In an ABAC framework, implementing such access control for this document would involve defining specific attributes for users (e.g., their affiliation with "Twitter" or "Encore") and actions they are allowed to perform (e.g., "read and comment" vs. "edit").

ABAC can have high cardinality and can be a lot more complex to reason about. Google published a white paper on how they think about this which is a really interesting read. You can see it here. There are some companies that have built out Zanzibar implementations you can use, my favorite is SpiceDB.

Designing for Flexibility and Security

Designing an authorization system that's both flexible and secure can be overwhelming. My advice would be to not over think it and start with the principle of least privilege, ensuring users have the minimum level of access necessary. As your product evolves, this approach keeps security tight while allowing for adjustments.

Documentation and clear policies are equally important to ensure as your company grows, your system continues to make sense. For example, if you are going to use RBAC groups, having predictable group names can be really helpful. One piece of advice I would give is to use a scheme in the format of $system_role such as SUPPORTTOOL_VIEWER,SUPPORTTOOL_ADMIN.

For documentation, I haven't personally found any amazing tooling here. If you define your policy in code, comments are very useful and you can use linting tools to ensure that good naming practices are followed. If you have an internal wiki like Notion and Confluence, these can work too as long as you are rigorous about updating it.

Ensuring Correctness

Like any other part of our system; we need to ensure we test our Authorization system. I think for most companies the correct thing to do here is a mixture of automated tooling and manual audits (which as you mature can be more and more automated).

Automated tools can help identify discrepancies between intended and actual permissions. Some of the more expensive RBAC tools have functionality built in for this, but you can also get away with a relatively simple script.

If you are looking for somewhere to start here, take a look at Open Policy Agent. If you define your RBAC policies using OPA's rego language, you can treat them as code and write unit tests, just like you would for any other language. They have a really nice playground you can experiment in. If you are familiar with the Go language, you'll find Rego feels somewhat familiar.

If you are applying RBAC to a customer-facing system, you may want to provide functionality for administrators to view who has what permissions, and potentially even highlight anomalous groupings automatically. 

Wrapping Up

Retro-fitting RBAC/ABAC is really difficult, but is surprisingly reasonable to reason about if you think about it early on in your product's journey. I can't think of a single system where you want every person accessing it to have the same permissions, so the sooner you get ahead of this the better. Starting with RBAC and the principle of least privilege will get you surprisingly far!

I hope this helps you to get started on your AuthZ journey!

About The Author

Matthew Boyle is an experienced technical leader in the field of distributed systems, specializing in using Go.

He has worked at huge companies such as Cloudflare and General Electric, as well as exciting high-growth startups such as Curve and Crowdcube.

Matt has been writing Go for production since 2018 and often shares blog posts and fun trivia about Go over on Twitter.

He's currently working on a course to help Go Engineers become masters at debugging. You can find more details of that here.

Encore

This blog is presented by Encore, the backend framework for building robust type-safe distributed systems with declarative infrastructure.

Like this article?
Get future ones straight to your mailbox.

You can unsubscribe at any time.