In a previous post I detailed how I retrieved my Cinema Cento blog posts using the Tumblr API and stored them as content within Umbraco. My eventual aim is to write the posts using Umbraco and then post them to Tumblr using the API.
In order to write a post to Tumblr you need to use OAuth to authenticate your user. After a few hours investigating, I found the DotNetOpenAuth library which has many helper functions for implementing the OAuth sign-in process. I installed this into my TumblrApi project using NuGet. Using the DotNetOpenAuth library you can implement the OAuth process as follows.
Note that the actual OAuth process involves a few more steps than these, but the DotNetOpenAuth library takes care of those for you. As the access token (and secret) are all that is required to make an OAuth call, it makes sense to store them in a database. In keeping with Umbraco, I chose to install PetaPoco in my TumblrApi project to manage this.
The DotNetOpenAuth library requires you to implement a new class that implements the “IConsumerTokenManager” interface. This token manager will manage the storage of the tokens (and their matching secrets). I decided to store them in a table in the same database as Umbraco, and to use PetaPoco to access that table. Here are the details of the “tblOauthToken” table I created.
I used the Peta Poco T4 Template Builder to create the “tblOauthToken” class that is used to access the table rows. Here is the full token manager class.
using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using PetaPoco; using System; using umbracoDbDSN; namespace TumblrApi { public class DatabaseTokenManager : IConsumerTokenManager { private Database Db; public string ConsumerKey { get; private set; } public string ConsumerSecret { get; private set; } public DatabaseTokenManager(string consumerKey, string consumerSecret, Database db) { ConsumerKey = consumerKey; ConsumerSecret = consumerSecret; Db = db; } public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { tblOauthToken existingToken = Db.SingleOrDefault<tblOauthToken>("SELECT * FROM tblOauthToken WHERE application=@0 AND tokenType=@1", "tumblr", TokenType.RequestToken); if (existingToken != null) { Db.Delete<tblOauthToken>(existingToken); } tblOauthToken newToken = new tblOauthToken(); newToken.application = "tumblr"; newToken.tokenType = (int) TokenType.RequestToken; newToken.token = response.Token; newToken.secret = response.TokenSecret; Db.Insert(newToken); } public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { tblOauthToken existingToken = Db.SingleOrDefault<tblOauthToken>("SELECT * FROM tblOauthToken WHERE application=@0 AND tokenType=@1", "tumblr", TokenType.RequestToken); if (existingToken != null) { Db.Delete<tblOauthToken>(existingToken); } existingToken = Db.SingleOrDefault<tblOauthToken>("SELECT * FROM tblOauthToken WHERE application=@0 AND tokenType=@1", "tumblr", TokenType.AccessToken); if (existingToken != null) { Db.Delete<tblOauthToken>(existingToken); } tblOauthToken newToken = new tblOauthToken(); newToken.application = "tumblr"; newToken.tokenType = (int)TokenType.AccessToken; newToken.token = accessToken; newToken.secret = accessTokenSecret; Db.Insert(newToken); } public string GetTokenSecret(string token) { String secret = null; tblOauthToken existingToken = Db.SingleOrDefault<tblOauthToken>("SELECT * FROM tblOauthToken WHERE application=@0 AND token=@1", "tumblr", token); if (existingToken != null) { secret = existingToken.secret; } return secret; } public TokenType GetTokenType(string token) { TokenType type = TokenType.InvalidToken; tblOauthToken existingToken = Db.SingleOrDefault<tblOauthToken>("SELECT * FROM tblOauthToken WHERE application=@0 AND token=@1", "tumblr", token); if (existingToken != null) { type = (TokenType) existingToken.tokenType; } return type; } // Not required by the interface, but means we don't have to store the access token somewhere else. public String GetAccessToken() { String token = null; tblOauthToken existingToken = Db.SingleOrDefault<tblOauthToken>("SELECT * FROM tblOauthToken WHERE application=@0 AND tokenType=@1", "tumblr", TokenType.AccessToken); if (existingToken != null) { token = existingToken.token; } return token; } } }
I extended my existing TumblrClient class to provide the main functionality. I have created this as a singleton class, although it probably wouldn’t be too expensive to keep creating it every time. The database lookups will only take place on each API call.
In order to use the DotNetOpenAuth library we need to set up a token manager and a “ServiceProviderDescription”, the latter containing the endpoint and protocol information. These will be passed-in as parameters when the object is instantiated.
Provider = new ServiceProviderDescription(); Provider.AccessTokenEndpoint = new MessageReceivingEndpoint(accessTokenEndpoint, HttpDeliveryMethods.PostRequest); Provider.RequestTokenEndpoint = new MessageReceivingEndpoint(requestTokenEndpoint, HttpDeliveryMethods.PostRequest); Provider.UserAuthorizationEndpoint = new MessageReceivingEndpoint(userAuthorisationEndpoint, HttpDeliveryMethods.PostRequest); Provider.TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }; Provider.ProtocolVersion = ProtocolVersion.V10a; var db = new PetaPoco.Database(connectionString); TokenManager = new DatabaseTokenManager(consumerKey, consumerSecret, db);
The “StartOAuth” function is designed to be called from a controller in step 1 of the process above. It will redirect to Tumblr for the user to login and grant permission to the application. After the redirect there is nothing to do except wait for the call-back from Tumblr.
// Assumes it is called from within an MVC controller (will redirect the page) public String StartOAuth(String callbackUrl) { WebConsumer consumer = new WebConsumer(Provider, TokenManager); // Url to redirect to var authUrl = new Uri(callbackUrl); // request access consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(authUrl, null, null)); // This will not get hit! return null; }
The controller for the call-back URL will then need to call OAuthCallback to process the returned tokens and store them in the database.
public Boolean OAuthCallback() { // Process result from the service provider WebConsumer consumer = new WebConsumer(Provider, TokenManager); AuthorizedTokenResponse accessTokenResponse = consumer.ProcessUserAuthorization(); // If we didn't have an access token response, this wasn't called by the service provider if (accessTokenResponse == null) { return false; } return true; }
As a test I implemented the GetUserInfo function, but this simply returns the JSON string for now. This demonstrates how to access the endpoints requiring OAuth.
public String GetUserInfo() { String accessToken = TokenManager.GetAccessToken(); // Process result Tumblr WebConsumer tumblr = new WebConsumer(Provider, TokenManager); // Retrieve the user's profile information MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://api.tumblr.com/v2/user/info", HttpDeliveryMethods.GetRequest); HttpWebRequest request = tumblr.PrepareAuthorizedRequest(endpoint, accessToken); WebResponse response = request.GetResponse(); String result = (new StreamReader(response.GetResponseStream())).ReadToEnd(); return result; }
I have created a simple surface controller to provide the required URLs that are needed. Eventually these calls could be implemented as controls in the Umbraco back office, but for now I will just access the start URL manually.
The “GetTumblrAuthorisation” action starts the ball rolling. As this URL will be redirected to Tumblr the return View can be ignored.
public ActionResult GetTumblrAuthorisation() { String callbackUrl = ConfigHelper.GetAppSetting("TumblrCallbackUrl"); TumblrOAuthClient tumblrClient = GetTumblrClient(); tumblrClient.StartOAuth(callbackUrl); return View(""); }
The “TumblrOAuthCallback” action is the call-back URL that is redirected to by Tumblr.
[HttpGet] // http://localhost:56226/umbraco/surface/TumblrView/TumblrOAuthCallback public ActionResult TumblrOAuthCallback() { TumblrOAuthClient tumblrClient = GetTumblrClient(); tumblrClient.OAuthCallback(); return View("TumblrOAuthCallbackPage"); }