One member to rule them all

Background

As part of the upgrade process moving one of our clients to Umbraco 6 from 4.7, we were asked to make a significant change for which we couldn't find one single "how-to" resource".

The client has a public marketing site and a private members area for teachers to download training materials both of which run on Umbraco as well as a separate asp.net webforms web app enabling teachers to register for and book training courses.

Currently the private members site and web app are disconnected. Users have different member profiles for the two systems, and so have to log on to each separately. Our client wants us to implement a shared security context so that the two systems can share profile information and implement single sign-on.

Final delivery is still ongoing, but this article will cover the proof of concept we built which shows how we will achieve this. We set out to:

  • demonstrate that a .NET Webforms application can share a security context with an Umbraco 6 MVC membership site;
  • demonstrate that these two applications can share:
    • a membership provider;
    • a member database, thereby sharing usernames and passwords;
  • demonstrate single sign-on [1] i.e.:
    • Sign onto one site and be logged in immediately to the other
    • Log off either site and immediately be logged off the other as well

Overview

The Proof of Concept was run from a single local development machine on which we ran:

  • IIS 7
  • SQL Server 2008 R2
  • Umbraco 6.1.6

Figure 1, below outlines the technical solution that was created.

Figure1

Figure 1: Proof of concept system architecture diagram

IIS was configured to run two separate AppPools. Although the applications are running on the same machine both applications are running independently of each other.

Shared membership provider

For the proof of concept we had to make a small change to the default Umbraco Membership Provider. Both applications shared the same code base for the membership provider, enabling both applications to communicate with the same database of members.

When you log into the site the default Umbraco Membership Provider persists data by using the m.Save() method. However our Webapp cannot implement this method as m.Save() is a method on the Umbraco Member object that commits the instantiated member object to the database. The Webapp doesn't contain any Umbraco binaries so for the sake of the proof of concept we commented out this method call to avoid an exception being thrown.

line 803-805

		// persist data
		//if (m != null)
		//    m.Save();

For future work we would customise the membership provider more fully to allow us to have complete Umbraco functionality and access the same member database.

Member controls for WebForms

For the webforms app we use the default ASP login controls:

  • LoginName - To see who is currently logged in
  • LoginStatus - To see if we are logged in
  • Login - The default ASP control to actually perform the login, it utilises the membership provider in the web.config
<form runat="server">
            <asp:LoginName ID="LoginName1" runat="server" />
            <asp:LoginStatus ID="LoginStatus1" runat="server" />
            <asp:Login ID="Login1" runat="server"></asp:Login>
 </form>

Creating Member Login Controls for Umbraco 6

The Umbraco 6 MVC login setup is a bit more complicated. Due to the nature of MVC we have to create our own authentication methods and controller. We used this 24days in Umbraco article as reference [2].

MemberLoginSurfaceController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using umbraco.cms.businesslogic.member;
using Umbraco.Web.Mvc;
using Umbraco6SLSite.Models;

namespace Umbraco6SLSite.Controllers
{
    public class MemberLoginSurfaceController : SurfaceController
    {

        [HttpGet]
        [ActionName("MemberLogin")]
        public ActionResult MemberLoginGet()
        {
            return PartialView("MemberLogin", new MemberLoginModel());
        }

        // The MemberLogout Action signs out the user and redirects to the site home page:

        [HttpGet]
        public ActionResult MemberLogout()
        {
            Session.Clear();
            FormsAuthentication.SignOut();
            return Redirect("/");
        }

        [HttpPost]
        [ActionName("MemberLogin")]
        public ActionResult MemberLogin(MemberLoginModel model)
        {
                //Check if the dat posted is valid (All required's & email set in email field)
            if (!ModelState.IsValid)
            {
                //Not valid - so lets return the user back to the view with the data they entered still prepopulated
                return CurrentUmbracoPage();
            }

            if (Membership.ValidateUser(model.Username, model.Password))
            {
                FormsAuthentication.SetAuthCookie(model.Username, true);
                return RedirectToCurrentUmbracoPage();
            }
            else
            {
                TempData["Status"] = "Invalid username or password";
                return RedirectToCurrentUmbracoPage();
            }
        }

    }
}

MemberLoginModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace Umbraco6SLSite.Models
{
    public class MemberLoginModel
    {
        [Required, Display(Name = "Enter your user name")]
        public string Username { get; set; }

        [Required, Display(Name = "Password"), DataType(DataType.Password)]
        public string Password { get; set; }
    }
    
}

Login.cshtml

@Html.Action("MemberLogin","MemberLoginSurface")

Configure IIS for Single Sign-on

By default IIS stores session data in memory for each application [See 3]. However, we want to share this session between the two applications.

So each has been configured to store session information in a separate ASP Session Database using the SessionStateMode=SQLServer setting in web.config:

<sessionState mode="SQLServer" cookieless="false" timeout="45"
      sqlConnectionString="data source=localhost;user id=sa;password=pA55w0rd" />

The database itself being created using the Aspnet_regsql.exe tool [See 3].

To setup the ASPState database on your instance:

aspnet_regsql.exe -S SampleSqlServer -E -ssadd -sstype p

Web.config

 <authentication mode="Forms">
      <forms name="yourAuthCookie" loginUrl="login.aspx" protection="All" path="ourproject.local" domain="ourproject.local" />
    </authentication>

The Webforms app shows another strength in its ability to be rapidly developed, the default asp login reacts to the configuration in the web.config. The code snippet below is the same as the MVC configuration. That configuration alone is enough to replicate the same result as the MVC app.

Web.config

 <authentication mode="Forms">
      <forms name="yourAuthCookie" loginUrl="login.aspx" protection="All" path="ourproject.local" domain="ourproject.local" />
    </authentication>

Putting it all together

The following screenshots run through the login and logout process demonstrating the single sign on:

Figure 21Figure 22

Figure 2.

We can see here that both of the applications are logged out. Notice also how they share the same domain name "ourproject".


Figure 31Figure 32

Figure 3.

In this image you can see I have entered login details for one of applications (app) and no details have been entered on the other (umbraco).


Figure 41Figure 42

Figure 4.

We can now see how both applications are showing the user is authenticated and logged in. We only entered our login details on the "app" application and yet we are simultaneously logged into the "umbraco" application.


Figure 51Figure 52

Figure 5.

We have now hit the "log out" link. Both applications are now reporting no users are currently logged in.

This outlines the basic concept of a single login process, for future work we would include the ability to reset passwords and other user management tools.

Next steps

So far this is just a proof of concept -- we've got a lot of work before going live.  For example, we need to do a lot more testing before going live, making sure that our Custom Membership provider doesn't break core Umbraco functionality.  Our client is expecting a big increase in the number of members on the site, so we're be developing some new membership management tools to help them.

References

Below are the links I used to aid my development for this proof of concept:

  1. Single sign on, Wikipedia http://en.wikipedia.org/wiki/Single_sign-on
  2. Create a login with Umbraco MVC /umbraco/2012/creating-a-login-form-with-umbraco-mvc-surfacecontroller/
  3. Session-State Modes, MSDN, http://msdn.microsoft.com/en-us/library/ms178586.ASPX
  4. FormsAuthentication.SetAuthCookie Method, MSDN http://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.setauthcookie(v=vs.110).aspx

Jacob Polden

Jacob is on Twitter as

Paul Marden

Paul is on Twitter as