Understanding HTTP status codes is crucial when developing websites and APIs. These codes, returned by servers, provide insight into the success or failure of a request. Two of the most commonly encountered status codes are 401 (Unauthorized) and 403 (Forbidden). While they might seem similar at first glance, they serve distinct purposes. This article delves into the nuances of these two status codes, helping software developers discern when and how to use them.
While many articles on the internet attempt to differentiate between these errors, modern application development standards require us to take a closer look at them. As web application development evolves with new trends and techniques every day, it is important to stay up to date while working with standards such as the 401 and 403 error codes.
The Basics of HTTP Status Codes
HTTP status codes are three-digit numbers returned by servers to indicate the outcome of a request. They are grouped into five classes, with the first digit defining the class. For instance, 4xx codes signify client errors, while 5xx codes indicate server errors.
In the 4xx class, 401 and 403 are particularly noteworthy. The "401 Unauthorized" status code indicates that the request lacks valid authentication credentials. On the other hand, the "403 Forbidden" status code signifies that the server understands the request but refuses to fulfill it. These two stages in access control are usually considered as authentication and authorization.
The error 401 Unauthorized can be a bit confusing. While 401 is an authentication error code, its description labels it as Unauthorized. Let’s dive into the 401 code to understand it in detail.
Diving Deeper into 401 Unauthorized
When a server returns a 401 status code, it's essentially saying, "I don’t recognize you." This code is used when authentication is required and has failed or hasn't been provided. The response must include a WWW-Authenticate header field, requiring the client to provide valid credentials.
For instance, consider an API endpoint that requires a valid API key. If a request to this endpoint doesn't include the key or provides an invalid one, the server will respond with a 401 status.
HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="Example"
When used correctly, the 401 error helps our API users understand what the next steps are for a successful response. For example, if we just want to get stronger authentication for a particular endpoint (MFA, for example) it should be declared in the response.
With the authentication stage handled, it is time to dive into the later stage, authorization, and 403 error code.
Understanding 403 Forbidden
The 403 status code has a different implication. It tells the client, "I know who you are, but you're not allowed here." Even if the client provides valid authentication credentials, the server can still return a 403 if the client doesn't have permission to access the requested resource.
For example, a user might be authenticated to access a website but lack the necessary permissions to view an admin page. In such cases, a 403 status would be appropriate.
HTTP/1.1 403 Forbidden
As you can see, the 403 code does not include a reason for the error. This is due to the person who should be taking action. In the authentication example, the user usually should be made aware of the error and the reasoning for it, fixing it by providing the required credentials. In authorization, it is up to the API user to decide what should be done with the current state of permissions. For example, they may wish to toggle forbidden features in the application for certain users.
A further distinction is the generic nature of the steps that follow an error. In authentication, there is only one way to get access to the relevant endpoint - getting the correct credentials. In authorization, the reasons are independent of the access to the resource - users without admin roles may not be able to obtain them.
RFC References and Their Importance
After understanding the differences between the error codes, it is now time to check the source of those errors and follow the basic guidelines to make sure application security and compliance are maintained.
RFCs (Request for Comments) are documents that describe various aspects of the internet, including protocols, procedures, and programs. For HTTP status codes, RFC 7235 specifically addresses the 401 Unauthorized status, while RFC 7231 details the 403 Forbidden status.
By referring to these RFCs, developers can gain a deeper understanding of their intended use and the implications of these status codes. It's always a good practice to refer back to the official documentation when in doubt.
RFCs are not new, but the world of application development is changing rapidly and daily. With this in mind, we need to adopt a dynamic mindset that adapts the principles of RFCs to the evolving situation of modern APIs.
User vs. Service Access Control
One of the things that changed significantly from the days of the status codes RFC, is the way services connected to each other. Back then, when the RFC was written, the most common server consumer was a user via a frontend application. In the modern world of microservices and software built by 3rd party APIs, a lot of the RPC calls are made from one service to another.
In regards to these status codes, the main difference between these kinds of consumers is the actions that should be taken after one of these codes is returned. While for user sessions the application should notify them to bring credentials again for their identity, different instructions apply for service sessions (such as token refreshment).
With this difference in mind, the relevant information needs to be assigned for each type of session. Occasionally, we would like to differentiate status codes between user and service sessions for the same error.
In spite of the clear distinction between 401 and 403 in access control, the dichotomy between those stages may cause some misconceptions and misuse.
From the 401 side, many developers can think of it as a status for missing credentials, while the code should also be used in case the credentials are incorrect or need refresh. In modern applications, asking for stronger authentication for authenticated users is a valid flow for the 401 code.
On the other hand, with the 403 code, permissions are not always tied to credentials or authenticated users. For example, an application should use 403 code for anonymous actions that can be performed only in particular circumstances (time-based, for example).
With a proper understanding of the stags of access control and differentiating between authentication and authorization, you can easily find the right error code for each endpoint and scenario.
When to Use Which
With these distinctions, RFC, and misconceptions in mind, let’s try to find some guidelines that will help us decide when to use each.
The first point to remember is that while both codes relate to authentication and authorization, they address different aspects of the process.
With this distinction in mind, we can proceed with the effect the code has on the application’s behavior and experience. Considering it together with the experience we would like to give to our API users - the developers using our HTTP APIs. These result in the following guidelines:
Use 401 when the application principal needs to authenticate itself to get a response.
Use 403 when the application principal doesn't have the required privileges to perform an action on a resource.
Let’s imagine a cloud storage service (such as Google Drive, for example). Such services usually ask users to log in to see and edit resources on the service but also allow some operations for unauthenticated users. Here is an example of various codes for various operations:
If a user tries to create a file without logging in, they should get a 401 error
If a user tries to create a file while logged in but has no permission to create files in a particular folder, they should get a 403 error
If a user wants to create a file, but the owner of this particular folder is required to pass an MFA test for it, they should get a 401 error
If a user is trying to get access to a public shared link, but the owner hides the file, they should get a 403 error
404 vs 403 - Error Encapsulation
Occasionally, some sites return a 404 (not found) error instead of a 403 error when a user is not authorized to access (or perform an operation on) a resource. The primary purpose of doing so is to hide the existence of the resource from the user instead of letting the user know the resource exists but they are unable to perform the requested operation. This practice is called error encapsulation, and we can see it implemented with 5xx errors when a 500 error is replaced with a 503 error to avoid informing potential attackers that an internal server error occurred. While it is not documented in any standard or RFP, it is a common practice in many applications.
To determine where and when to use each, we should put the knowledge of our API user (developer or end-user) as they are the first-class citizens we want to design our API for. If we would like our users to know they are prohibited from performing an operation due to lack of permissions, we should use 403 error. We would also like to return 403 for users who can leverage the information and request those permissions. Returning a 404 is only justified when knowledge of the resource’s existence poses a security risk in itself.
Understanding the difference between 401 and 403 is essential for developers. While both indicate authentication and authorization issues, they serve distinct purposes. By using them appropriately, developers can provide clearer error messages, leading to better user experiences and easier debugging.
Always remember to refer to the official RFCs when in doubt and ensure that your applications handle these status codes gracefully. With the knowledge from this guide, you're well-equipped to navigate the intricacies of HTTP 401 vs. 403.
Looking to make sense of your application's access control? Permit.io provides authorization as a service, allowing you to implement authorization into your app within minutes and manage it with a no-code UI.