401 seems to be used to indicate an authentication failure whereas 403 for an authorization failure (which means authentication succeeded?) In case of an oauth flow if I try to authenticate with an expired token what is the right error code that prompts the client to refresh the token and try again?
A 401. The 401 vs 403 confusion is not new. See some discussion in a w3.org email here: https://lists.w3.org/Archives/Public/ietf-http-wg/2008AprJun/0418.html, though the cited RFC 2616 has been obsoleted.
Re 401, see https://www.rfc-editor.org/rfc/rfc7235#section-3.1
Re 403, see https://www.rfc-editor.org/rfc/rfc7231#section-6.5.3
From the 401 description, though "valid" is up for interpretation:
The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.
But even more relevant, see RFC 6750 regarding Bearer Token authentication, Section 3, last paragraph. https://www.rfc-editor.org/rfc/rfc6750#section-3
And in response to a protected resource request with an authentication attempt using an expired access token:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"
Think about the boarding process in an airport. They check 2 things:
They check who you are (you need to show your ID or passport). This is authentication. For an HTTP request, letting the server know who you are (using your username and password, or a JWT token, or private key, etc.) is authentication. Failing this step is error 401.
They check that you are on the passenger list for the flight you want to board. This is authorization, and the plane is the "resource" you are trying to access. For an HTTP request, the resource that the user generally tries to access is either a webpage or an API endpoint. Failing this step is error 403.
To sum it up:
- Authentication: Who are you? Prove your identity.
- Authorization: Now that we know who you are, let us check whether you can access this ressource or not.
So now when you think about an access problem (token expired, token parsing failed, invalid password, user is not admin, user is trying to access another user's data, etc.) and want to know which HTTP code you should return, just ask yourself:
If this issue was transposed into an airport scenario and I needed to deny access to a user, would the reason be him failing to prove his identity? If yes, then this is 401. Otherwise, this is 403.
For instance:
Connecting to an API endpoint with an expired token can be transposed to showing an expired ID/passport at an airport. Is the rejection because of failing to prove his identity? Yes, so it's 401. <= This is the answer to the question you asked, but let's discuss other cases below.
A missing token can be transposed to trying to board your plane without an ID or passport. Is the rejection because of failing to prove his identity? Yes, so it's 401.
A malformed/unparseable JWT token can be transposed to an unreadable/damaged passport. Is the rejection because of failing to prove his identity? Yes, so it's 401.
A user trying to access a page for which he's not granted can be transposed to someone trying to access a different plane. Is the rejection because of failing to prove his identity? No, so it's 403.
You banned a user: this can be transposed to a passenger being blacklisted. Is the rejection because of failing to prove his identity? No, so it's 403.
etc.
TL;DR: 401
Long version, in addition to crunk1 (valid) answer:
401 would mean that the token was missing or invalid. In other words, it failed validation or parsing for some reason.
403 would mean that the token was successfully validated/parsed, but then the authorization to perform the action was denied for some reason.
Now, an expired token means that the token was successfully parsed but that the expiration date set in that token is already passed. Which is somewhat in-between if you consider that checking the expiration date is part of the authorization process. Personally I believe that it is part of the token validation, not the authorization, for those reasons:
There are some cases where the resource server (the API that receives and validates the token) would not even be able to know that the token is expired, and would return a 401 instead:
- if the resource server uses its Authorization Server introspection endpoint and that one only returns
{"active": false}
with no further info, which means that the token is not valid (it might have been valid in the past, but that information was "forgotten" by the AS). - if the resource server tries to validate the token signature but the token is so old and expired that the validation keys have been renewed since it was issued. In that case even if the token is syntaxically valid but expired, the RS has no reason to trust that and should consider it as invalid instead.
Also, a 403 response would instruct the client that it is an authorization issue, so retrying with an new token carrying the same access rights doesn't have much chance to succeed, while a 401 would pass the information that the token was not accepted, so maybe retrying with a new fresh token might work.
For those reasons, I chose to return a 401 in my implementations. But TBH this doesn't matter much, since the token expiration is supposed to be handled by the client, based on the expires_in information returned by the AS at the same time as the token, more than by a return code from the API when the token is expired.
© 2022 - 2024 — McMap. All rights reserved.