Thursday, November 24, 2011

Technical: Using REST web services with 2-legged OAuth on datapower.

2-legged OAuth consists of:
•    The client signs up to the server. The server & client has a shared secret.
•    The client uses these key to gain access to resources on the server

What is REST ?

GET method
The client(secret=password) sends the following request to datapower.
Request: GET http://testname:1010/testname?name=KIM
Header: Authorization: OAuth     oauth_nonce="12345abcde",
                                                  oauth_consumer_key="Kim",
                                                  oauth_signature_method="HMAC-SHA1",
                                                  oauth_timestamp="1319032126",
                                                  oauth_version="1.0",
                                                  oauth_signature="m2A6bZejY7smlH6OcWwaKLo7X4o="

oauth_nonce = A randomly-generated string that is unique to each API call. This, along with the timestamp, is used to prevent replay attacks.
oauth_consumer_key = the client’s key.
oauth_signature_method = the cryptographic method used to sign.
oauth_timestamp = the number of seconds that have elapsed since midnight, January 1, 1970, also known as UNIX time.
oauth_signature = The authorization signature.

On datapower a Multi-protocol gateway needs to be configured allowing requests with an empty message body.

The secret needs to be shared between client and datapower. To make everything easy I stored the shared secret in an xml file in the “local:” file system of datapower.

The next step is verifying the signature. The best way to verify the signature is recreate the signature with the shared secret and then compare the two signatures. Of course to do this, some xslt skills are recommended.
This is the signature base-string that will be signed with the shared secret.

GET&http%3A%2F%2Ftestname%3A1010%2Ftestname&name%3DKIM%26oauth_consumer_key%3DKim
%26oauth_nonce%3D12345abcde%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1319032126%26oauth_version%3D1.0

How do we get to this?
If you look closely you see that there are 3 distinguished parts.
  1. GET& 
  2. http%3A%2F%2Ftestname%3A1010%2Ftestname& 
  3. name%3DKIM%26oauth_consumer_key%3DKim%26oauth_nonce%3D12345abcde%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1319032126%26oauth_version%3D1.0
The first part is fairly straightforward.
    GET = the http method used.

The second part is the URL which is url-encoded.
We’ll need to percent encode all of the parameters when creating our signature. These "reserved" characters must be converted into their hexadecimal values and preceded by a % character. The rest of the characters are "unreserved" and must not be encoded.
The reserved characters are:
!   *   '   (   )   ;   :   @   &   =   +   $   ,   /   ?   %   #   [   ]  white-space
These are the percent-encoded values of the above characters:
%21 %2A %27 %28 %29 %3B %3A %40 %26 %3D %2B %24 %2C %2F %3F %25 %23 %5B %5D %20
Be careful with URL encoding functions built into languages, they may not encode all of the reserved characters, or may encode unreserved characters.
    <xsl:variable
     name="BasicUrl"
     select="dp:encode(string(
     dp:variable('http://testname:1010/testname’)
        ),'url')
    "/>


The third part gets a bit more complicated to do with xslt, because the number of parameters and OAuth header fields can change.
We need to extract the Oauth header from the request.
     <xsl:variable
      name="OAuthParams"
      select="dp:request-header('Authorization')"
     />

The URL parameters are also necessary. The parameters in this example are “name=KIM”. Put them all together and alphabetically organize them. You will get a string like this:
name=KIM&oauth_consumer_key=Kim&oauth_nonce=12345abcde&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1319032126&oauth_version=1.0
And again url-encode this string using the datapower function(dp:encode).If you now concatenate the 3 parts with a “&” you will get the signature base string.

The next thing we need to calculate the signature is the shared secret. OAuth standard uses a concatenation of the shared secret & token(<secret>&<token>). Since we are working with the 2-legged OAuth we don’t need the token and we can leave it empty. So in the example we used the shared secret “password”. Important is that you add the “&” behind the secret: ”password&”. Now we have the necessary information to calculate the signature.
   <xsl:variable name="Calculate_Signature"
    select="dp:hmac('http://www.w3.org/2000/09/xmldsig#hmac-sha1',
    ‘password&’,
   <SignatureBaseString>)"/>


Finally we can compare the signature of the OAuth header with our own calculated signature. Don’t forget to validate the timestamp against a predefined limit e.g. 5 minutes. If all checks out, the client is authorized to use the web service.

link to check OAuth signature

POST method is coming soon…

Author: Kim

