I have set up a company in the QuickBooks developer portal, and have successfully created an OAuth token (which I can refresh as specified in this answer). So, unlike the majority of these questions, this is not about OAuth... I'm using bare PHP here, as I cannot use the SDK for technical reasons.
After getting the OAuth token, I am trying to perform the next API call from the API playground, getting the company information. I effectively call the QB API with this code:
($query is /v3/company/<companyID>/companyinfo/<companyID> (companyID is the "Realm ID" from the API playground), $base is the sandbox-URL (same as "Sandbox Base URL" in the header), $access_tokens["access"] is the OAuth access token; $url is https://${base}${query})
$headers = array(
"GET " . $query . " HTTP/2",
"Host: " . $base,
"Accept: application/json",
"Authorization: Bearer " . $access_tokens["access"],
"Production Base URL: https://quickbooks.api.intuit.com",
"Sandbox Base URL: https://sandbox-quickbooks.api.intuit.com"
);
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_HEADER, true);
$output = curl_exec($curl);
After the call, the value of $output is:
HTTP/2 400
date: Tue, 31 Aug 2021 14:42:55 GMT
content-type: text/plain
content-length: 11
server: envoy
Bad Request
The request headers (as returned by the QB server via curl_getinfo) are:
GET /v3/company/4...0/companyinfo/4...0 HTTP/2
Host: sandbox-quickbooks.api.intuit.com
accept: application/json
authorization: Bearer e...Q
production base url: https://quickbooks.api.intuit.com
sandbox base url: https://sandbox-quickbooks.api.intuit.com
(the auth token and company ID have been cut)
If I call the URL directly in a browser, I get a 401 (authentication error) as expected, so I assume the OAuth part works (as I said, I can successfully retrieve an access token and refresh it). I'm just wondering if there is anything else that I'm doing wrong which causes the QB API call to fail?
There's a lot here that looks a little funky... I would start by trying these things:
Removing the invalid GET header. GET is not a valid HTTP header (it's part of the method/verb instead), and you shouldn't be trying to specify HTTP/2 in this way. Remove this:
"GET " . $query . " HTTP/2",
Remove the two other invalid HTTP headers. It looks like you're confusing Intuit's documentation for HTTP headers here. These are not valid headers and you should not be sending them:
"Production Base URL: https://quickbooks.api.intuit.com",
"Sandbox Base URL: https://sandbox-quickbooks.api.intuit.com"
Don't specify the Host: header to cURL. There's no reason to, and it could potentially conflict with the URL you pass cURL. cURL will calculate and send this for you.
"Host: " . $base,
I'd also check tomake sure you're sending the request to the correct URL. If it's for a sandbox company, it goes to the sandbox URL. If it's for a production company, it goes to the production URL.
Related
I am developing REST API and while it is easy to set raw JSON data for request in cURL for POST
$payload = json_encode(array("user" => $data));
//attach encoded JSON string to the POST fields
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
I cannot figure out how to send such data with GET requests.
Is there something like CURLOPT_GETFIELDS or CURLOPT_RAWDATA? The purpose of sending JSON with GET request is to pass in some params.
I do not wish to add formdata to the request, I wish to post JSON so that it can be parsed on the receiver.
Thanks!
EDIT:
based on comments I want to avoid confusion, so the resulting request should look like:
GET / HTTP/1.1
Host: 127.0.0.1:3000
Content-Type: application/json
Accept: application/json
Host: 127.0.0.1:3000
content-length: 13
Connection: keep-alive
cache-control: no-cache
{
"a": "b"
}
as you can see, GET request here has data and it is parsed and works perfectly by web server. How do I achieve this with cURL?
GET requests do not have a body, that's the whole idea: you're just getting something from the server, as opposed to posting something to it. From RFC 7231:
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
In other words, a GET request can have data, but it should not. From earlier in the spec, where GET is defined as a safe method:
Request methods are considered "safe" if their defined semantics are
essentially read-only; i.e., the client does not request, and does
not expect, any state change on the origin server as a result of
applying a safe method to a target resource.
...
Of the request methods defined by this specification, the GET, HEAD,
OPTIONS, and TRACE methods are defined to be safe.
If you really want to have JSON in your GET request (and send it to a reasonably implemented server resource) the only place it can go is in the URI as part of the query string. For GET requests I find using file_get_contents to be much easier than dealing with cURL.
<?php
$payload = json_encode(["user" => $data]);
$url_data = http_build_query([
"json" => $payload
]);
$url = "https://some.example/endpoint.php?" . $url_data;
$result = file_get_contents($url);
If you want to send it to an unreasonably implemented server resource, and violate the spirit of the HTTP RFCs, you could do this:
<?php
$url = "https://some.example/endpoint.php";
$payload = json_encode(["user" => $data]);
$ctx = stream_context_create(["http" => [
"header"=>"Content-Type: application/json",
"content"=>$payload
]]);
$result = file_get_contents($url, false, $ctx);
If you're determined to do this specifically with cURL, you might have luck with the CURLOPT_CUSTOMREQUEST option set to "GET" and CURLOPT_POSTDATA with your data.
I've built a custom database that I would like to populate with some of my UP3 data. I've successfully authenticated and received the JSON response with my Bearer token. From here, I'm sort of lost.
From the documentations, I need to send a GET request to
https://jawbone.com/nudge/api/v.1.1/users/#me
Each time I do, I get:
No 'Access-Control-Allow-Origin' header is present on the requested resource
I've added:
header('Access-Control-Allow-Origin: http://example.com');
(replacing example.com with my domain)
to the page that is sending the GET request. Any thoughts?
The Jawbone endpoints all have the CORS header set to
Access-Control-Allow-Origin: *
However, this still will not allow cross-site requests from localhost (thanks #TirthrajBarot for the explanation in the comments), so you should disable this check in Chrome with something like the Access-Control-Allow-Origin extension.
After authenticating the user and permissions, I redirect to another page to validate the authentication code and get the access token. From there, I go to another page to send the GET request for the data. Here is the code that is working:
$url = "https://jawbone.com/nudge/api/v.1.1/users/#me/sleeps?" . $sleepDate;
$options = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept: application/json\r\n" .
"Authorization: Bearer " . $accessToken . "\r\n" .
"Host: {my website}"
)
);
$context = stream_context_create($options);
$sleepData = #file_get_contents($url, false, $context);
The data is returned, decoded, and processed. The result is pushed into a text box in a form for the user to edit if desired. There is also a button to enter the data into the database (along with a bunch of hidden form fields that enter other relevant details)
I use the following code to get the bearer token:
$token = base64_encode($client_id.':'.$client_sec);
$data = array ('grant_type' => 'client_credentials');
$data = http_build_query($data);
$header = array(
'Authorization: Basic '.$token,
'Content-type: application/x-www-form-urlencoded;charset=UTF-8',
'Content-Length: ' . strlen($data)
);
$options = array(
CURLOPT_HTTPHEADER => $header,
CURLOPT_HEADER => false,
CURLOPT_URL => 'https://api.twitter.com/oauth2/token',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $data
);
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
curl_close($ch);
print_r($result);
exit;
But output all the time:
{"errors":[{"label":"authenticity_token_error","code":99,"message":"Unable to verify your credentials"}]}
What I doing wrong?
After fighting with this problem for a while I found the problem was I was making the call to /oauth2/token using Advanced Rest Client from a browser I was already logged into Twitter with. After logging out of Twitter and making the API call again it worked fine.
Short answer: make sure you do not already have an active session logged into Twitter when attempting to request a Bearer token.
I struggled with this for awhile and none of the answers I've found seemed to help. The documentation for the error is also a vague "something went wrong".
My problem is that I was using a mashup of code I found, and the headers weren't used correctly:
$headers = array(
'Authorization' => 'Basic ' . base64_encode($appid . ':' . $secret), // WRONG!!!
'Authorization: Basic ' . base64_encode($appid . ':' . $secret), // Correct!
'Content-Type: application/x-www-form-urlencoded;charset=UTF-8', // Correct!
);
For me, the problem was that the Authorization header was using key-value format, while the content-type header was not. This broke the authorization header.
Here are some other things to check that also relate to error 99:
Verify that your credentials are correct and base64 encoded (see above)
Make sure the request is using POST
Ensure the content-type is set (see above)
Make sure you included grant_type=client_credentials as a post field.
SSL is required, make sure that is being used (https://)
Try verbose logging to help debugging. It should include SSL certificate information, your authorization header, and content type header. This won't show the grant_type field though, only headers.
If everything looks OK but it still won't work, you might be getting rate limited. Rate limits reset every 15 minutes.
When you finally get your access token, make sure you cache it to avoid rate limiting. You get 450 requests every 15 minutes, I believe. Half of that will be spent on getting your access token if you don't cache it!
There's an accepted answer here already but just in case someone stroll to this post and had the same issue I did...
Twitter docs for reference -> OAuth 2.0 docs
Misconception #1: The Authorization String is generated using the consumer key (aka API-Key) and consumer secret (aka API Secret Key). The display of those credentials in the UI on developer.twitter.com is less apparent than that of apps.twitter.com. Nonetheless common RIF problem.
Misconception #2: This one is not really an misconception but an implementation error when base64 encoding the url concatenated Consumer Key+":"+Consumer Secret. If not doing this programmatically be sure to check for whitespaces anywhere (especially around the :) in the concatenated string you are base64 encoding.
Just a tad bit advice as well postman has a wonderful utility that makes the rest call to retrieve an oauth2.0 token (as well as other auth tokens) this was useful for me when trying to consume api's with the that required an oauth1.0 token
After fighting with this problem, i finally come up with the solution. Twitter is not actually sending the right message if error exist anywhere.
When i send request from curl, it works fine but when through code. i was having same error {"errors":[{"label":"authenticity_token_error","code":99,"message":"Unable to verify your credentials"}]}
So what i got, problem was lying with Access control header. setting these header does not work for me
xhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xhttp.setRequestHeader('Access-Control-Allow-Headers', '*');
xhttp.setRequestHeader('Access-Control-Allow-Origin', '*')
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
as a workaround i just used this url to bypass request to handler cors
https://cors-anywhere.herokuapp.com/https://api.twitter.com/oauth2/token
added "https://cors-anywhere.herokuapp.com/" before the actual url and it began to work. hope someone may face this issue in problem
Twitter OAuth 2.0 Bearer Token:
Step 1: Encode consumer key and secret
A - Concatenate the encoded consumer key, a colon character ":", and the encoded consumer secret into a single string.
B - Base64 encode the string from the previous step.
Example function: convertStringBase64("Api key" +":"+"Api key secret")
C- This steps generate you "Authorization"
Step 2: Obtain a Bearer Token
URL: https://api.twitter.com/oauth2/token
The request must be an HTTP POST request.
The request must include an Authorization header with the value of Basic <base64 encoded value from step 1>.
The request must include a Content-Type header with the value of application/x-www-form-urlencoded;charset=UTF-8.
The body of the request must be grant_type=client_credentials.
Example request (Authorization header has been wrapped):
POST /oauth2/token HTTP/1.1
Host: api.twitter.com
User-Agent: My Twitter App v1.0.23
Authorization: Basic eHZ6MWV2RlM0d0VFUFRHRUZQSEJvZzpMOHFxOVBaeVJn
NmllS0dFS2hab2xHQzB2SldMdzhpRUo4OERSZHlPZw==
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 29
Accept-Encoding: gzip
grant_type=client_credentials
If the request was formatted correctly, the server would respond with a JSON-encoded payload:
Example response:
HTTP/1.1 200 OK
Status: 200 OK
Content-Type: application/json; charset=utf-8
...
Content-Encoding: gzip
Content-Length: 140
{"token_type":"bearer","access_token":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
For more informtaion look this in Twitter Developer API: https://developer.twitter.com/en/docs/authentication/oauth-2-0/application-only
I am trying to make a CURL request with some basic authentication (through sending an encrypted header). However, when I try to set the "Host: _" header, the remote server responds with:
Bad Request: Your browser sent a request that this server could not understand.
Here's the code that makes the CURL call. Note that it works as soon as I comment out the "Host: url" header in the $http_header variable. However, it is used on the target server as part of the authentication procedure, so I can not simply remove it.
$curl = curl_init();
$opt = array(
CURLOPT_URL=>$url,
CURLOPT_RETURNTRANSFER=>1,
CURLOPT_HTTPHEADER=>$http_header,
CURLOPT_POST=>1,
CURLOPT_POSTFIELDS=>$data_params,
CURLOPT_CONNECTTIMEOUT=>5, // Timeout to 5 seconds
);
curl_setopt_array($curl, $opt);
// $output contains the output string
$output = curl_exec($curl);
// it closes the session
curl_close($curl);
The contents of $http_header (an associative array):
array
0 => string 'Host: http://localhost/work/myproject'
1 => string 'Content-Type: multipart/form-data'
2 => string 'X-Authorization: Basicauth Z2xZjA2YzJhMzIxYTI1ZmIzZTgxYQ=='
The Host header doesn't accept a full URL, but only a hostname.
In this case the solution is to replace this header:
"Host: $url"
with:
"Host: ". parse_url($url, PHP_URL_HOST) ."
The Host header takes a hostname, not a URL. i.e. localhost, not http://localhost/work/myproject.
cURL should be generating it automatically from CURLOPT_URL though.
The Host header should not contain the protocol, only the host's name. So in this case all you need is localhost:
'Host: localhost'
I don't think either answer answers the asker's question. I've ran into the same problem. From looking at what cURL is sending, it is sending it's own Host header. This means that when you add one you now have two. All servers MUST respond with a 400 error when they see more than one host header.
The trick is get cURL not to generate it's own header (which I haven't figured out how to do yet) or don't send yours. In your case it seems not sending yours is fine. But, for what I'm doing, I have to send mine.
We're using Commission Junction's REST service, which requires we sent an API key in the Authorization header.
We set the header like this:
$ch = curl_init();
curl_setopt_array($ch, array(
// set url, timeouts, encoding headers etc.
CURLOPT_URL => 'https://....',
// ...
));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Authorization: ' . CJ_API_KEY,
'User-Agent: ' . OUR_USER_AGENT
));
$response = curl_exec($ch);
$info = curl_getinfo($ch);
The problem is that the Authorization header isn't sent (we debugged this by using a local url and doing a var_export($_SERVER) which shows a User-Agent header is set, but not the Authorization header.)
If we change the header name to X-Authorization, it gets sent - but this hasn't helped us as the service specifically requires the Authorization header.
How do we get PHP + cURL to send an arbitrary Authorization header?
The Authorization header isn't included in PHP's $_SERVER variable. To properly debug a request you should use apache_request_headers() which shows we were sending the Authorization header exactly as we wanted.
The problem then moved on to figuring out exactly what to put in the Authorization header given some pretty bad documentation.
When the header is set by the client, then the Authorization-header from the request is included in $_SERVER — not sure if this is something new, but it is now. HTTP-headers get prefixed in the $_SERVER array with HTTP_ which may be something you previously overlooked.
Also, apache_request_headers() is a function which is only defined when you use Apache as a web server. So everyone with nginx etc. is left out.
Demo
On the server-side:
<?php
// server.php
var_dump($_SERVER['HTTP_AUTHORIZATION']);
Test
Start a webserver (requires PHP 5.4):
$ php -S 0.0.0.0:31337 -t .
Make sure server.php is in the current directory.
Use cURL to test:
$ curl -H 'Authorization: FOO' http://0.0.0.0:31337/server.php
string(3) "FOO"
Works. :)