Authorization is the other half of the 'auth' equation. If authentication determines who a user or entity is, authorization (often stylized as authz) determines what that user or entity can access or do within a system. The business logic that governs this access within a system makes up its authorization scheme. Authz schemes can vary in complexity from basic boolean logic (has access/doesn't have access) based on basic attributes to more complex schemes based on multiple inputs and data.
Unlike authentication, authorization and access control schemes are less 'plug-and-play' and can vary quite a bit in implementation and usage. Nevertheless, there are a few common authz patterns found in systems today:
Role based access control (RBAC) is one of the most popular and widely used authz schemes. Used primarily in B2B software and SaaS, RBAC involves 3 main entities: users (or subjects), roles and permissions. Permissions define specific actions that can be taken within a system while roles serve as 'containers' for multiple permissions within the system. As an example, a system might have an 'admin' role with permissions such as 'create-report' and 'delete-report' assigned to it. Users can then be granted the 'admin' role in order to be subsequently granted the underlying admin permissions.
Attribute based access control (ABAC) is an equally popular and widely used authz scheme. Similar to RBAC, ABAC policies are used to grant or restrict access to functionality or data in an application. Unlike RBAC which is purely based on permission and/or role, ABAC policies can be driven by one or more specific attributes of an entity or user. For example, an ABAC policy might dictate that any user with a "@warrant.dev" email address has the ability to 'create-report' within a system. This permission is granted based on a specific user attribute (in this case email address) instead of the presence of a specific role. ABAC can be used to drive powerful access control policies but is limited to the explicit attributes of a user or entity.
Relationship based access control (ReBAC) is an authz model that grants or restricts access based on the explicit and implicit relationships between specific entities. An example of ReBAC in use is the Google Drive model of file sharing and ownership. A Google Doc typically has an 'owner', multiple 'editors' and multiple 'viewers'. So users can have an 'owner', 'editor' and/or 'viewer' relationship with a document. These relationships then further define what a user can do with a document. For example, an 'owner' or 'editor' can change a document's contents but a 'viewer' may only view it. ReBAC can enable more powerful and granular authz but in practice may require a fundamental change in the application's underlying data model.
Improper authorization and access control can lead to severe security vulnerabilities in applications. In fact, the latest OWASP Top 10 lists 'Broken Access Control' as its #1 vulnerability based on occurrence and severity. Authz and access control vulnerabilities can range from users gaining access to behavior/data they shouldn't be able to access to malicious users exploiting an authz hole to manipulate or corrupt data at large scale.
Given the scope and severity of authz vulnerabilities, it's especially important to follow best practices when implementing authz schemes. Here are a few to consider:
A good rule to follow with authorization is to 'deny by default' or 'enforce least privileges'. This simply means that by default, users should have the bare minimum level of access within a system. This at least ensures that users don't accidentally have access to data or parts of a system that they shouldn't be able to access. One simple authz scheme to enable this is RBAC. Users can be segmented into different 'roles' that grant more elevated levels of access based on need. For example, an application may have every user start within a 'teammate' role that doesn't allow for privileged access. If a user requires privileged access, they may then be assigned an 'admin' role in the future that grants them the additional privileges. This type of RBAC authz at least guarantees that all users don't have privileged permissions by default.
Generally, more granular authz schemes lead to applications with better security and user experience. In this context, 'granularity' refers to the detail with which authz policies can be defined. As an example, having an authz policy that can grant 'editor' access to a specific report within a system is more powerful than having a RBAC-type policy which just defines that 'all admins can edit reports'. The main tradeoff with this approach is that authz systems that support this level of granularity are more complicated to implement and maintain vs. using more traditional RBAC-type systems.
Authorization logic can become quite complex over time and usually lives close to the business logic of an application. As a result, authz logic often spreads across a codebase and can get increasingly difficult to review or change. As an example, an application without a centralized RBAC service may have role and permissions checks scattered throughout the codebase and worse, these checks may be implemented differently. This introduces further complexity and increases the chance of security holes within an application. That's why, a best practice for authorization logic, whether it's implemented within the application or in an external service, is to centralize it. This makes it easier to review, make changes and even test the impact of changes as an application evolves.
Even an authorization system that follows all best practices can be compromised, whether through a bad code-change, misconfiguration or even human error. The first step to recover from a breach often involves understanding the scope of the breach and what data was affected. That's why it's important to ensure that your authz system has an audit log, ideally one which logs each interaction including the requesting user or entity, the data or query being made as well as the overall result of the operation.