Monday, August 24, 2009

How to check signature in MySpace iframe application (.net version)

It was a kind of headache for me after I decided to add checking signature to our MySpace application. I couldn't make it work using existing tools like the MySpaceToolkit. But eventually I was able to do it myself. I cannot say it was painless. There were several examples in php like this one, that could bring even more questions than answers. But they meant to be working and my code started working too though having eaten a plenty of my time. Here is the complete code:

private static string GenerateIFrameSignature(HttpRequest req, string secretKey)
{
    const string c_unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

    // We cannot use the HttpUtility.UrlEncode method because it produces lowercase output as OAuth requires uppercase.
    // The code of the delegate was extracted out of MySpaceToolkit library.
    Func UrlEncode = (value) =>
    {
        StringBuilder result = new StringBuilder();

        foreach (char symbol in value)
        {
            if (c_unreservedChars.IndexOf(symbol) != -1)
            {
                result.Append(symbol);
            }
            else
            {
                result.Append('%' + String.Format("{0:X2}", (int)symbol));
            }
        }

        return result.ToString();
    };

    var sortedKeysWOSig = req.QueryString.AllKeys.Where(k => k != "oauth_signature").OrderBy(k => k);
    var query = new StringBuilder();

    foreach (string key in sortedKeysWOSig)
    {
        if (query.Length > 0) query.Append('&');

        query.Append(UrlEncode(key))
        .Append('=')
        .Append(UrlEncode(req.QueryString[key]));
    }

    StringBuilder @base = new StringBuilder();
        @base.Append("GET&")
        .Append(UrlEncode(req.Url.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.Unescaped)))
        .Append('&')
        .Append(UrlEncode(query.ToString()));


    using (KeyedHashAlgorithm alg = HMACSHA1.Create())
    {
        //according to http://oauth.googlecode.com/svn/spec/core/1.0/oauth-core-1_0.html#rfc.section.9.2 we should concat [Consumer Secret] + & + [Token Secrect]. The latter one is not provided by MySpace. So we imply empty string in this place.
        alg.Key = Encoding.ASCII.GetBytes(secretKey + "&");

        byte[] dataBuffer = Encoding.ASCII.GetBytes(@base.ToString());
        byte[] hashBytes = alg.ComputeHash(dataBuffer);

        return Convert.ToBase64String(hashBytes);
    }
}

There is no reason to use the UrlEncode as delegate except placing it in a single method for readability of this post.

After comparing signature out of 'oauth_signature' query string parameter and the one the method above returns you can also verify auth timestamp to be up to date:

private const string _oauthTimestampKey = "oauth_timestamp";
private static readonly TimeSpan _staledInterval = TimeSpan.FromMinutes(10);
private static readonly DateTime _stDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

...

long authTime;
if (!Int64.TryParse(request.QueryString[_oauthTimestampKey], NumberStyles.Any, CultureInfo.InvariantCulture, out authTime))
{
    #region Logging
    if (_log.IsDebugEnabled) _log.DebugFormat("Couldn't parse oauth_timestamp value {0}", authTime);
    #endregion
    return false;
}

var now = DateTime.UtcNow;
DateTime time = _stDate.AddSeconds(authTime);
if (now - time > _staledInterval)
{
    #region Logging
    if (_log.IsDebugEnabled) _log.DebugFormat("Auth time was too late. Expected greater than {0}. Actual was {1}", now - _staledInterval, time);
    #endregion
    return false;
}

No comments:

Post a Comment