Reputation: 14516
I am working on a RESTful API that includes third-party integration. We use OAuth with the authorization code flow to authenticate against the third party. The user must log in to our service and then additionally to the third party so that our application can access the third party.
Some of our resources require interaction with the third party to complete (say GET /third-party-userpic
). If the user is already logged in to that service, we will retrieve the access token from our data store and use that to retrieve the user's picture, easy!
If we don't hold valid credentials for that user to that service, however, we can't get the user's picture. This will occur on first use, and may also occur if the credentials expire or are revoked. In this case, we want to communicate back to the client that they need to visit the authorization URI and begin the OAuth flow.
My colleagues and I designing this have discussed several possibilities, including:
200 OK
success with an indication that authentication is required or permitted in the response body somehow. This is inelegant and doesn't extend well to resources of different content-types, as well as requiring the client to know when it is accessing a resource which may require this authentication scheme.403 Forbidden
along with a link to authenticate in either the headers or body. The issue is that this is requires distinguishing from a 403 Forbidden
generated due to permissions issues, and feels out of line with what we want 403
to mean.401 Unauthorized
. This has the same problem of requiring the client to break it apart from 401
generated by our platform when the user isn't logged in. There is the additional issue that 401 Unauthorized
is part of the HTTP challenge-response authentication scheme, but we are not implementing a challenge-response flow here. The client never touches the credentials.4XX
status code to allow the client to easily separate this condition from other possible failures. This seems like the cleanest from a technical point of view, but making new status codes isn't something we probably need to be doing!Upvotes: 3
Views: 599
Reputation: 5055
What https://www.rfc-editor.org/rfc/rfc6750 suggests is following:
insufficient_scope The request requires higher privileges than provided by the access token. The resource server SHOULD respond with the HTTP 403 (Forbidden) status code and MAY include the "scope" attribute with the scope necessary to access the protected resource.
WWW-Authenticate: Bearer realm="myprotectedresource",
error="insufficient_scope",
error_description="Insufficient scope for this resource scopes", scope="SOME_SCOPE"
Below is authorization flow diagram from same source
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Upvotes: 0
Reputation: 6606
To get this started: I find it hard to believe that displaying a user pic is a critical component, so I assume there are grater ramifications than a user being condemned to being represented by a default avatar.
Within this scenario, a 2xx-class response code is pretty much out of the question as the action has not been successful. While there is a mechanism on the serverside failing, the client has a chance to correct this (by authenticating via OAuth). This rules out the 5xx-class status codes, suggesting the 4xx-class.
As you correctly asserted, 401 is not quite right, as this code is specific to the requested resource. From RFC 7235, section 3.1:
The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.
However, the target resource in your case is fine; it is an external service being troublesome. Apart from that, 401 is taking you into the heart of the HTTP Authentication Framework which is introducing a number of practical issues.
The most fitting status code in this range is indeed 403. From RFC 7231, section 6.5.1:
The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it. A server that wishes to make public why the request has been forbidden can describe that reason in the response payload (if any).
While this may sound unrelated at first, consider that
[...] a request might be forbidden for reasons unrelated to the credentials.
The RFC also reads:
The client MAY repeat the request with new or different credentials.
So this code is relaxed in that it is not specific to the requested resource, but considers other circumstances preventing a successful operation very well. This is also the very response code you will get when you follow the flowchart on Choosing an HTTP Status Code — Stop Making It Hard.
Upvotes: 1
Reputation: 12310
with an indication that authentication is required or permitted in the response body somehow
I think this depends a lot on “required” vs. “permitted”: can you otherwise complete the request successfully, with only a part of the result missing?
If you can (and want to), maybe something like this:
HTTP/1.1 200 OK
Date: Tue, 10 May 2016 01:08:07 GMT
Content-Type: application/vnd.whatever+json
Link: <https://third-party.example/authorize>;
rel="https://example.com/doc/#authorize-3rd-party"
Warning: 299 - "part of the content is missing; authorize with third party"
...content with only the pic missing...
If you can’t (or don’t want to), then 424 (Failed Dependency), 403 (Forbidden) (but see comments), and 400 (Bad Request) all sound like they might be appropriate status codes. However, status codes are not a solution to every problem. It’s perfectly normal to refine their meaning in the response payload. There’s even a new standard for this—RFC 7807:
HTTP/1.1 400 Bad Request
Date: Tue, 10 May 2016 01:08:07 GMT
Content-Type: application/problem+json
{
"type": "https://example.com/doc/#authorize-3rd-party",
"title": "You need to authorize with a third party for this.",
"authorizeAt": "https://third-party.example/authorize"
}
You’re very right in your misgivings about 401 (Unauthorized) and inventing your own status code.
Upvotes: 0