- Aug 21, 2024
Authorization - what, how, and why?
- Daniel Krzyczkowski
When building cloud and application solutions, the first critical topic to address is authentication. This involves discussing how users will access the system and the methods we will use to verify their identities. There are many ways users can authenticate. There are many ways for users to authenticate. However, while authentication is crucial, we must also remember the importance of authorization.
First, it is good to remind what is the difference between authentication and authorization.
Authentication: This is the process of verifying the identity of a user. It answers the question, "Who the user is?"
Authorization: This is the process of determining what an authenticated user is allowed to do. It answers the question, "What can user do?"
Authorization challenges and issues
OWASP API Security Project is an initiative by the Open Web Application Security Project (OWASP) focused on addressing security issues related to Application Programming Interfaces (APIs). APIs have become a critical component in modern software architecture, enabling different software systems to communicate with each other. However, they also present unique security challenges, as they expose endpoints that can be exploited if not properly secured.
API Security Top 10: Similar to the well-known OWASP Top 10 for web application security, the OWASP API Security Project provides a list of the top 10 vulnerabilities specific to APIs. This list helps developers, security professionals, and organizations understand and prioritize the most significant risks to API security.
The most popular vulnerabilities related to authentication and authorization are:
1. Broken Object Level Authorization - a security flaw where an application doesn’t properly check whether a user has permission to access a specific piece of data. This means that if someone knows (or guesses) the unique identifier for an item, they can access it, even if it belongs to someone else.
Example: Suppose you have an online banking app, and you want to check your account balance. You send a request to the bank’s server with your account ID: GET /accounts/12345/balance.
The server should check if you’re the owner of account 12345 before giving you the balance information.
If the server doesn’t properly check, and you change the ID to 12346 (someone else’s account), you might be able to see their balance too. That’s a BOLA vulnerability.
2. Broken Authentication - a security vulnerability where the methods used to confirm a user’s identity are flawed, allowing attackers to take over accounts or bypass security checks. This can happen when passwords, session management, or authentication mechanisms are poorly implemented or misconfigured. Broken Authentication can lead to account takeovers, where attackers gain unauthorized access to user accounts. This can result in identity theft, financial loss, and compromised sensitive data.
Example: There are multiple example of this type of vulnerability, some of them are: no validation of the authenticity of tokens, accepting unsigned JWT tokens, no validation on JWT token expiry date or using plain text/weakly hashed passwords.
3. Broken Function Level Authorization - a security flaw where an application doesn’t properly check whether a user has permission to perform certain actions or access specific functions. This allows users to perform actions they shouldn’t be able to, like accessing admin features or modifying other users’ data.
Example: Imagine a website where regular users can view their profile, but only admins can delete user accounts. If a regular user figures out the URL or button that lets admins delete accounts (e.g., /admin/delete_user?user_id=123), and the system doesn’t check if the user is an admin, they might be able to delete any account.
4. Broken Object Property Level Authorization - a security issue where an application fails to properly check if a user has permission to access or modify specific properties or fields of an object. This means that even if a user is allowed to access an object (like a profile or document), they might be able to change details or fields they shouldn't have control over.
Example: In an online store, customers might be able to view and update their shipping address for an order. But what if the system also lets them change the order’s total price or status (like marking it as "shipped" or "delivered")? If the system doesn’t restrict access properly, a customer might change the price to something lower or mark the order as "delivered" without actually receiving it.
Authorization implementation approaches
Once we understand some of the vulnerabilities related to authorization, let's discuss how we can actually implement authorization. There are a couple of ways to implement authorization in the application. Let me describe the two most popular methods.
Authorization based on the token claims
This method of authorization implementation is the most popular. In this scenario we include authorization data in the ID and Access Tokens so the application or API can look into the token and make authorization decisions basing on claims values. This can be good fit for very simple authorization decisions.
Example:
There are few important downsides of this approach. Let's discuss some of them.
Claims used for authorization decisions
The above solution where we use claims (or to be more specific, roles claim) to store information for authorization decisions is limited. Claims were designed to represent the user's identity. When using claims for authorization decisions we face below challenges:
We cannot store complex authorization schema in claims. In the above example we have simple role names. What about the situation where we want to handle complex authorization scenario like: "User who is global manager can access documents but only from specific department". With simple role claims such scenarios become hard to be implemented.
Large amount of claims can lead to large tokens which should be avoided.
What about the situation when the role of the user has changed? In the ID and Access tokens we still have the "old" role until these tokens expire. Of course we can obtain new token but this will require contacting identity provider and in some cases re-authentication. This is not an ideal user experience.
To summarize, authorization rules are business logic, while claims are just about the user's identity and there is no business logic behind them.
User's Identity != User's Permissions
Authorization based on the external authorization store
For more complex authorization rules the better idea is to use external authorization – commonly called "P*P" architectures (PIP, PDP, PEP).
The P*P architecture for authorization is a framework that separates the process of making authorization decisions (who can do what) from the system enforcing those decisions. The two main components in this architecture are the Policy Decision Point (PDP) and the Policy Enforcement Point (PEP).
This approach to authorization is the recommended one and should be preferred over the authorization using claims in tokens. Examples include architectures based on IDQL, OPA/Rego, XACML and Zanzibar.
As presented in the above diagram, there are few components of this architecture:
Web applications
Mobile applications
Identity Provider (Microsoft Entra External ID in this case)
Financial API
Now from the authorization perspective, there are two important components:
1. Policy Enforcement Point (PEP) - a gatekeeper that checks if someone is allowed to access certain data or resources. When a request comes in to API, the PEP asks another system (called the Policy Decision Point or PDP, explained below) to decide if the request should be allowed. The PEP doesn’t make any decisions itself; it just follows the rules provided by the PDP. You can find PEPs wherever important data needs to be protected in an application. Typically, PEP is implemented as a part of the API logic to make authorization decisions on each incoming request.
2. Policy Decision Point (PDP) - a system component that makes decisions about whether users or processes should be allowed to perform specific actions or access certain resources, based on predefined rules or policies.
How does PDP work? To make it simple, there are three steps:
Receives Requests: The PDP gets a request from the PEP, which is asking whether a specific action or access should be permitted.
Checks the Rules: The PDP evaluates the request against a set of policies or rules that define who is allowed to do what. These rules might include things like user roles, permissions, relations, and conditions.
-
Makes a Decision: Based on the evaluation, the PDP decides if the request should be allowed or denied and sends this decision back to the PEP.
Authorization standardization
When it comes to authentication standards, we know that there is OAuth, OpenID Connect, SAML and others. When it comes to authorization, there can be more confusion about the standard approaches/frameworks how to properly implement it.
The AuthZEN Working Group (under OpenID Foundation) is a collaborative initiative focused on improving and standardizing authorization mechanisms, particularly in the context of web applications and services. The group's aim is to address the challenges and complexities associated with authorization, which is the process of determining whether a user or system has the right to access certain resources or perform specific actions.
Personally, I think this is great move as we want to make authentication and authorization standards to be interoperable. The AuthZEN Group proposed Authorization API 1.0 draft to make it easier to implement communication in P*P architectures. You can read more under the link I provided. I want to touch on some important parts from this specification.
The Access Evaluation API defines the message exchange pattern between a client (PEP) and an authorization service (PDP) for executing a single access evaluation.
The Access Evaluation API request:
{
"subject": {
"type": "user",
"id": {
"format": "iss_sub",
"iss": "https://issuer.example.com/",
"sub": "145234573"
}
},
"resource": {
"type": "account",
"id": "123"
},
"action": {
"name": "can_read",
"method": "GET"
},
"context": {
"time": "1985-10-26T01:22-07:00"
}
}The Access Evaluation request is a 4-tuple constructed of the four previously defined entities:
subject: The subject (or principal) of type Subject
action: The action (or verb) of type Action.
resource: The resource of type Resource.
context: The context (or environment) of type Context.
The Access Evaluation API Response:
{
"decision": true
}The simplest form of a response is simply a boolean representing a Decision, indicated by a "decision" field.
In the above example we simply ask the question:
Can user identified by sub value 145234573 access (or to be more specific, read details about) account with id 123?
With this approach we can implement communication between Policy Enforcement Point (PEP) and Policy Decision Point (PDP).
Summary
In this article we discussed different approaches to implement authorization. Each application is different and has different authorization requirements. It is important to carefully design the authorization model and then implement the proper enforcements.