Geeks With Blogs

News
Joe Mayo Cloud and Glue

It’s easy to implement the ASP.NET Owin support for Log-In with Twitter because you only need to uncomment a statement in Startup.Auth.cs as described in the article, ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on. However, the problem with the Twitter implementation is that you don’t get OAuth tokens back in the default implementation – there’s only a user name and ID. This post explains how to get those OAuth tokens, which I’ll call Access tokens for consistency with the current ASP.NET implementation. There are two important parts of doing this: changing authentication options in startup to grab the tokens and adding reading logic to to the AccountController login actions.

Modifying Startup Configuration Options

ASP.NET has a default implementation of external login providers in /App_Start/Startup.Auth.cs, using an overload of UseTwitterAuthentication that takes ConsumerKey and ConsumerSecret credentials from the Twitter app page. My solution uses the other overload that accepts a TwitterAuthenticationOptions, shown below:

        public void ConfigureAuth(IAppBuilder app)
        {
            // Enable the application to use a cookie to store information for the signed in user
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
            });
            // Use a cookie to temporarily store information about a user logging in with a third party login provider
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");

            app.UseTwitterAuthentication(
                new TwitterAuthenticationOptions
                {
                    ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
                    ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"],
                    Provider = new LinqToTwitterAuthenticationProvider()
                });

            //app.UseTwitterAuthentication(
            //   consumerKey: ConfigurationManager.AppSettings["consumerKey"],
            //   consumerSecret: ConfigurationManager.AppSettings["consumerSecret"]);

            //app.UseFacebookAuthentication(
            //   appId: "",
            //   appSecret: "");

            //app.UseGoogleAuthentication();
        }

The important bit of the TwitterAuthenticationOptions instance is the LinqToTwitterAuthenticationProvider assigned to the Provider property. This provider has a virtual method for handling the request after authentication. Here’s the implementation of LinqToTwitterAuthenticationProvider, which I’ll be adding to LINQ to Twitter in the LinqToTwitter.AspNet assembly:

    public class LinqToTwitterAuthenticationProvider : TwitterAuthenticationProvider
    {
        public const string AccessToken = "TwitterAccessToken";
        public const string AccessTokenSecret = "TwitterAccessTokenSecret";

        public override Task Authenticated(TwitterAuthenticatedContext context)
        {
            context.Identity.AddClaims(
                new List<Claim>
                {
                    new Claim(AccessToken, context.AccessToken),
                    new Claim(AccessTokenSecret, context.AccessTokenSecret)
                });

            return base.Authenticated(context);
        }
    }

The Authenticated method executes after the user has logged into Twitter. The objective is to get the credentials and add them to the claims set for the request. In this case, the code adds the AccessToken and AccessTokenSecret to the claims. These claims attach to the context, which the framework passes back to the login logic in AccountController. Notice that the class contains a couple constants to help avoid typos.

Note: The term “Claim” might be a little foreign to you, but it just means “Something you have”. e.g. you have a User ID and Password to prove who you are, but there are other things like tokens that give you access to APIs, like Twitter. In this case, the Access Token and Access Token Secret are claims that you application has access to Twitter.

Saving Twitter Credentials

The points of contact with the AccountController are the ExternalLoginCallback, LinkLoginCallback, and ExternalLoginConfirmation methods. All three of these methods have logic that need to save credentials. The following code shows the call to StoreTwitterCredentials:

        //
        // GET: /Account/ExternalLoginCallback
        [AllowAnonymous]
        public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
        {
            Session.Clear();
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }

            // Sign in the user with this external login provider if the user already has a login
            var user = await UserManager.FindAsync(loginInfo.Login);
            if (user != null)
            {
                await StoreTwitterCredentials(user);
                await SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = loginInfo.DefaultUserName });
            }
        }

        //
        // GET: /Account/LinkLoginCallback
        public async Task<ActionResult> LinkLoginCallback()
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
            if (loginInfo == null)
            {
                return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
            }
            var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
            if (result.Succeeded)
            {
                var currentUser = await UserManager.FindByIdAsync(User.Identity.GetUserId());
                await StoreTwitterCredentials(user);
                return RedirectToAction("Manage");
            }
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }

        //
        // POST: /Account/ExternalLoginConfirmation
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
        {
            if (User.Identity.IsAuthenticated)
            {
                return RedirectToAction("Manage");
            }

            if (ModelState.IsValid)
            {
                // Get the information about the user from the external login provider
                var info = await AuthenticationManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    return View("ExternalLoginFailure");
                }
                var user = new ApplicationUser() { UserName = model.UserName };
                var result = await UserManager.CreateAsync(user);
                if (result.Succeeded)
                {
                    result = await UserManager.AddLoginAsync(user.Id, info.Login);
                    if (result.Succeeded)
                    {
                        await StoreTwitterCredentials(user);
                        await SignInAsync(user, isPersistent: false);
                        return RedirectToLocal(returnUrl);
                    }
                }
                AddErrors(result);
            }

            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }

