For years, there’s been an ongoing discussion which HTTP status code to use for “not authorized” scenario – and the original HTTP 1.1 specification wasn’t exactly crystal clear about the distinction between 401 (unauthorized) and 403 (forbidden).
But there is definitely the need to distinguish between the situation where no or invalid credentials were supplied with a request and the situation where a valid credential was supplied, but the “entity” belonging to that credential is not authorized for the operation it is trying to do.
Here are some examples:
- In good old ASP.NET FormsAuth (well this also applies to the brand new cookie middleware in Katana) – a 401 is turned into a 302 to the login page. That’s fine for anonymous requests – but when a user is already authenticated, a failed authorization (e.g. using [Authorize(Role=”foo”)]) will result in showing the login page again – not very intuitive. You rather want do deal with that error or or show an error page.
- In Web APIs and token based authentication your client need to be able to distinguish if the token is e.g. expired or if it is missing the necessary scopes. The “Bearer Token Usage” spec (http://tools.ietf.org/html/rfc6750) is pretty clear about this. Expired or malformed tokens should return a 401 – missing scopes should result in a 403.
You might have heard that the HTTP 1.1 spec has been re-written recently. It is now clearer on the status codes as well (you know it is getting serious when you see a Courier font, right?):
The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource. The server generating a 401 response MUST send a WWW-Authenticate header field (Section 4.1) containing at least one challenge applicable to the target resource.
A server that receives valid credentials that are not adequate to gain access ought to respond with the 403 (Forbidden) status code.
Unfortunately, the ASP.NET MVC/Web API [Authorize] attribute doesn’t behave that way – it always emits 401. The logic would be simple for failed authorization: “if user is anonymous – emit 401, if not – emit 403” (we now emit 403s for our scope authorization helpers – here and here).
Maybe we can fix that in vNext.