Authenticating with an OAuth 1.0a provider from .NET CF

Last night, I shared my first stab at a mobile Fire Eagle client: a Windows Mobile application which posts location updates to the Yahoo! Fire Eagle service.

A couple of the bits of code were fiddly, and are worth sharing.

In this post, I’ll outline how I perform the code behind the OAuth authentication I described in my last post. Hopefully, this might help anyone else wanting to do something similar.


Before I start, a few quick warnings / comments:

  • I needed to use .NET Compact Framework (.NET CF) – the subset of .NET that you get on Windows Mobile. In a few places, you might see my code doing something in a more long-winded way than might be necessary with the helper classes you get in the full .NET framework
  • I used .NET 2 – this is what comes pre-installed on Windows Mobile 6, so it’s a big help to avoid getting users to have to install .NET 3 on their phone. Again, it means I’ve not taken advantage of some easier ways of doing things if I’d used .NET 3.5
  • I was using Fire Eagle’s auth for mobile applications so the code is based on that authentication sequence
  • I needed to use HMACSHA1 to generate a signature – a class which is missing in the .NET Compact Framework. For my app, I used an implementation from the Community Edition of the (awesome!) Smart Device Framework from OpenNetCF. The API is consistent enough with .NET Standard Framework that if you are writing a desktop application, you can probably ignore this.
  • Big thanks to the contributors of the oauth project on Google Code. The csharp code there isn’t compatible with .NET CF, and isn’t updated for OAuth 1.0a, so isn’t suitable for use as-is, but it was still a massive help in getting me started in understanding how to generate a valid signature. And I did nick a few generic bits from there (In essence, of the code that follows, if it looks sane, I borrowed it from the reference implementation. If it’s gibberish, I probably wrote it myself!)
  • This was my first attempt at writing an OAuth application client, so I’m not saying this is how you should do it… rather this is what I managed to get to work 🙂
  • In reality, the two methods below are quite similar, and in my app, I reused code in more generic methods. However, generic methods can make it harder to understand how specific operations work, so for this post I’ve expanded out methods so you can see exactly what you need to do to get an access token, for example. This is for readability… this isn’t meant to be code for an efficient library.
  • I’ve ripped out the error handling to make the code easier to read in a blog post… but if you wanted to use this for real, you probably want to catch an exception or two 😉

With that out of the way… here is the code.

As mentioned above, I am following the authentication sequence from Fire Eagle’s mobile auth.

Step 1 from that page is Obtaining an unauthorized request token

/* these are the values we want to get from Fire Eagle by the end of this */
String requestToken  = "";
String requestSecret = "";

/* for oauth_timestamp */
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string timestamp = Convert.ToInt64(ts.TotalSeconds).ToString();

/* for oauth_nonce */
/* (obviously you wouldn't create a new Random class every time!) */
string nonce = new Random().Next(123400, 9999999).ToString();

/* parameters to include in the request - get the list from       */
/*  "Obtaining an unauthorized request token"                     */
/* http://fireeagle.yahoo.net/developer/documentation/mobile_auth */
System.Collections.Generic.List<string> parameters = new System.Collections.Generic.List<string>();
parameters.Add("oauth_callback=oob");
parameters.Add("oauth_consumer_key=" + UrlEncode(OAUTHCONSUMERKEY_FOR_MY_APP));
parameters.Add("oauth_nonce=" + UrlEncode(nonce));
parameters.Add("oauth_timestamp=" + UrlEncode(timestamp));
parameters.Add("oauth_signature_method=HMAC-SHA1");
parameters.Add("oauth_version=1.0a");

/* the url to get a request token */
string url = "https://fireeagle.yahooapis.com/oauth/request_token";

/* although not clearly documented, it seems that parameters need to be */
/*  sorted in order for Fire Eagle to accept the signature              */
parameters.Sort();
string parametersStr = string.Join("&", parameters.ToArray());

