Selecting the right response to API requests helps secure your application. While it may not seem so on the outside, every unnecessary piece of information makes it easier for an attacker to understand how to gain access. And on the flip side every piece of missing information makes it harder for a consumer of your API to understand the response to an HTTP request.
Here we’ll break down the most common HTTP error responses used for the purposes of API security. When a request is successful, that means that:
- The request token uniquely identifies a user correctly
- The resource in the request exists
- The action on the resource is valid
- The user has necessary permission for that action on that resource
Then a successful response status code can be used => 2XX
Fundamentally there are three relevant error codes: 401, 403, 404.
401
401 — indicates that the request has not been applied because it lacks valid authentication credentials for the target resource. The user is not authenticated. (See here for more information on the difference between authentication and authorization). The API requires a valid user, this is determined by the Authorization header in the request. A 401 is the right error code when:
- There is no token specified
- The token specified is in an invalid format
- The token has expired as token contain a window for when they are valid
- And in rare cases, the token is valid, but should not be used for this API (also known as the audience)
403
403 — indicates that the server understood the request but refuses to authorize it. The user is not authorized. The user attempted to perform an action, but the token that identified the user does not have sufficient permissions to do that. It’s really helpful to return the permissions that the user is missing so that they can go request them from an admin. Google presents to you a page like this:
404
404 — indicates that the server can’t find the requested resource. Links which lead to a 404 page are often called broken or dead links, and can be subject to link rot. If the url path otherwise known as the resource doesn’t exist, then a 404 is appropriate.
Picking the right error code
That seems easy to handle, and most of the time, they go in order:
- Validate Token
- Verify user permissions
- Check resource existence
Sometimes 2 and 3 happen in the reverse order depending on what’s easier for the application server. However, when you do this you could be opening your resources up to exposing too much information. What happens if a user doesn’t have access to a resource and that resource exists? 403 seems reasonable. What happens if that resource doesn’t exist? 404?
In that case, without access to a resource, you return different information based on whether that resource exists. A user without permission can start scanning all your endpoints and potential resources searching for existing ones. If those resources could be publicly shareable like Zoom sessions, you could end up with more Zoombombing on your hands.
Instead we should break down 403’s into more nuanced categories. If a user knows about a resource and doesn’t have permission then return a 403. You may still want to share the missing permissions or two to request access from. However, if the user shouldn’t know about the resource, then neither return who to contact, the missing permissions, nor a 403 suggesting that the resource exists. In this case return the 404.
A concrete example is what Authress provides when authorizing users. Your application has resources. Access to those resources is stored in Authress access records. Access records are account specific. If an Authress user from another access asks to read one of your accounts access records, they get a 404. If they ask to see the account info, 404. If they attempt to get a list of users with access to a resource, 404. Under no circumstance do they get any other than a 404, except if they:
- Make a request to a list resource. They get an empty array ([]) and a 200. (collection endpoints don't return 404)
- Have knowledge of the domain your account is using, then they can successfully access the identity provider configuration.
And that’s about it.
Returning a 403 is great for your actual users, but can start to expose important details of your application. Don’t let potential attackers know which resources exist.
Going further
Want to see how Authress uses error codes to ensure security? Checkout the Authress Management Portal — API section.
Originally published at https://authress.io on July 1, 2020.
Top comments (1)
Then there is the under utilized 410 Gone. The biggest issue with 410 is that you often need a human to decide that "this resource will never come back again" and not many think about lifecycling their urls. I'd say it is a good idea for most content to have a lifecycle, and to think what happens once the content will no longer be available. Possible solutions are to redirect to a newer resource (such as newer version of a product) or to indicate something as fully gone but that it did exist at one point. This is a huge difference to 404 which is very generic and you can't tell if it was correct in the past or you have an incorrect link or why it doesn't work.