I am trying to communicate with my Xero account through Xero API, a simple bash script and cURL. Also, I am working with a Xero Private App, this means I have already generated a public/private keypair, the public key uploaded to Xero and the private being used in my procedure.
Keypair generated as suggested here https://developer.xero.com/documentation/api-guides/create-publicprivate-key:
openssl genrsa -out privatekey.pem 1024
openssl req -new -x509 -key privatekey.pem -out publickey.cer -days 1825
openssl pkcs12 -export -out public_privatekey.pfx -inkey privatekey.pem -in publickey.cer
The following is my code and thought process.
First, I create the parameters for the OAuth request. I know Xero Private Apps work with OAuth1.0a and need to be signed with RSA-SHA1.
oauth_consumer_key="my_key"
oauth_nonce="$(($(date +%s) - 10000))"
oauth_signature_method="RSA-SHA1"
oauth_timestamp="$(date +%s)"
oauth_token="my_key"
oauth_version="1.0"
Now, I focus on generating the OAuth Signature as explained clearly in https://oauth1.wp-api.org/docs/basics/Signing.html. I make sure to create a base string using Method
(I call it verb
), URL
and Params
. I make sure that Params
are sorted by name. Also, I URL_encode these values before concatenating with &
.
verb=GET
url=https://api.xero.com/api.xro/2.0/Invoices/243216c5-369e-4056-ac67-05388f86dc81
params=oauth_consumer_key=$oauth_consumer_key\&oauth_nonce=$oauth_nonce\&oauth_signature_method=$oauth_signature_method\&oauth_timestamp=$oauth_timestamp\&oauth_token=$oauth_token\&oauth_version=$oauth_version
baseString=$(urlencode $verb)\&$(urlencode $url)\&$(urlencode $params)
echo $baseString
returns
GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2FInvoices%2Fe4d08842-29fc-4228-8227-8661e0f93ea3&oauth_consumer_key%*%26oauth_nonce%3D1523125307%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1523135308%26oauth_token%*%26oauth_version%3D1.0
urlencode function:
function urlencode() {
echo -n "$1" | perl -MURI::Escape -ne 'print uri_escape($_)'
}
I sign the baseString
using OpenSSL as follows.
oauth_signature=$(echo -n "$baseString" | openssl dgst -sha1 -sign "C:\path\to\keys\privatekey.pem" | openssl enc -A -base64)
echo $oauth_signature
Now, I create the Authorization header, with the same parameters but including the signature which has just been generated.
auth_header="Authorization: OAuth oauth_consumer_key=\"$oauth_consumer_key\", oauth_nonce=\"$oauth_nonce\", oauth_signature=\"$oauth_signature\", oauth_signature_method=\"$oauth_signature_method\", oauth_timestamp=\"$oauth_timestamp\", oauth_token=\"$oauth_token\", oauth_version=\"$oauth_version\""
echo $auth_header
returns
Authorization: OAuth oauth_consumer_key="*", oauth_nonce="1523124975", oauth_signature="*", oauth_signature_method="RSA-SHA1", oauth_timestamp="1523134975", oauth_token="*", oauth_version="1.0"
Finally, I send a GET request via cURL to get a specific invoice.
curl -G "https://api.xero.com/api.xro/2.0/Invoices/243216c5-369e-4056-ac67-05388f86dc81" -H "$auth_header"
And I receive...
oauth_problem=signature_invalid&oauth_problem_advice=Failed%20to%20validate%20signature
Where I have expected a JSON response with the invoice or something telling me that the invoice does not exist.
I feel that I have followed the steps correctly. Well, I guess there is a problem with the signature. Either the thing it was signing wasn't formed correctly or a problem with RSA-SHA1? I'm at a loss. I would appreciate any feedback.
EDIT: Thanks to @rustyskates, I fixed some mistakes, however, now I get:
<ApiException xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ErrorNumber>500</ErrorNumber>
<Type>UnknownErrorException</Type>
<Message>An error occurred in Xero. Check the API Status page http://status.developer.xero.com for current service status. Contact the API support team at api@xero.com for more assistance.</Message>
</ApiException>
which still looks like a problem because Xero doesn't report operational problems and many others seem to have experienced this too.