0

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.

Community
  • 1
  • 1
Bob77
  • 13,167
  • 1
  • 29
  • 37

2 Answers2

0

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.

Chris Pushbullet
  • 1,039
  • 9
  • 10
  • If regular HTTP Auth can be used then the 401 makes perfect sense. I may have misread the Pushbullet API docs but they seem to exclude that and instead require the `Access-Token` header. So when a bad token is sent the 401 does work, unless your library fails to report it to you and instead starts prompting the user for a logon user/pw. Microsoft's UrlMon stack (underneath IE, the MSXML library, etc.) will do this though their WinHttp stack does not. It seems unlikely that most browsers wouldn't do this as well. It is just a pitfall awaiting some API users. – Bob77 Oct 03 '15 at 15:42
  • Looking at my logs and the RFCs, I suspect the `401` status isn't the problem so much as the `Www-Authenticate` header returned. If this client had not used anonymous HTTP credentials in the 1st place it probably would have received the `401` instead of having it "managed" via a prompt. – Bob77 Oct 03 '15 at 15:48
  • Ahh the latter point I just made above is wrong too. Even sending a correct auth token as the user ID gets a 401 and a GUI prompt. See edits to original post above. – Bob77 Oct 03 '15 at 16:28
  • Regular HTTP auth can be used, I removed it from the docs because it is definitely more complicated than just including an Access-Token header, as this post indicates. So if I removed the WWW-Authenticate header, that wouldn't fix your issue, would it? – Chris Pushbullet Oct 03 '15 at 20:53
  • If you do the normal HTTP basic auth "Authentication": "Basic " + base64(access_token + ":") that should not give you a 401 – Chris Pushbullet Oct 03 '15 at 20:54
  • Just tried it, using a `"."` as password. Works fine with a correct token. With bad token it passes the `401` back to the code without raising any "login" prompts (most likely because the login creds were not anyonymous). So the UrlMon-based HTTP client can be used, but only with Basic Authentication (not the documented Access-Token header). I don't care for this but it works and the only alternative might make others unhappy (strip out accepting Basic Auth and return a proper 403 for non-standard auth technique failure). Seems clear we can't have it both ways. – Bob77 Oct 04 '15 at 06:12
0

Alternative:

If support for downlevel versions of Windows is not required you can use the WinHttp.WinHttpRequest object as a replacement for the MSXML2.XMLHTTP object used in the question examples above.

Set JsonBag = PBConfig.CloneItem("CreatePushJson") 'Make a copy.
JsonBag("title") = txtTitle.Text
JsonBag("body") = txtBody.Text

With WinHttp
    .Abort 'Clean up previously failed request if any.
    .Open "POST", PBConfig("CreatePushUrl"), True
    .SetAutoLogonPolicy AutoLogonPolicy_Never
    .SetRequestHeader "Access-Token", PBConfig("AccessToken")
    .SetRequestHeader "Content-Type", "application/json"
    .Send JsonBag.JSON
End With

The key to this is .SetAutoLogonPolicy AutoLogonPolicy_Never which we don't have available with the older class.

Note that this example makes use of the fact that a JsonBag has .Item() as its default property... just in case you were wondering about that difference in this code snippet compared to previous ones. It has no bearing on the use of WinHttp and could have been written this way in the earlier snippets as well.

Bob77
  • 13,167
  • 1
  • 29
  • 37