14 comments:

  1. Great article. Have you looked at the new 5.0 release? I am trying to find some samples for two legged and three legged with the oauth 2.0 support in datapower

    ReplyDelete
  2. Thx for your comment, unfortunately I haven't had the possibility to explore the new release 5.0 yet…

    ReplyDelete
  3. Like the write up, but I'm using 5.0 and the hmac() function will not allow me to pass an ampersand with the secret key...
    Actually within xsl it won't allow that at all - it has to be appended as "&-a-m-p-;" or similar -
    Then you have to get past the error -
    *base64 decode of shared secret key failed*
    Which is where I happen to be stuck at right now...

    ReplyDelete
  4. Hi Alan,

    I've verified my code and noticed that I used a shortcut in this blog.
    Can you try this?
    ================ Secret + token - url encode ================
    xsl:variable name="ConsumerSecret" select="dp:encode( string(dp:variable('var://context/Frontend/ConsumerSecret')) , 'url' )"

    xsl:variable name="ConsumerToken" select="dp:encode( string(dp:variable('var://context/Frontend/ConsumerToken')) , 'url' )"
    ====================== Concatenate ==========================
    xsl:variable name="Secret1" select="concat( $ConsumerSecret , '&' , $ConsumerToken )"
    ====================== Encode ===============================
    xsl:variable name="Secret" select="dp:encode( $Secret1 , 'base-64' )"
    ================ Calculate Signature =========================
    xsl:variable name="CalculatedSIGN1" select="dp:hmac('http://www.w3.org/2000/09/xmldsig#hmac-sha1',$Secret,$SignatureBaseString)"

    xsl:variable name="CalculatedSIGN" select="dp:encode( string($CalculatedSIGN1), 'url' ) "

    greetZ
    Kim

    ReplyDelete
  5. Thanks Kim, unfortunately when $Secret1 is base-64 encoded, it will generate a different signature that won't validate with my service host (a Yahoo BOSS geocode service.)
    I have a .NET client that works fine - I'm just trying to proxy the call with DataPower so the internal client doesn't need to know the oAuth credentials... this one little snag has really got me baffled...

    ReplyDelete
  6. I was finally able to get this to work by appending an ampersand to the secret password, converting it to hex and passing it as such in the hmac function... 'hex:37626339396663393236666365663226'
    Thanks!

    ReplyDelete
  7. Hi Kim,
    I am trying implement same but need some more information on this. Do you have some sample code for above scenario. Also I want to know where to set this base authorization string in http headers

    ReplyDelete
    Replies
    1. Hi Sudhir
      The base authorization string is a concatenation of the http method, url and all oauth header fields in the request. You need to create this string using xslt.

      GET&http%3A%2F%2Ftestname%3A1010%2Ftestname&name%3DKIM%26oauth_consumer_key%3DKim
      %26oauth_nonce%3D12345abcde%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1319032126%26oauth_version%3D1.0

      After creating the signature using the base string + shared secret, You can compare the signature with the signature in the request.

      I have to double check were I stored the code, but I'm sure that I still have it.
      How do you want me to deliver this to you?

      FYI, Oauth 2.0 is supported starting from Datapower version 5.0. So maybe it's worth to have a look at it.
      http://www.ibm.com/developerworks/websphere/library/techarticles/1208_poon2/1208_poon2.html

      Best regards,
      Kim

      Delete
  8. Hi Kim,
    Thanks for quick response. Is possible to deliver the sample code on my email id (sudhir.gomase@in.ibm.com)

    The Rest API that I want to invoke uses Oauth 1.0
    Regards
    Sudhir Gomase

    ReplyDelete
    Replies
    1. Sudhir,

      Can you verify your mail? I get back an unknown email address..

      thx
      Kim

      Delete
    2. Kim in the code That u have sent to sudhir REST ReadConfig.xsl file is missing can u pls send that

      Rakesh

      Delete
  9. Hi Kim,

    Correct email id is sudhir.gomase@gmail.com.

    Actually I noticed that I put incorrect email id then immediately I posted correct email id but it seems that second post is not posted successfully

    ReplyDelete
  10. Hi Kim ,
    Could you please send the sample code on my email id sudhir.gomase@gmail.com

    Regards
    Sudhir Gomase

    ReplyDelete
  11. Hi Kim,
    Thanks for sharing the code. But we are still facing HTTP 401 - Authorization error.
    Is it possible to have call with so I will explain you what exactly we are doing and after discussion, you might come to know that where things are going wrong. I am available on +912067061012 at 13:00 Indian time
    Regards
    Sudhir Gomase

    ReplyDelete