Search code examples
pushbullet

401 Status Returned on Access Token Errors


While a 401 Unauthorized may seem spiffy for these ("Access token is missing or invalid") it can throw many a client HTTP stack into prompting the user for credentials, something that won't succeed anyway since normal HTTP authentication mechanisms are not in play.

While I can detour that using another client library that I can direct not to attempt auto-auth or user prompting (and have done so) this seems to violate RFC 7235 as far as I can tell.

I suspect that a 403 Forbidden would be more compliant here and less grief for API users. Most of them probably just see any non-2XX status and immediately run to look for a JSON "error" reponse body.

I have a detour so I'm not complaining, but something seems fishy here. Surely I'm missing something? Is it common practice now to use the 401 in this manner for REST-like HTTP APIs?

More detail

This works as long as the proper auth token is used, but causes a GUI prompt for user/pw if a bad token is used:

Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
    .Item("title") = txtTitle.Text
    .Item("body") = txtBody.Text
End With

With XMLHTTP
    .abort 'Clean up previously failed request if any.
    .open "POST", PBConfig.Item("CreatePushUrl"), True
    .setRequestHeader "Access-Token", PBConfig.Item("AccessToken")
    .setRequestHeader "Content-Type", "application/json"
    .onreadystatechange = SinkRSChange
    .send JsonBag.JSON
End With

If the prompt is canceled by the user then the 401 gets reported to the code.

In light of information below I tried sending the auth token as a user ID value. However this raises a prompt even if the auth token is correct:

Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
    .Item("title") = txtTitle.Text
    .Item("body") = txtBody.Text
End With

With XMLHTTP
    .abort 'Clean up previously failed request if any.
    .open "POST", PBConfig.Item("CreatePushUrl"), True, PBConfig.Item("AccessToken")
    .setRequestHeader "Content-Type", "application/json"
    .onreadystatechange = SinkRSChange
    .send JsonBag.JSON
End With

If the user manually enters the valid auth token into the prompt as the user ID the request then succeeds.

Based on new information below

This can be made to work by explictly sending a "." as password:

Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a deep copy of template JSON.
With JsonBag
    .Item("title") = txtTitle.Text
    .Item("body") = txtBody.Text
End With

With XMLHTTP
    .abort 'Clean up previously failed request if any.
    .open "POST", PBConfig.Item("CreatePushUrl"), True, PBConfig.Item("AccessToken"), "."
    .setRequestHeader "Content-Type", "application/json"
    .onreadystatechange = SinkRSChange
    .send JsonBag.JSON
End With

Correct token value works, bad token value returns the 401 where it can be handled. No credentials prompt dialogs now.


Solution

  • Normal HTTP authentication mechanisms are technically in play. The api even asks your browser for credentials so you can do requests in your browser (someone actually requested that).

    HTTP libraries that have special behavior for 401s do seem to be a problem, but the one time it happened I was been able to disable the magic 401 handling. I have no idea who is in violation of RFC 7235 here. RFC 2616 10.4.2 seems to indicate that the current behavior is "correct". Do you have a list of HTTP clients that prompt the user for credentials?

    Maybe a 403 makes more sense here, but Stripe at least seems to use a 401: https://stripe.com/docs/api#errors and they are all about the REST. Switching to a 403 would break all existing clients as well. Most clients actually don't look at the JSON body oddly enough, they just look at the status code.

    I think if I make another HTTP API it will have only 200/400/500 status codes with POST of JSON encoded bodies and JSON responses.