Implementing OAuth In Scala
by Justin Michalicek on Aug. 2, 2011, 3:34 a.m. UTCSo I really liked Scala when I worked with it in the Seven Languages book. I've also been meaning to write an OAuth library for some time. I decided that this would be an interesting first project in Scala. It's not overly complex, but not so simple that I won't learn anything either.
So far I've written just enough to make the first request for a request_token from an OAuth provider. I used traits, treating them more or less like a Java abstract class, to create classes to sign the messages using HMAC-SHA1 or plaintext methods and will be adding the RSA-SHA1 option eventually.
There seems to be a lot of confusion on how to generate the HMAC-SHA1 signature in Java (and so Scala). I saw several different examples of what to do and none of them worked. The code below is what I eventually came to. This has worked to authenticate to both Twitter and Yahoo.
Sorry for the funky formatting. I need to change my CSS up plus copying and pasting formatted text in and then formatting with markdown just gets a git off at times.
package jm.oauth.messagesigner import jm.oauth.MessageSigner import java.net.URLEncoder import org.apache.commons.codec.digest.DigestUtils //nicer implementation to work with than java.security.MessageDigest import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Base64 import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import scala.collection.immutable.SortedMap class HmacSha1 extends MessageSigner{ //May compact the url, method, and params to a single object override def createSignature(key: String, token: String, method: String, url: String, requestParams: Map[String, String]): String = { //First create a SortedMap which is sorted on the key from our Map //and then feeds that into map() to combine key and value, then into reduce to join each k,v pair with an & val sorted = SortedMap(requestParams.toList:_*) val sigString = method.toUpperCase() + "&" + URLEncoder.encode(url) + "&" + URLEncoder.encode(sorted.map(p => p._1 + "=" + p._2).reduceLeft{(joined,p) => joined + "&" + p}) return new String(Base64.encodeBase64(generateSHA1Hash(sigString, key, token).getBytes())) } def generateSHA1Hash(value: String, key: String, token: String): String = { //When token is null it gets cast to the string "null" if it is just concatenated it in here val keyString = URLEncoder.encode(key) + "&" + (token match { case x if x != null => token case x => "" }) val keyBytes = keyString.getBytes(); val signingKey = new SecretKeySpec(keyBytes, "HmacSHA1") val mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); val rawHmac = mac.doFinal(value.getBytes()); return new String(rawHmac) } }
The key problem left to this part is that apparently the OAuth spec does not say what should be returned from the request to get the request_token. Twitter and Yahoo both return different things, which makes it a bit difficult to create a generic return value. I was going to just return a Tuple of the values, but I will probably have to return a Map instead.
If you want to follow this, I've got the code up on my git repo and if I take it far enough to be useful, will probably also put it on github eventually.