string baseStr = "GET" + "&" + 
                 UrlEncode(url) + "&" + 
                 UrlEncode(parametersStr);

/* create the crypto class we use to generate a signature for the request */
HMACSHA1 sha1 = new HMACSHA1();
byte[] key = Encoding.UTF8.GetBytes(VAL_OAUTHCONSUMERSECRET + "&" + 
                                    requestSecret); 
if (key.Length > 64)
{
    /* I had to do this to handle a minor bug in my version of HMACSHA1 */
    /*  which falls over if you give it keys that are too long          */
    /* You probably won't need to do this.                              */
    SHA1CryptoServiceProvider coreSha1 = new SHA1CryptoServiceProvider();
    key = coreSha1.ComputeHash(key);
}
sha1.Key = key;

/* generate the signature and add it to our parameters */
byte[] baseStringBytes = Encoding.UTF8.GetBytes(baseStr);
byte[] baseStringHash = sha1.ComputeHash(baseStringBytes);
String base64StringHash = Convert.ToBase64String(baseStringHash);
String encBase64StringHash = UrlEncode(base64StringHash);
parameters.Add("oauth_signature=" + encBase64StringHash);
parameters.Sort();

/* we are ready to send the request! */
string requestUrl = url + "?" + string.Join("&", parameters.ToArray());
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUrl);
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";

string rawData = "";

try
{
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    StreamReader responseStream = new StreamReader(response.GetResponseStream());
    rawData = responseStream.ReadToEnd();
    response.Close();
}
catch (WebException err)
{
    Stream objStream = err.Response.GetResponseStream();
    StreamReader objStreamReader = new StreamReader(objStream);
    rawData = objStreamReader.ReadToEnd();
}

/* if it worked, we should have oauth_token and   */
/*  oauth_token_secret in the response            */
foreach (string pair in rawData.Split(new char[] { '&' }))
{
    string[] split_pair = pair.Split(new char[] { '=' });

    switch (split_pair[0])
    {
        case "oauth_token":
            requestToken = split_pair[1];
            break;
        case "oauth_token_secret":
            requestSecret = split_pair[1];
            break;
    }
}

This gives you requestToken and requestSecret.

Step 2 is Obtaining user authorizations

You give the user requestToken and send them to https://fireeagle.yahoo.net/oauth/mobile_auth/MY_APP_ID.

They enter the request token into the webpage, and hit confirm. This gets them a verification code which they bring back to your application.

You can see screenshots of this in my last post.

Step 3 is Obtaining user-specific access token

This is very similar to getting a request token.

/* these are the values we got from Fire Eagle in step 1 */
String requestToken  = "";
String requestSecret = "";

/* this is the value the user got from Fire Eagle website in step 2 */
String verifier      = "SOMETHING"; 

/* these are the values we want to get from Fire Eagle */
String accessToken   = "";
String accessSecret  = "";

/* for oauth_timestamp */
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string timestamp = Convert.ToInt64(ts.TotalSeconds).ToString();

/* for oauth_nonce */
/* (obviously you wouldn't create a new Random class every time!) */
string nonce = new Random().Next(100000, 9999999).ToString();

/* parameters to include in the request - get the list from       */
/*  "Obtaining user-specific access token"                        */
/* http://fireeagle.yahoo.net/developer/documentation/mobile_auth */
System.Collections.Generic.List<string> parameters = new System.Collections.Generic.List<string>();
parameters.Add("oauth_consumer_key=" + UrlEncode(OAUTHCONSUMERKEY_FOR_MY_APP));
parameters.Add("oauth_verifier=" + verifier);
parameters.Add("oauth_token=" + UrlEncode(requestToken));
parameters.Add("oauth_nonce=" + UrlEncode(nonce));
parameters.Add("oauth_timestamp=" + UrlEncode(timestamp));
parameters.Add("oauth_signature_method=HMAC-SHA1");
parameters.Add("oauth_version=1.0a");

/* the url to get an access token */
string url = "https://fireeagle.yahooapis.com/oauth/access_token";

