DotNetOpenAuth, OAuth, and MVC For Dummies

I recently was trying to understand OAuth so that I could utilize the LinkedIn API in my Asp.Net MVC application. The LinkedIn API has a pretty good summary of the OAuth process. However, the more I looked at it (and other documentation on OAuth), the more confused I got at implementing OAuth myself. So I went around and looked for a C# library to help me out, and found DotNetOpenAuth.Net. Unfortunately, the DotNetOpenAuth website is horribly designed without any real tutorials. After searching around the internet I was able to piece some things together, and hopefully this will help someone figure this out quicker than I was able to.

Requesting User Authorization

The first step of authorizing with OAuth and DotNetOpenAuth is to redirect to the OAuth provider’s authorization page, so the user can grant your application access to perform queries/service calls on their behalf. DotNetOpenAuth needs several pieces of information to begin this process. The first is to create a ServiceProviderDescription object, that contains the provider’s URL for retrieving the request token, URL for retrieving the access token, URL for requesting user authentication, what OAuth protocol version to use, and details of the tamper protection used to encode the OAuth signature. An example of creating the provider description for connecting to LinkedIn is:

        private ServiceProviderDescription GetServiceDescription()
        {
            return new ServiceProviderDescription
            {
                AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/accessToken", HttpDeliveryMethods.PostRequest),
                RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken", HttpDeliveryMethods.PostRequest),
                UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.linkedin.com/uas/oauth/authorize", HttpDeliveryMethods.PostRequest),
                TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
                ProtocolVersion = ProtocolVersion.V10a
            };
        }

The next thing that DotNetOpenAuth requires is a token manager. The token manager is a class which DotNetOpenAuth utilizes to store and retrieve the consumer key, consumer secret, and a token secret for a given access key. Since how you will store the user access tokens and token secrets will vary project to project, DotNetOpenAuth assumes you will create your own token storage and retrieval mechanism by implementing the IConsumerTokenManager interface.

For testing, I looked online for an in memory token manager class, and found the following code:

    public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager
    {
        private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();

        public InMemoryTokenManager(string consumerKey, string consumerSecret)
        {
            if (String.IsNullOrEmpty(consumerKey))
            {
                throw new ArgumentNullException("consumerKey");
            }

            this.ConsumerKey = consumerKey;
            this.ConsumerSecret = consumerSecret;
        }

        public string ConsumerKey { get; private set; }

        public string ConsumerSecret { get; private set; }

        #region ITokenManager Members

        public string GetConsumerSecret(string consumerKey)
        {
            if (consumerKey == this.ConsumerKey)
            {
                return this.ConsumerSecret;
            }
            else
            {
                throw new ArgumentException("Unrecognized consumer key.", "consumerKey");
            }
        }

        public string GetTokenSecret(string token)
        {
            return this.tokensAndSecrets[token];
        }

        public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
        {
            this.tokensAndSecrets[response.Token] = response.TokenSecret;
        }

        public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
        {
            this.tokensAndSecrets.Remove(requestToken);
            this.tokensAndSecrets[accessToken] = accessTokenSecret;
        }

        /// <summary>
        /// Classifies a token as a request token or an access token.
        /// </summary>
        /// <param name="token">The token to classify.</param>
        /// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
        public TokenType GetTokenType(string token)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IOpenIdOAuthTokenManager Members

        public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization)
        {
            this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
        }

        #endregion
    }

Now that we have a Token Manager class to use, and a service description we can begin the authorization process. This can be accomplished with the following code:

        public ActionResult StartOAuth()
        {
            var serviceProvider = GetServiceDescription();
            var consumer = new WebConsumer(serviceProvider, _tokenManager);

            // Url to redirect to
            var authUrl = new Uri(Request.Url.Scheme + "://" + Request.Url.Authority + "/Home/OAuthCallBack");

            // request access
            consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(authUrl, null, null));

            // This will not get hit!
            return null;
        }

This sets up the DotNetOpenAuth consumer object to use our in memory token manager, and our previously defined service description object. We then form the URL we want the service provider to redirect to after the user grants your application access. Finally we tell the consumer to send the user authorization request. The Send() method will end the execution of the Asp.Net page, and thus no code after the Send() call will be called. The user will then see the authorization page on the service provider’s website, which will allow them to allow or deny access for your application.

Receiving the OAuth CallBack

Once the user logs into the service provider and gives your application authorization, the service provider will redirect to the callback URL specified in the previous code. The service provider includes the oauth token and secret, which needs to be processed by DotNetOpenAuth. The following code can be done to process the auth token and store it and the secret in the token manager:

        public ActionResult OAuthCallback()
        {
            // Process result from the service provider
            var serviceProvider = GetServiceDescription();
            var consumer = new WebConsumer(serviceProvider, _tokenManager);
            var accessTokenResponse = consumer.ProcessUserAuthorization();

            // If we didn't have an access token response, this wasn't called by the service provider
            if (accessTokenResponse == null)
                return RedirectToAction("Index");

            // Extract the access token
            string accessToken = accessTokenResponse.AccessToken;

            ViewBag.Token = accessToken;
            ViewBag.Secret = _tokenManager.GetTokenSecret(accessToken);
            return View();
        }

Perform A Request Using OAuth Credentials

Now that we have the user’s authorization details we can perform API queries. In order to query the API though, we need to sign our requests with a combination of the user’s access token and our consumer key. Since we retrieved the user’s access token in the previous code, you need to figure out a way to store that somewhere, either in the user’s record in the database, in a cookie, or any other way you can quickly get at it again without requiring the user to constantly re-auth.

