401 Status Returned on Access Token Errors - pushbullet

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.

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.

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.

Related

MS Access API Request

This is my first post here though have been using the great advice/solutions here for years so am very grateful. But this one, I can't find solution for.
I have an MS access front end/back end in use for various office admin tasks and records. Much of the data my office works with on a daily basis is cloud based. However API access is provided with app key and secret key. I have no issues using the API explorer with these keys, but can't get anything through code (VBA). I keep reading it is perfectly possible to do this with VBA which is why I kept trying different solutions, but now need help. (I have replaced URL and keys etc)
Dim myObj As New MSXML2.XMLHTTP60
Dim url, endPoint, params, tickers, appKey, secretKey As String
url = "theURL.com"
endPoint = "theEndPoint"
params = "id="
tickers = "1"
appKey = "12345678"
secretKey = "12345678"
myObj.Open "GET", url, False
myObj.setRequestHeader "Content-Type", "application/json"
myObj.setRequestHeader "app-key", appKey
myObj.setRequestHeader "secret-key", secretKey
myObj.send
This returns "App Key is required." I have tried various solutions including converting keys to Base64, putting the keys within the Open request, sending the keys as part of the send request etc. Always get "App Key is required" when reading response. I know in this code I have't actually requested anything but that returns same message when I do. I just wanted to keep what I posted simple.
Any help at all is greatly appreciated as this would allow great deal of automation for our office.
Thanks
James

How to implement in VBA a REST(ful) API for a moodle Database?

I am trying to handle moodle data from our schools MS-Access database using VBA-code to post xml.objects that I also successfully used for an API communicating with Telegram (the messenger service).
I tried to implement the following code from an example for using the RESTful-API into my VBA-code:
curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Accept: application/json" \ -H 'Authorization: {token}' \ -d'options[ids][0]=6' \ "https://localhost/webservice/restful/server.php/core_course_get_courses"
This is how my VBA-code looks like:
Private Sub btnTestMoodleApi_Click()
Dim objRequest As Object
Dim strResult As String
Dim strPostData As String
Dim strURL As String
Dim strToken As String
Set objRequest = CreateObject("MSXML2.XMLHTTP")
strURL = xxx
strToken = xxx
strPostData = "options[ids][0]=432"
With objRequest
.Open "POST", strURL & "/webservice/restful/server.php?wstoken={" & strToken & "}/core_course_get_courses"
.setRequestHeader "Content-Type", " application/x-www-form-urlencoded"
.setRequestHeader "Accept", "application/json"
.setRequestHeader "Authorization", "{strToken}"
.Send (strPostData)
strResult = .responseText
Debug.Print strResult
End With
End Sub
The error I get in MS-Access is rather useless to me (also changing some aspects as described below did not change the error message):
" -runtime error 2147483638: The data necessary to complete this operation is not yet available
I suspect the following error sources:
a) I thought that "-H" means header, but the ".setRequestHeader" method just accepts one variable and value. I guessed maybe I can use it several times. But I am not sure if that works or how else I could declare a header in a xml.object.
b) I guess that "-d" means data, I had no idea what to do with it, thus I put into the .send() method. I think that is where the html body goes. I could be utterly wrong...
c) I nested the token for my API into the URL, because I saw it like this in another example. However the original instructions for the moodle-plugin do not have the token in the URL (only in the Authorization Header). I tried both ways, it did not work either way...
I would be really glad if someone with experience in vba could help me, how to implement API instructions into the vba code or at least point me in the right direction. Actually I do not really need this particular core function but the more complex ones to create courses etc. But I thought it was best to start with an easy example as I don't know much about API/ xml/ php/ html etc..
Thanks for reading
Arndt David
If the data is not yet available it seems like a loop to wait for results works
While objRequest.readyState <> 4
DoEvents
Wend
Besides that access to the webservice works from vba. Another problem is the implementation of the Authorization Header which is probably a formatting problem that I will clearify in another post.

How to Emulate a POST Request from PostMan - For a series of Integration Tests in a WebAPI Development

