Technical Articles
Creating OAuth 1.0 Signature with SCP API Portal
If you’ve worked a lot with APIs this may be a walk in the park for you – but for me it took some good time to figure out how exactly to do this. So I thought I share my experience and solution. But please keep in mind…
Disclaimer:
! I do not claim that the below guide is best practice or the best way to do OAuth 1.0 requests signing. The below guide contains a possible solution that worked for me !
Use-Case and API details
I have subscribed to an open REST API which I would like to use in a Fiori app. The API provider requires all requests to be signed properly with OAuth 1.0, using the HMAC-SHA1 signature algorithm. When I subscribed to the API, I received following data:
- API Endpoint: https://provider/api (I’m not sure if it’s ok to give the correct provider address so I’m using a fictional one)
- API Consumer key: abcd1234 (demo value)
- API Consumer secret: 1234zzzz5678 (demo value)
And here are the instructions on how to sign OAuth 1.0 requests (rewritten):
Create a Signature Base String by concatenating the HTTP method (GET or POST), the Request URL, and your query parameters in the following format:
<HTTP Method>&<Request URL>&<Normalized Parameters>
For OAuth authentication the following normalized parameters are required for every request:
- oauth_consumer_key: your consumer key as sent upon registering
- oauth_signature_method: must be “HMAC-SHA1”
- oauth_timestamp: The date and time, expressed in the number of seconds since January 1, 1970 00:00:00 GMT. The timestamp value must be a positive integer and must be equal or greater than the timestamp used in previous requests
- oauth_nonce: A randomly generated string for a request that can be combined with the timestamp to produce a unique value
- oauth_version: Must be “1.0”
Parameters are written in the format “name=value” and sorted using lexicographical byte value ordering. Finally the parameters are concatenated in their sorted order into a single string, each name-value pair separated by an ‘&’ character.
Calculating signature and adding as parameter:
- oauth_signature: calculated signature using the HMAC-SHA1 signature algorithm where text is the Signature Base String and key is the concatenated values of the Consumer Secret and Access Secret separated by an ‘&’ character (even if there is no Access Token required for a request, show ‘&’)
Above shows clearly that each request requires quite a bit of work and parameters to function. Therefore, for me the best way to do this, was in the SAP Cloud Platform API Management.
SAP Cloud Platform API Management Setup
Create API
First of we need to create our API in the API portal.
- Log into your SCP and navigate to the API Management Portal
- Go to the developer tab, select APIs and click on Create.
- Select URL from the radio buttons
- Enter the API Endpoint as URL, for example: https://provider/api
- Give a name and title and click Create
Setup API Policies
We will be making use of policies, in order to not have to deal with all the parameters and calculating a signature, every single time in our app when we want to call the API. Instead we will make sure that the API policies in the the inbound pre-flow, will add all the required parameters and will sign the request, so that the API provider accepts the requests sent.
How do you do that? Well honestly I had no idea myself, before I started this, but it’s not that bad in the end. Ask yourself; what is it exactly, you are trying to do? For this use case the answers are:
- I want to modify the request before it goes to the actual API provider
- I need to add static and dynamic (such as timestamp and nonce) parameters into the request URL
- I need to calculate a signature and add it as parameter into the URL
Not too bad. Let’s tackle the first and most simple part: adding URL parameters into the request, before it goes to the API provider.
Once your API is deployed, click on Policies button as shown below (I believe you need to deploy it once first, before you can add policies but I may be wrong and I don’t fully remember – either way if you don’t see the Policies button, deploy your API) – note that the Policies button may be hiding under the “three-dots-button”:
You are now in the Policy Editor. On the left-hand-side you can see the “flows”, into which you can add policies. We already identified the one to use for this use-case: TargetEndpoint PreFlow (before sending to actual API provider).
Part 1 – Add URL Parameters
On the right-hand-side you can see different policies to add. We want to add parameters into the request URL and for that we use the one called “Assign Message“. And here’s how to do it:
- Make sure you are in Edit mode, otherwise click on the Edit button in the upper right corner
- Click on TargetEndpoint – PreFlow
- Find the Assign Message policy (shown below) and add
- Give the policy a name: I called mine Set_OAuth1_Params
- Click on the policy in the flow to change it
The policy will already have some example code for you but here is what we need for the OAuth 1.0 request – we add all the extra parameters:
<!-- This policy can be used to create or modify the standard HTTP request and response messages -->
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
<Add>
<QueryParams>
<QueryParam name="oauth_nonce">{nonce}</QueryParam>
<QueryParam name="oauth_consumer_key">{consumer}</QueryParam>
<QueryParam name="oauth_signature_method">{oauth_method}</QueryParam>
<QueryParam name="oauth_timestamp">{timestamp}</QueryParam>
<QueryParam name="oauth_version">{oauth_version}</QueryParam>
<QueryParam name="oauth_signature">{sig}</QueryParam>
</QueryParams>
</Add>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<AssignTo />
</AssignMessage>
What does the above mean? Very simple: in the “Add” tag, we add new url (aka query) parameters by using the “QueryParams” and “QueryParam” tags per parameter.
Next we give the parameter a name and before the tag ends, we give the parameter a value. We could surely set here some of the parameters required by the API as described above, such as the OAuth version, which is always “1.0”. However, certain parameters are dynamically set and depend on the request itself. So I opted for all extra parameters, using a variable value. As seen above you simply access a variable with curly-brackets: {VariableName}.
If we would test our API now, through this policy following would happen:
- If my request is…
https://provider/api?giveme=somedata
- …the API management would forwards this to the endpoint as:
https://provider/api?giveme=somedata&oauth_nonce=&oauth_consumer_key= ...
All parameters are added but empty: that is because the variables we use above, are not yet set since we just “invented them”. So now we need a bit of code to get the dynamic values and to set all the variables, we referenced in our Assign Message policy.
Part 2 – Set Dynamic and Static Parameter Values
Before our first policy runs, we need to add another policy to the inbound pre-flow: since we need to write some code and myself I’m best used to JavaScript, I’ll be using the “JavaScript Extension Policy“.
- Click again on TargetEndpoint – PreFlow
- Select the JavaScript policy under Extension Policies (far down on the right-hand-side)
- Give the policy a name: I named mine: Set_OAuth1_Dynamic
- Make sure you move the policy before the first one we created (via the arrow buttons)
Once again we get some helper code directly and we only need to change a little bit (this time, for Part 3 we change some more):
<!-- this policy allows us to execute java script code during execution of an API Proxy -->
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" xmlns='http://www.sap.com/apimgmt'>
<!-- contains reference to any library scripts that help the main code file
<IncludeURL>jsc://help.js</IncludeURL> -->
<!-- contains the name of the main code file -->
<ResourceURL>jsc://oauth1.js</ResourceURL>
</Javascript>
I made 2 changes:
- Don’t use IncludeURL and instead comment it away
- Rename the ResourceURL to oauth1.js
Next we need to create the javascript file we just referenced:
- At “Scripts” on the left-hand-side (below the flows and “Created Policies”), click on the plus button on the right
- Name the file oauth1 (or whichever name you gave it, but without the “.js” part)
- Type is JavaScript
- At Script, instead of Import (default) choose Create
This will open a blank code editor. Here now we can access the request and set the variables, we use in the other policy. Below code only shows the static and dynamic variables, not the OAuth 1 signature (this we will do in part 3). Only copy-pasting this part will not be enough!:
// set static OAuth 1.0 variables
var sConsumer = "abcd1234",
sSecret = "1234zzzz5678",
sMethod = "HMAC-SHA1",
sVersion = "1.0";
// get timestamp in seconds
var seconds = parseInt( new Date().getTime() / 1000 , 10),
sTimestamp = seconds.toString();
// create random string for OAuth nonce
var sNonce = "",
sPossible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(var i = 0; i < 10; i++) {
sNonce += sPossible.charAt(Math.floor(Math.random() * sPossible.length));
}
// set variables to be used in Set_OAuth1_Params policy
context.setVariable("nonce", sNonce);
context.setVariable("timestamp", sTimestamp);
context.setVariable("consumer", sConsumer);
context.setVariable("oauth_method", sMethod);
context.setVariable("oauth_version", sVersion);
- First of I set my consumer key and secret: here maybe you would prefer that the Fiori app actually sends this information instead. That would also be very possible; you could use the Extract Variables policy to get them from the request and put them into variables there. But personally, I thought it better, that the API Management has this information and I don’t have to set consumer key and secret in my app. Also please note, that this key and secret is not to be confused with the access secret, you may want to use to protect your currently developed API here, from the API portal. This is the key and secret from the Target Endpoint – from the actual provider which must be given, to even receive a response from the target, without an error code!
- Next I’m setting the static values for the signature method and OAuth version (you will understand later why they are variables in the code; they will be used more than once)
- Next step is to calculate current date and time in seconds. This is done with javascript’s Date() and getTime(), divided by 1000 to get seconds, instead of milliseconds. We also convert this into a positive integer and finally, into a string for the url parameter.
- We generate a nonce string by giving alphanumeric characters as possible characters in a string and then randomly adding 10 characters into the nonce string, by using Math.random()
- Lastly, we set the variables we use in the Set_OAuth1_Params policy with our javascript code variables (all but the variable we called “sig” for signature – this is in part 3)
If we would test our API now, using both policies the URL parameters would have the proper values (this can be double checked in the Debug mode, which shows all variables set per policy!) and the API provider responds with:
Invalid signature: ""
If this is the case for you, that is in fact a good sign! It means all required parameters are sent correctly to the API Endpoint and only the signing is missing.
Part 3 – Calculate Signature and sign request
Now that all static and dynamic parameters are done correctly, we can worry about the signature signing. As written in above chapter, the signature needs to be calculated in a certain way: short version:
<HTTP Method>&<Request URL>&<Normalized Parameters>
Above gives the signature base string, which needs to be encrypted with HMAC-SHA1. How can we do this in Javascript? Luckily, there is a library for this called CryptoJS which can be found at Google Code. We will need 2 files from CryptoJS:
- hmac-sha1.js for using the HMAC-SHA1 encryption algorithm
- enc-base64.js for encoding the encryption and put it into a string format, to be able to send it as URL parameter
Let’s setup CryptoJS:
- Download the named files
- Go back to your Policy Editor and change the Set_OAuth1_Dynamic policy: we now add two IncludeURL tags for both files we just downloaded – IMPORTANT is that the hmac-sha1.js is first, since enc-base64,js needs it!
<!-- this policy allows us to execute java script code during execution of an API Proxy --> <Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" xmlns='http://www.sap.com/apimgmt'> <!-- contains reference to any library scripts that help the main code file --> <IncludeURL>jsc://hmac-sha1.js</IncludeURL> <IncludeURL>jsc://enc-base64.js</IncludeURL> <!-- contains the name of the main code file --> <ResourceURL>jsc://oauth1.js</ResourceURL> </Javascript>
- Under “Scripts” on the left-hand-side, click the plus button again, use Import this time and add both the downloaded files. Make sure you name the files in the same way, as you referenced in the policy above (but again without the “.js” part)
Now we are able to use CryptoJS. So let’s do this:
- Open again your own script file oauth1.js
- Add following code at the very top of the file (this is a javascript function for sorting)
var fnSort = function (a, b) { var s1 = a.toLowerCase(), s2 = b.toLowerCase(); return s1 < s2 ? -1 : s1 > s2 ? 1 : 0; };
- Add following code in between the existing code, just before setting all variables (above comment “// set variables to be used in Set_OAuth1_Params policy”)
// get request url and parameters for OAuth signature signing var sUrl = context.getVariable("target.url"), sQuery = context.getVariable("request.querystring"), sVerb = context.getVariable("request.verb"), sToken = "", // sent as url parameter, but optional aOAuth = [ "oauth_consumer_key=" + sConsumer, "oauth_nonce=" + sNonce, "oauth_signature_method=" + sMethod, "oauth_timestamp=" + sTimestamp, "oauth_version=" + sVersion ]; // split query string into parameters and add yet missing OAuth 1.0 parameters // (which are added in Set_OAuth1_Params policy into the URL, with below set variables) var aParams = sQuery.split("&").concat(aOAuth); // sort parameters alphabetically aParams.sort(fnSort); // create again a parameters string, but with ALL parameters sorted var sParams = ""; for(var i = 0; i < aParams.length; i++) { if(sParams === "") sParams = aParams[i]; else sParams += "&" + aParams[i]; // check for token param (optional parameter)... if(aParams[i].substr(0,11) === "oauth_token") { sToken = aParams[i].split("=")[1]; //...and get token value } } // set OAuth 1.0 signature base string and key var sBase = sVerb + "&" + encodeURIComponent(sUrl) + "&" + encodeURIComponent(sParams); var sKey = sSecret + "&" + sToken; // even if sToken is empty !!! // calculate signature var encrypted = CryptoJS.HmacSHA1(sBase, sKey); var sSignature = CryptoJS.enc.Base64.stringify(encrypted);
- And finally, set the yet missing variables:
if(sToken !== "") context.setVariable("oauth_token", sToken); context.setVariable("sig", sSignature);
Here’s what the code does (under 3.) in words:
- We get the request url and already existing parameters (meaning anything after ?), as a string. For example “giveme=somedata”
- Next we get the HTTP method (GET or POST)
- Then we add all the OAuth 1.0 required parameters as “name=value” pairs into an array
- Next we put all parameters together: the ones send from the original request and the new ones, we will add in our other policy
- As described above, the API provider requires the parameters to be sorted so next we sort the array, using the fnSort function we added to the beginning of the file
- Next we create a string again, this time with all parameters (sorted) as “name=value” pairs and separated by “&” (here we also check for an optional token that could be send via the request and if we find it, we set the token value)
- Now we are ready for the Signature Base String: we concatenate the HTTP method, the request url and all the parameters separated by “&” – important here is to encode both the url and the parameters (this is because they use & and = characters, which would mess up the base string)
- Now we have the “text” but before we can sign the request, we need to set our key: the consumer secret we received from the API provider and the optional token we just set, if found
- Lastly we use CryptoJS to do the calculation work and finally we base64 encode the result and transform it into a usable string
Closure
And would you believe it – that’s it. Now with every single request, that my Fiori app sends, the API policies will add the current timestamp, set a random nonce, add all needed parameters and finally calculate the correct signature for any kind of request (also considering already existing URL parameters). So I won’t have to juggle with lots of extra parameters in my Fiori app and stitch the request together there every time I need to send one: I can just call “the correct URL” and API management, takes care of the rest.
As mentioned earlier some people may not like adding the consumer key and secret into the javascript code. But remember: the consumer for the API Endpoint is you, the developer, who registered for the API, not the user of your app (or usually not for open APIs). Therefore I find this solution very fitting for my use case.
I may add a smaller blog later on, on how to use this API in a Fiori app. But for now I hope this saves some time of some people and helps, to get to know API Management and policies a bit better.
Hi Melanie,
Appreciate your efforts in writing this fantastic blog.
I have similar requirement, I implemented steps mentioned by you but still I get invalid signature?
FYI- In debugging mode, I can see signature getting generated and also getting passed.
Could you please help here?
Regards,
Hemant
Morten Wittrock
Elijah Martinez
Sriprasad Shivaram Bhat
Hello Experts,
Can you please look into this issue? I need to resolve "invalid signature issue" as it is a requirement.