OAuth 1.0 Authorization sign process
Hello community,
Just wanted to share..
While requested to access some service I was told to authenticate my request using OAuth 1.0 (revision A) with HMAC-SHA256 as a signing method. And knowing little to none about it. I started by testing it first in Postman, and all worked just fine. BUT with Object Script, well.. a big smile goes here! :) as I had some issues and no one to suggest..
In the end the most helpful thing to do was to refer the docs here : https://oauth.net/core/1.0a/ while the signature method in it is for HMAC-SHA1 and RSA-SHA1 it is valid for HMAC-SHA256 as well.
lets declare some parameters - required to the sign process: (not real values ofc...)
In my case, I also use encoded parameters in Authorization header
Knowing all this data, we can see in the code snippet in Postman the generated result for our sign under the oauth_signature param. in this case the result is mdmQ6T%2BMSgWnKaRfjms4U89iBG9tgDudg15Q7%2FMNGwk%3D
So lets construct our Object Script version to get this result
//paramsset nonce = "s3fr5drk83kde3"//..getNonce() // random stringset timestamp = 1696497844//$zdatetime($horolog,-2)set consumerKey = "cons123key321"set customerSecret = "conssecret123"set signatureMethod = "HMAC-SHA256"set version = "1.0"set token = "acc999token456"set tokenSecret = "toksec234234"set httpMethod = "GET"set url = "https://www.somerandom123.com/noplace/"next we need to construct the message to sign: The documentations dictate the following structure :
[payload to sign as string] = <httpMethod>&<encodedUrl>&<encodedParams>
lets start with the params: as stated in the docs we need to populate and sort "oauth_*" variables delimited with '&' ($char(38))
and the result must be encoded (uri).
//params string build//sorted array (required !), as vectores are dictionary sorted for strings//all is goodset dictionaryArray("oauth_consumer_key") = consumerKey
set dictionaryArray("oauth_nonce") = nonce
set dictionaryArray("oauth_signature_method") = signatureMethod
set dictionaryArray("oauth_timestamp") = timeStamp
set dictionaryArray("oauth_token") = token
set dictionaryArray("oauth_version") = version
// messy but okset pos = 0set paramString = ""set key = ""for {
set key = $order(dictionaryArray(key),1,value)
quit:(key = "")
set$piece(paramString,"&",$increment(pos)) = key_"="_value
}keep in mind that the service provider can ask for some additional params to accompany the "oauth_*" , just add them to the array...
to achieve the required encode, in JavaScript, the use is :
let myEncodedMessage = encodeURIComponent(myMessage);in Object Script it is a bit different - most likely due to my cache version.
set paramStringUtf8 = $zconvert(paramString,"O","UTF8")
set encodedParamString = $zconvert(paramStringUtf8,"O","URL")but the result of the two command lines above will not be sufficient if we have "/" char in the message string. This off course will happen in the encoded URL part of our message to sign.
e.g.
set urlUtf8 = $zconvert(url,"O","UTF8")
set encodedUrl = $zconvert(urlUtf8,"O","URL")
set encodedUrlEscape = $replace(encodedUrl,"/","%2F")
write !,"url = "_url,!," utf8 = "_urlUtf8,!," encodedUrl ="_encodedUrl,!," encodedUrlEscape ="_encodedUrlEscape
/*output---------------------------------------------------------
url = https://www.somerandom123.com/noplace/
utf8 = https://www.somerandom123.com/noplace/
encodedUrl =https%3A//www.somerandom123.com/noplace/
encodedUrlEscape =https%3A%2F%2Fwww.somerandom123.com%2Fnoplace%2F
----------------------------------------------------------------*/so I had to forcefully replace "/" to the encoded "%2F" to make sure the result is right.
set encodedUrlEscape = $replace(encodedUrl,"/","%2F") //requiredand after we encoded our params and url the constructed message to sign will look like :
set baseString = httpMethod_"&"_encodedUrlEscape_"&"_encodedParamsString
write !,baseString
GET&https%3A%2F%2Fwww.somerandom123.com%2Fnoplace%2F&oauth_consumer_key%3Dcons123key321%26oauth_nonce%3Dcons123key321%26oauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3D1696497844%26oauth_token%3Dacc999token456%26oauth_version%3D1.0with our message constructed (baseString) we need to construct the key that will sign it.
the structure is: [key] = <customerSecret>&<tokenSecret>
set cipherKey = customerSecret_"&"_tokenSecretsign the message:
set HMACSHA256 = $system.Encryption.HMACSHA(256,baseString,cipherKey) //the HMACSHA256 will result in chars like stream , so we need to encode it to base64.set HMACSHA256Base64 = $system.Encryption.Base64Encode(HMACSHA256)
write !,HMACSHA256Base64
mdmQ6T+MSgWnKaRfjms4U89iBG9tgDudg15Q7/MNGwk=
//and if we want to Encode parameters in Authorization headerset HMACSHA256Base64Url = $zconvert(HMACSHA256Base64,"O","URL")
write !,HMACSHA256Base64Url
mdmQ6T%2BMSgWnKaRfjms4U89iBG9tgDudg15Q7/MNGwk%3Dand we got the required value to put in "oauth_signature" param.
all that left is to construct the Authorization header and run the http request to the required service. keep in mind that here the param values are quoted.
full example of our steps:
set nonce = "s3fr5drk83kde3"//..getNonce() // need to be random uniqueset timeStamp = 1696497844//$zdatetime($horolog,-2)set consumerKey = "cons123key321"set customerSecret = "conssecret123"set signatureMethod = "HMAC-SHA256"set version = "1.0"set token = "acc999token456"set tokenSecret = "toksec234234"//sorted array (required !)set dictionaryArray("oauth_consumer_key") = consumerKey
set dictionaryArray("oauth_nonce") = nonce
set dictionaryArray("oauth_signature_method") = signatureMethod
set dictionaryArray("oauth_timestamp") = timeStamp
set dictionaryArray("oauth_token") = token
set dictionaryArray("oauth_version") = version
//make an encoded (url) param stringset pos = 0set paramString = ""set key = ""for {
set key = $order(dictionaryArray(key),1,value)
quit:(key = "")
set$piece(paramString,"&",$increment(pos)) = key_"="_value
}
set paramStringUtf8 = $zconvert(paramString,"O","UTF8")
set encodedParamString = $zconvert(paramStringUtf8,"O","URL")
// case we have "/" - the uri conversion does not work !set encodedParamString = $replace(encodedParamString,"/","%2F")
set httpMethod = "GET"// must be uppercaseset url = "https://www.somerandom123.com/noplace/"set urlUtf8 = $zconvert(url,"O","UTF8")
set encodedUrl = $zconvert(urlUtf8,"O","URL")
// case we have "/" - the uri conversion does not work !set encodedUrl = $replace(encodedUrl,"/","%2F")
// [payload to sign] = <httpMethod>&<encodedUrl>&<encodedParams>set baseString = httpMethod _"&"
_ encodedUrl _"&"
_ encodedParamString
//generally need to do the same manipulation as above ...set cipherKey = customerSecret_"&"_tokenSecret
//SIGNset HMACSHA256 = $system.Encryption.HMACSHA(256,baseString,cipherKey)
//ENCODE B64set HMACSHA256Base64 = $system.Encryption.Base64Encode(HMACSHA256)
//ENCODE URLset HMACSHA256Base64Url = $zconvert(HMACSHA256Base64,"O","URL")
set signature = HMACSHA256Base64Url
//set Authorization headerset authorization = "OAuth "
_"oauth_consumer_key="""_consumerKey_""","
_"oauth_token="""_token_""","
_"oauth_signature_method="""_signatureMethod_""","
_"oauth_timestamp="""_timeStamp_""","
_"oauth_nonce="""_nonce_""","
_"oauth_version="""_version_""","
_"oauth_signature="""_signature_""""set sc = ..runHttpGET(url,authorization,.out) //your custom method for whatever %Net.HttpRequest you use.. hope it helped some one to save some time.
Comments
Absolutely fantastic and invaluable. Thank you so much.