In order to use that access token to call a service provider API function, you can form a prepared HttpWebRequest by calling the PrepareAuthorizedRequest() method on the WebConsumer class. The following is an example how to use an access token to query the LinkedIn API.

        public ActionResult Test2()
        {
            // Process result from linked in
            var LiServiceProvider = GetServiceDescription();
            var linkedIn = new WebConsumer(LiServiceProvider, _tokenManager);
            var accessToken = GetAccessTokenForUser();

            // Retrieve the user's profile information
            var endpoint = new MessageReceivingEndpoint("http://api.linkedin.com/v1/people/~", HttpDeliveryMethods.GetRequest);
            var request = linkedIn.PrepareAuthorizedRequest(endpoint, accessToken);
            var response = request.GetResponse();
            ViewBag.Result = (new StreamReader(response.GetResponseStream())).ReadToEnd();

            return View();
        }

And now, if the user has authenticated going to /Home/Test2 will correctly access LinkedIn on behalf of the user!

Update: For those who are looking for a tool to help test API calls prior to having to write them down formally in code, please see my Testing Oauth APIs Without Coding article!

About these ads

36 responses to “DotNetOpenAuth, OAuth, and MVC For Dummies

    • I did, but I believe when I tried it I had issues compiling it and getting it to run. Since the last check-in was in 2010 I did not know what state it was in and didn’t bother putting any effort in to determine why I wasn’t having issues.

      Performing API requests is the easy part anyway (especially with tools like JSON.net). OAuth would have been the main reason to use that library, but I couldn’t get it working quickly.

  1. I used DotNetOpenAuth 3.4.7.11121 in Mvc3 application .
    The given key was not present in the dictionary exception is thrown.
    Any solutions would be a great help.

    • Not exactly sure, but make sure that the tokens you are trying to access are actually getting stored in the TokenProvider class. Most likely it’s trying to look in the token provider for a token that doesn’t exist.

      • I am having this same problem. Everything seems to be close to work, but after I grant access in LinkedIn, the code returns to oAuthCallback, and throws an exception on this line:

        var accessTokenResponse = consumer.ProcessUserAuthorization();

        {DotNetOpenAuth.Messaging.ProtocolException: Failure looking up secret for consumer or token. —> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
        at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
        at LinkedInHIMSS.InMemoryTokenManager.GetTokenSecret(String token) in C:\Users\rbraman\Documents\Visual Studio 2010\Projects\LinkedInHIMSS\LinkedInHIMSS\Models\InMemoryTokenManager.cs:line 46
        at DotNetOpenAuth.OAuth.ChannelElements.OAuthChannel.Signature

    • I found a solution for the “The given key was not present in the dictionary”.

      The InMemoryTokenManager instance (called _tokenManager in the above example) needs to be persisted after being instantiated. I did it using the session object.

      so every time you supply a token manager. it has to be the SAME INSTANCE since it accumulates tokens and secrets on each request during the authentication process.

      Step 1:
      Add this method to you class (along with the other methods on the example above)
      private InMemoryTokenManager GetInMemoryTokenManager()
      {
      InMemoryTokenManager tokenManager = (InMemoryTokenManager)Session[“tokenManager”];
      if (tokenManager == null)
      {
      tokenManager = new InMemoryTokenManager(accessToken, accessTokenSecret);
      Session[“tokenManager”] = tokenManager;
      }

      return tokenManager ;
      }

      Step 2:
      Replace _tokenManager with GetInMemoryTokenManager()

      now you’re creating the token manager for the first time, and supplying the same instance of subsequent calls.

      Hope this helps

      • I’m having trouble with the session object being empty after redirection from the callback. I’m using the cache to store the tokenmanager before the callback, and removing the tokenmanager from the cache after the callback.

  2. First thank you you for posting this. I am trying to put your code into an example MVC project, and I am stuck on one line:

    accessToken = GetAccessTokenForUser()

    GetAccessTokenForUser does not exist. DO you have code for this function. Thanks. -Richard

      • the problem is that it keeps trying to get the tokens, but the content is not static, so you have to save it somewhere, I’m currently trying to store in a cookie, on the method StoreNewRequestToken(), you can see there the values are added to the dictionary tokensAndSecrets, but once the context of that instance ends, you no longer have access to those values. So on that method I’m trying to store them in a cookie. The structure of the dictionary is: value= response.Token and key= response.TokenSecret

  3. The problem is RedirectToAction, ViewBag are not accessible.
    I’m getting for both ‘they don’t exist in the current context’

    Also, what is GetAccessTokenForUser() ?

  4. I’m relatively weak at coding. I was wondering..

    Where do I put the codes for:
    – Requesting User Authorization
    – Receiving the OAuth CallBack
    – Perform A Request Using OAuth Credentials

  5. I’m having the same problem. I also have an issue with the ActionResult. Is there a reference that I need to add or a using directive??

  6. When you type the line
    var authURL =new Uri(Request.Url.Scheme + “://” + Request.Url.Authority + “/Home/OAuthCallBack”);

    Where exactly have you defined Request?

  7. I might be the only dumb here.
    I need to use google, twitter, etc login into my existing website.
    I’m using Domain Based Routing and therefore, managing multiple website from same MVC4 application.

    I need step by step tutorial which helps me to create OpenAuth logins and get those data to stored in my own table.
    I need Email ID of the Authenticated User as this is what I’m using as Primary Logins.

    I’m not sure if I’m able to explain what I need, but if you got it, your help is really appreciated.

    Thanks
    Garth

  8. Just wanted to say thank you. Brilliant straight forward example. Saved me a lot of time setting up OAuth for a 3rd party web service (not LinkedIn.) Works beautifully.

    As mentioned in the thread above, I created a property in my controller to store an instance of the InMemoryTokenManager in the Session and access it from there for temporary persistence.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s