I am using PostMan as part of the testing regime for a WebAPI service development that I am working on. In order for external parties to gain access to our WebAPI service they first need to obtain an access token.
The POST request returns some JSON containing the required access token:
{
"access_token": "anencryptedaccesstoken",
"scope": "am_application_scope default",
"token_type": "Bearer",
"expires_in": 3218
}
I am putting together a series of integration tests which need to emulate the POST calls from POSTMAN. I am currently using System.Net.WebClient to achieve this. I am not sure what I need to do in order to achieve my goal. Here is a function that I am using to try and obtain the access token:
Public Shared Function GetAccessToken(ByVal endpoint As String, wc As WebClient) As String
Dim result As String = ""
Dim data As Byte() = Nothing
'Header information
wc.Headers.Add("Authorization", "Basic <alongencryptedstring>")
wc.Headers.Add("Content_Type", "application/x-www-form-urlencoded")
result = wc.UploadString(endpoint, "POST", "")
Return result
End Function
The 'Body' tab in PostMan contains the following entries:
grant_type - client_credentials
Content_Type - application%2Fx-www-form-urlencoded
In this instance as far as I am aware there is no 'data' element to the PostMan request hence the empty string in my use of UploadString. The function returns the following error:
"The remote server returned an error: (415) Unsupported Media Type."
I am new to web app development so please bear with me, I am finding it difficult to phrase what I think are meaningful question in the context of this post, I hope however that I have ssupplied sufficient information to convey the gist of my problem and for someone to be able to respond appropriately.
Kind Regards
Paul.

VBA http get request response is gibberish

I tried to search for a quick answer to this but I did not see a response...so I apologize if this is redundant.
I am pretty new to VBA and only use it to pull data from APIs to make my life easier.
My question is about the response im getting from a particular API. HEre is the code im using:
Dim URL As String: URL = "API URL HERE in json format"
Dim Http As New WinHttpRequest
Dim Resp As String
Http.Open "GET", URL, False
Http.Send
Resp = Http.ResponseText
Debug.Print Resp
So the Resp text is complete gibberish...is this a security thing? I pass a security key successfully when I use it in the browser so I assumed it was not that.... I have used this exact method on numerous APIs but this is the first time I have seen this garbled response. FYI the url I am using works just fine in a browser.
The Resp looks like this for example:
gõi:?ñq¢²^2?7AÄ??æºz³Gs=ΠÜ?¬«¤%?$ÖÉ'q¯¼|?¼²ôue¦½Þ"HË!ø5[4]s½?·Þ.OÛÃBh×?4"rÊÊ[r7
Thanks for any help!
Khauna

VBA HTTP GET request - cookies with colons

I am trying to send an HTTP GET request in VBA which includes a cookie containing a colon character, like so:
objReq.Open "GET", "http://my.url.com?foo=bar", False
objReq.setRequestHeader "Cookie", "abcd=cookie:containing:colons"
objReq.Send
Depending on what object type I use for objReq, however the request gets treated differently.
The following object type works:
Dim objReq As MSXML2.ServerXMLHTTP
Set objReq = New MSXML2.ServerXMLHTTP
Unfortunately, I need to use a different object type (as MSXML2.ServerXMLHTTP can't capture sufficient detail about HTTP redirects). From what I've read, I need to use Winhttp.WinHttpRequest, MSXML2.ServerXMLHTTP40, or MSXML2.ServerXMLHTTP60, but using any of those objects results in the following error when including colons in the cookie value.
I have tried replacing the colons with Chr(58), %3A, and double-quoting within the string to no avail. I have also tried adding a 'Content-Type' header with various character encodings, but that doesn't seem to work either.
Anyone know how I can send a cookie value containing colons using the Winhttp.WinHttpRequest, MSXML2.ServerXMLHTTP40, or MSXML2.ServerXMLHTTP60 objects?
PS: Alternatively, if anyone knows how I can get the ending URL of a redirect sequence when using MSXML2.ServerXMLHTTP, that would work as well! Winhttp.WinHttpRequest would allow me to capture a 302 status code, and MSXML2.ServerXMLHTTP40 or MSXML2.ServerXMLHTTP60 would allow me to use GetOption(-1), but MSXML2.ServerXMLHTTP doesn't support either of these methods (from what I can tell).
I did a bit of testing with WinHttpRequest and I came up with the following code:
Dim objReq As WinHttp.WinHttpRequest
Set objReq = New WinHttp.WinHttpRequest
objReq.Option(WinHttpRequestOption_EnableRedirects) = True
objReq.Open "GET", "http://www.example.com", False
objReq.setRequestHeader "Cookie", "abcd=cookie:containing:colons"
objReq.send
I did notice i got the same error that you posted when I forgot to include the "http://" in the url.
I hope this helps!