OAuth for Dummies
Posted: October 6, 2009 Filed under: Code, OAuth | Tags: OAuth Leave a comment »Having developed a little feature where I had to publish a Tweet to uh… Twitter, I did a checkup on how OAuth actually works. If you have the time you can go through Beginner’s Guide to OAuth, which is really excellent and was my source or introduction to OAuth. I’ll just condense it a little further, mostly because it is a good way to actually “get it”.
What is OAuth?
OAUth makes it possible for providers to communicate in a secure and authenticated manor, without needing to share user secrets (username/password). For example it can allow the users of my web application to post Tweets to their Twitter account without needing to specify their username and password (except if the need to login to the Twitter site).
Who is involved?
Just to set the scene and get into the terms used, the actors involved are:
We want to get access to the Service Provider, e.g. Twitter.
The Consumer would like to access a resource on the Service Provider, e.g. my application.
The User want to access a resource on the Service Provider via the Consumer, e.g. post a Tweet using my application.
How does it work?
Seen from a larger perspective the idea is that the 3 actors (the Service Provider, the Consumer and the User) communicate with each other in such a way that all agree on who is who.
In more technical terms, all 3 actors generates and present various tokens (share secrets) to each other, which when combined establishes a trusted relationship between the Consumer and the Service Provider on behalf of the User.
Step-by-Step
A User want to perform an action on the Consumer which requires access to the/a protected resource on the Service Provider, e.g. create a Tweet.
- The Consumer contacts the Service Provider, requesting a Request Token and a Request Secret.
- The User (e.g. his/her browser) is then redirected to the Service Provider presenting the Request Token.
- The Service Provider validates the user and request approval from the User that the Consumer (found via the Request Token) can access the User’s protected resource.
- The Service Provider generates an Access Token and an Access Secret.
- The User (e.g. his/her browser) is then redirected to the Consumer, presenting the Access Token (the Access Secret is kept um… secret).
- The Consumer now takes the Access Token and the Request Secret and asks the Service Provider for the Access Secret.
- Once the Consumer has the Access Token and the Access Secret, it can access the protected resource.
The point of presenting the Request Secret to the Service Provider in step 6 is so that replays cannot be performed using the Access Token alone.
The Tokens are used to communicate between the Consumer and the Service Provider via the User (his/her browser). The Secrets are kept between the Consumer and the Service Provider.
Now what?
Once the relationship is in place, the “real” communication can take place. This is where it gets hairy and where we’ll stop for this dummy introduction.
However during my implementation I found a little problem with the way .NET UrlEncode works – it is simply not compatible with OAuth. I found an implementation by Andrew Arnott, who is OAuth/OpenID aficionado and author of DotNetOpenAuth that does this and is RFC 3986 compliant (it basically used the UrlEncode from .NET and then fixes what is broken):
/// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
/// <summary>
/// Escapes a string according to the URI data string rules given in RFC 3986.
/// </summary>
/// <param name="value">The value to escape.</param>
/// <returns>The escaped value.</returns>
/// <remarks>
/// The <see cref="Uri.EscapeDataString"/> method is <i>supposed</i> to take on
/// RFC 3986 behavior if certain elements are present in a .config file. Even if this
/// actually worked (which in my experiments it <i>doesn't</i>), we can't rely on every
/// host actually having this configuration element present.
/// </remarks>
public static string UrlEncodeRfc3986(this string value)
{
// Start with RFC 2396 escaping by calling the .NET method to do the work.
// This MAY sometimes exhibit RFC 3986 behavior (according to the documentation).
// If it does, the escaping we do that follows it will be a no-op since the
// characters we search for to replace can't possibly exist in the string.
var escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}