/* although not clearly documented, it seems that parameters need to be */
/*  sorted in order for Fire Eagle to accept the signature              */
parameters.Sort();
string parametersStr = string.Join("&", parameters.ToArray());

string baseStr = "GET" + "&" + 
                 UrlEncode(url) + "&" + 
                 UrlEncode(parametersStr);

/* create the crypto class we use to generate a signature for the request */
HMACSHA1 sha1 = new HMACSHA1();
byte[] key = Encoding.UTF8.GetBytes(VAL_OAUTHCONSUMERSECRET + "&" + 
                                    requestSecret); 
if (key.Length > 64)
{
    /* I had to do this to handle a minor bug in my version of HMACSHA1 */
    /*  which falls over if you give it keys that are too long          */
    /* You probably won't need to do this.                              */
    SHA1CryptoServiceProvider coreSha1 = new SHA1CryptoServiceProvider();
    key = coreSha1.ComputeHash(key);
}
sha1.Key = key;

/* generate the signature and add it to our parameters */
byte[] baseStringBytes = Encoding.UTF8.GetBytes(baseStr);
byte[] baseStringHash = sha1.ComputeHash(baseStringBytes);
String base64StringHash = Convert.ToBase64String(baseStringHash);
String encBase64StringHash = UrlEncode(base64StringHash);
parameters.Add("oauth_signature=" + encBase64StringHash);
parameters.Sort();

/* we are ready to send the request! */
string requestUrl = url + "?" + string.Join("&", parameters.ToArray());
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUrl);
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";

string rawData = "";

try
{
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    StreamReader responseStream = new StreamReader(response.GetResponseStream());
    rawData = responseStream.ReadToEnd();
    response.Close();
}
catch (WebException err)
{
    Stream objStream = err.Response.GetResponseStream();
    StreamReader objStreamReader = new StreamReader(objStream);
    rawData = objStreamReader.ReadToEnd();
}

/* if it worked, we should have oauth_token and   */
/*  oauth_token_secret in the response            */
foreach (string pair in rawData.Split(new char[] { '&' }))
{
    string[] split_pair = pair.Split(new char[] { '=' });

    switch (split_pair[0])
    {
        case "oauth_token":
            accessToken = split_pair[1];
            break;
        case "oauth_token_secret":
            accessSecret = split_pair[1];
            break;
    }
}

Job done – the user has authenticated your app.

Make sure that you put accessToken and accessSecret somewhere safe, to use these when you make API calls in future.

5 Responses to “Authenticating with an OAuth 1.0a provider from .NET CF”

  1. […] shame, and a bit clunky – if only Yahoo! offered web service hosting, then I could reuse the OAuth credentials I collect to update Fire Eagle […]

  2. Hi,
    I am developing mobile application for FriendFeed.
    firstly thanks for your article. I had been searching HMACSHA1 encrypytion at CF; i found at your article.
    But i got error invalid_oauth_signature from FF APi.
    Can you help me?

  3. dale says:

    Bahadir – Sorry to hear you’re having trouble. It’s hard to predict exactly where you might have gone wrong without more diagnostics.

    I’d recommend trying something like the oauth proxy described at http://mojodna.net/2009/08/21/exploring-oauth-protected-apis.html as a way of finding out what is happening under the covers

  4. hi,

    i am facing the problem. i am using the same code as you wrote above related to “Obtaining an unauthorized request token” for windows mobile 6; but its giving me error of

    System.Net.WebException: The remote server returned an error: (401) Unauthorized.
    at System.Net.HttpWebRequest.finishGetResponse()
    at System.Net.HttpWebRequest.GetResponse()

    but if i use the same code for web project, it is working fine. I am using the same consumer key and consumer secret key for both web app and mobile app.

    Can you suggest any solution?

  5. dale says:

    Sorry – an HTTP 401 doesn’t really give enough information to debug from. Have you tried an OAuth proxy?