Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Azure, PHP, OpenXML, VSTS, ...

About the author

Maarten Balliauw is currently employed as .NET Technical Consultant at RealDolmen. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
View Maarten Balliauw's MVP profile

Search

Latest Twitter

    Follow me on Twitter...

    My projects

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    © Copyright Maarten Balliauw 2010

    CarTrackr on Windows Azure - Part 4 - Membership and authentication

    This post is part 4 of my series on Windows Azure, in which I'll try to convert my ASP.NET MVC application into a cloud application. The current post is all about implementing authentication in CarTrackr.

    Other parts:

    Picking a solution...

    In my opening post on this series, I defined some tasks which I would probably have to do prior to being able to run CarTrackr on Azure. For membership and authentication, I defined 2 solutions:  Cloudship or Windows Live ID.

    At first, Cloudship looked really nice as it is just an implementation of ASP.NET's provider model based on Azure. Some thinking cycles later, this did not feel right for CarTrackr... For CarTrackr, authentication only would be enough, membership would be real overkill.

    The solution I'll be using in CarTrackr is Windows Live ID. Luckily, there's some ASP.NET MVC code for that in an older release of the MVC Membership Starter Kit.

    Adding Live ID to CarTrackr

    First of all, add the WindowsLiveLogin.cs class from the MVC Membership Starter Kit. Also ,ake sure it can configure itself by adding the Live ID settings in web.config:

    <appSettings>
        <!-- See: http://msdn2.microsoft.com/en-us/library/bb676633.aspx and https://msm.live.com/app/default.aspx -->
        <add key="wll_appid" value="001600008000AF26"/>
        <add key="wll_secret" value="mvcmembershipstarterkit"/>
        <add key="wll_securityalgorithm" value="wsignin1.0"/>
    </appSettings>

    Now, I always like removing code. Actually, a lot of methods can be removed from the AuthenticationController due to the fact that Live ID will take care of lost password e-mails and stuff like that. After these stripping actions, my AccountController looks like the following:

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Security.Principal;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Security;
    using System.Web.UI;
    using CarTrackr.Core;
    using CarTrackr.Repository;
    using CarTrackr.Models;
    using CarTrackr.Filters;

    namespace CarTrackr.Controllers
    {

        [HandleError]
        [OutputCache(Location = OutputCacheLocation.None)]
        [LiveLogin]
        public class AccountController : Controller
        {
            private IUserRepository UserRepository;

            public AccountController()
                : this(null, null)
            {
            }

            public AccountController(IFormsAuthentication formsAuth, IUserRepository userRepository)
            {
                FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
                UserRepository = userRepository;
            }

            public IFormsAuthentication FormsAuth
            {
                get;
                private set;
            }

            [Authorize]
            public ActionResult Index()
            {
                return RedirectToAction("Login");
            }

            public ActionResult Login()
            {
                return View("Login");
            }

            public ActionResult Logout()
            {
                FormsAuth.SignOut();

                // Windows Live ID logout...
                HttpCookie loginCookie = new HttpCookie( "webauthtoken" );
                loginCookie.Expires = DateTime.Now.AddYears( -10 );
                Response.Cookies.Add( loginCookie );

                return RedirectToAction("Index", "Home");
            }

            protected override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                if (filterContext.HttpContext.User.Identity is WindowsIdentity)
                {
                    throw new InvalidOperationException("Windows authentication is not supported.");
                }
            }

            #region Live ID

            public ActionResult WindowsLiveAuthenticate()
            {
                // initialize the WindowsLiveLogin module.
                WindowsLiveLogin wll = new WindowsLiveLogin(true);

                // communication channels
                HttpRequestBase request = this.HttpContext.Request;
                HttpResponseBase response = this.HttpContext.Response;

                // extract the 'action' parameter from the request, if any.
                string action = request["action"] ?? "";

                /*
                  If action is 'logout', clear the login cookie and redirect
                  to the logout page.

                  If action is 'clearcookie', clear the login cookie and
                  return a GIF as response to signify success.

                  By default, try to process a login. If login was
                  successful, cache the user token in a cookie and redirect
                  to the site's main page.  If login failed, clear the cookie
                  and redirect to the main page.
                */
                if (action == "logout")
                {
                    return RedirectToAction("Logout");
                }
                else if (action == "clearcookie")
                {

                    HttpCookie loginCookie = new HttpCookie("webauthtoken");
                    loginCookie.Expires = DateTime.Now.AddYears(-10);
                    response.Cookies.Add(loginCookie);

                    string type;
                    byte[] content;
                    wll.GetClearCookieResponse(out type, out content);
                    response.ContentType = type;
                    response.BinaryWrite(content);
                    response.End();
                    return new EmptyResult();

                }
                else
                {
                    WindowsLiveLogin.User wllUser = wll.ProcessLogin(request.Form);

                    HttpCookie loginCookie = new HttpCookie("webauthtoken");
                    if (wllUser != null)
                    {
                        loginCookie.Value = wllUser.Token;

                        if (wllUser.UsePersistentCookie)
                        {
                            loginCookie.Expires = DateTime.Now.AddYears(10);
                        }
                    }
                    else
                    {
                        loginCookie.Expires = DateTime.Now.AddYears(-10);
                    }

                    // check for user in repository
                    CarTrackr.Domain.User user = UserRepository.RetrieveByUserName(wllUser.Id);
                    if (user == null)
                    {
                        user = new CarTrackr.Domain.User();
                        user.UserName = wllUser.Id;
                        UserRepository.Add(user);
                    }

                    // log user in
                    response.Cookies.Add(loginCookie);
                    FormsAuthentication.SetAuthCookie(user.UserName, false);

                    return RedirectToAction("Login");
                }
            }

            #endregion
        }

        // The FormsAuthentication type is sealed and contains static members, so it is difficult to
        // unit test code that calls its members. The interface and helper class below demonstrate
        // how to create an abstract wrapper around such a type in order to make the AccountController
        // code unit testable.

        public interface IFormsAuthentication
        {
            void SetAuthCookie(string userName, bool createPersistentCookie);
            void SignOut();
        }

        public class FormsAuthenticationWrapper : IFormsAuthentication
        {
            public void SetAuthCookie(string userName, bool createPersistentCookie)
            {
                FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
            }
            public void SignOut()
            {
                FormsAuthentication.SignOut();
            }
        }
    }

    Yes, that's almost no code left compared to the original! Remember, Live ID will take care of all user-account-related stuff for me. The only thing I'm doing here is accepting the authentication ticket Live ID provides to CarTrackr. Yes, I'm actually registering the user on my cloud storage too, because I want to track how much users actually use CarTrackr...

    One thing to notice: I've created an action filter attribute (hence the [LiveLogin] attribute on the AccountController class). The LiveLogin action filter looks like the following:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using CarTrackr.Core;
    using CarTrackr.Models;

    namespace CarTrackr.Filters
    {
        public class LiveLogin : ActionFilterAttribute
        {
            public override void OnResultExecuting(ResultExecutingContext filterContext)
            {
                WindowsLiveLogin wll = new WindowsLiveLogin(true);

                SignInViewData viewData = new SignInViewData
                {
                    AppId = wll.AppId,
                    UserId = GetUserId(wll, filterContext.HttpContext.Request)
                };

                filterContext.Controller.ViewData["WindowsLiveLogin"] = viewData;
            }

            public static string GetUserId(WindowsLiveLogin wll, HttpRequestBase request)
            {
                HttpCookie loginCookie = request.Cookies["webauthtoken"];

                if (loginCookie != null)
                {
                    string token = loginCookie.Value;

                    if (!string.IsNullOrEmpty(token))
                    {
                        WindowsLiveLogin.User user = wll.ProcessToken(token);

                        if (user != null)
                        {
                            return user.Id;
                        }
                    }
                }

                return null;
            }
        }
    }

    What hapens in this code is actually checking for the Live ID context we are in. This context can be used in any view of CarTrackr since it is stored in the ViewData dictionary by this action filter: ViewData["WindowsLiveLogin"]. This context is used by a simple LiveIdControl (code in download later on) to render the Live ID sign in / sign out link.

    Reminder for deployment...

    One reminder left when deploying this to Azure: I'll have to make sure that Live ID posts the authentication ticket to the correct URL in CarTrackr. This can be done later in the Azure project management interface:

    Live ID settings in Azure

    Conclusion

    This was a quite easy task compared to configuring TableStorage. Thank's to the MVC Membership Starter Kit, the Live ID integration was easy.

    Stay tuned for the final part: deployment on Azure! I'll also provide a download link and a live link to the project.

    kick it on DotNetKicks.com


    Categories: ASP.NET | Azure | C# | General | MVC

    Comments

    alvinashcraft.com | Reply

    Thursday, December 18, 2008 4:46 PM

    pingback

    Pingback from alvinashcraft.com

    Dew Drop - December 18, 2008 | Alvin Ashcraft's Morning Dew

    code-inside.de | Reply

    Thursday, January 15, 2009 1:37 AM

    pingback

    Pingback from code-inside.de

    HowTo: Einstieg in "Cloud-Computing" | Code-Inside Blog

    Add comment




      Country flag

    biuquote
    • Comment
    • Preview
    Loading