Each method calls StoreTwitterCredentials, passing an ApplicationUser instance argument. The purpose of the StoreTwitterCredentials method is to extract and save Twitter access tokens. Here’s the implementation:

        async Task StoreTwitterCredentials(ApplicationUser user)
        {
            var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
            if (claimsIdentity == null) return;

            IList<Claim> currentClaims = await UserManager.GetClaimsAsync(user.Id);
            Claim accessToken = claimsIdentity.FindFirst(LinqToTwitterAuthenticationProvider.AccessToken);
            Claim accessTokenSecret = claimsIdentity.FindFirst(LinqToTwitterAuthenticationProvider.AccessTokenSecret);

            var claimKeys =
                (from claim in currentClaims
                 select claim.Type)
                .ToList();

            if (!claimKeys.Contains(accessToken.Type))
                await UserManager.AddClaimAsync(user.Id, accessToken);

            if (!claimKeys.Contains(accessTokenSecret.Type))
                await UserManager.AddClaimAsync(user.Id, accessTokenSecret);

            var auth = new AspNetAuthorizer
            {
                CredentialStore = new SessionStateCredentialStore
                {
                    ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
                    ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"],
                    OAuthToken = accessToken.Value,
                    OAuthTokenSecret = accessTokenSecret.Value
                }
            };

            using (var ctx = new TwitterContext(auth))
            {
                var twitterUser =
                    await
                    (from acct in ctx.Account
                     where acct.Type == AccountType.VerifyCredentials
                     select acct.User)
                    .SingleOrDefaultAsync();

                string name = twitterUser.Name;
                // you can do something with Twitter User data here too
            }
        }

StoreTwitterCredentials gets a list of currentClaims that the program already stores for the user, which is used to prevent adding the credentials more than one time. The important part of this algorithm is the call to AuthenticationManager.GetExternalIdentityAsync. This returns the list of claims that the LinqToTwitterAuthenticationProvider.Authenticated method added to the context.Identity. Identity holds a list of claims. Then the code gets the AccessToken and AccessTokenSecret and adds them to the database. The call to UserManager.AddClaimAsync adds the claim to the ASP.NET database where all the other ASP.NET identity credentials and roles are stored.

After this code executes, open the Server Explorer window (Ctrl+W, L) and open Data Connections, DefaultConnection, Tables, AspNetUserClaims, right-click and select Show Table Data. This will reveal the Twitter Access Tokens that StoreTwitterCredentials saved. Now, any time you need to use those tokens, call UserManager.GetClaimsAsync(user.Id), just like the statement in StoreTwitterCredentials, and pull out the claims and their values.

The remaining part of the StoreTwitterCredentials method shows how to implement LINQ to Twitter integration. Notice how I added all four credentials to the AspNetAuthorizer. Any time you load all four credentials into an authorizer, LINQ to Twitter will not require the user to authorize your application again. You can just use the authorizer and perform queries right away as demonstrated in StoreTwitterCredentials where it instantiates a TwitterContext with the AspNetAuthorizer and performs the VerifyCredentials query. The VerifyCredentials query lets you validate that the credentials you have are good and gives you a User entity instance back.

Summary

Now you know how to modify the Twitter Owin login provider built into ASP.NET to obtain Twitter access tokens. You also saw how to modify code in AccountController to read and save those tokens. As an added bonus, you learned how to perform LINQ to Twitter integration, allowing you the best of both worlds by also letting your Twitter development work the way the default ASP.NET projects are set up in Visual Studio.

Posted on Sunday, March 16, 2014 10:38 PM | Back to top


Comments on this post: Obtaining Twitter Access Tokens with ASP.NET Identity

# re: Obtaining Twitter Access Tokens with ASP.NET Identity
Requesting Gravatar...
Hi. Can you update nuget package?
Need to have LinqToTwitterAuthenticationProvider
Thanks!!
Left by Timbioz on Mar 27, 2014 4:40 AM

# re: Obtaining Twitter Access Tokens with ASP.NET Identity
Requesting Gravatar...
Timbioz,

I recently updated and LINQ to Twitter v3.0.3 is on NuGet and contains LinqToTwitterAuthenticationProvider.

@JoeMayo
Left by Joe Mayo on May 28, 2014 10:16 PM

# re: Obtaining Twitter Access Tokens with ASP.NET Identity
Requesting Gravatar...
Great article.

Will you be updating it so it works with VS2013 Update 2 and Identity 2.0?

I have been trying to fit this one in into working with Identity 2.0 but not been successful.
Left by Fleming on Jun 13, 2014 10:27 AM

Your comment:
 (will show your gravatar)
 


Copyright © Joe Mayo | Powered by: GeeksWithBlogs.net | Join free