Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

How to implement a custom IPrincipal in ASP.NET MVC 4 internet project

4.92/5 (8 votes)
9 Apr 2013CPOL2 min read 120.8K  
A simple tip on how to implement a custom IPrincipal in ASP.NET MVC 4 template project with WebMatrix.WebSecurity and OAuthWebSecurity.

Introduction

This article explains a simple tip on how to customized the IPrincipal used in ASP.NET MVC4 internet application project template. You can try this tip if you want to attach additional information on the IPrincipal (Controller.User) for some purposes.

Background 

This tip is based from the solution I used in implementing custom identity in my ASP.NET MVC 3 project which I got from this thread: http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal.

The main solution is almost the same from the said thread but with just a few tweaks required to set data to additional IPrincipal properties when OAuthWebSecurity is used as authentication method.  

Using the code

Initially ASP.NET MVC 4 internet project template is configured to use both WebMatrix.WebSecurity (for local accounts) and OAuthWebSecurity (for external site accounts) for authentication. Also accounts data are getting saved in a UserProfile table which only have properties for user ID and username, and some predefined webpages_TABLES. 

This initial setup is not enough for us to achieve our goal: that is to attach additional information in the IPrincipal. In this example we will going to need to add the first name and last name info of the user but you can add any data to suit your needs.

We will need first a storage of the additional data we want to attach. To do this you can just simply add properties on the UserProfile class defined in AccountModels.cs. Or use any table then modify the InitializeSimpleMembershipAttribute.cs from the Filters folder and set your DBContext and table name:

C#
public SimpleMembershipInitializer()
{
    Database.SetInitializer<YourDBContext>(null);

    try
    {
      using (var context = new UsersContext())
      {
        if (!context.Database.Exists())
        {
          // Create the SimpleMembership database without Entity Framework migration schema
          ((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
        }
      }

      WebSecurity.InitializeDatabaseConnection("DefaultConnection", "YourDesiredTable", 
              "UserId", "UserName", autoCreateTables: true);
    }
    catch (Exception ex)
    {
      throw new InvalidOperationException("The ASP.NET Simple Membership database could " + 
        "not be initialized. For more information, please see " + 
        "http://go.microsoft.com/fwlink/?LinkId=256588", ex);
    }
}

Another way is to leave the SimpleMembershipInitializer as it is and check this tutorial: http://www.asp.net/mvc/tutorials/mvc-4/using-oauth-providers-with-mvc 

If your data storage is now ready we can now start creating custom IPrincipal

C#
public interface ICustomPrincipal : System.Security.Principal.IPrincipal
{
    string FirstName { get; set; }
 
    string LastName { get; set; }
}
public class CustomPrincipal : ICustomPrincipal
{
    public IIdentity Identity { get; private set; }
 
    public CustomPrincipal(string username)
	{
		this.Identity = new GenericIdentity(username);
	}
 
	public bool IsInRole(string role)
	{
		return Identity != null && Identity.IsAuthenticated && 
		   !string.IsNullOrWhiteSpace(role) && Roles.IsUserInRole(Identity.Name, role);
	}

	public string FirstName { get; set; }

	public string LastName { get; set; }

	public string FullName { get { return FirstName + " " + LastName; } }
}

public class CustomPrincipalSerializedModel
{
    public int Id { get; set; }
 
    public string FirstName { get; set; }
 
    public string LastName { get; set; }
}

Then in the AccountController class, add this method. We will need this method to serialize the user data and attach it in a cookie: 

C#
public void CreateAuthenticationTicket(string username) {     
 
      var authUser = Repository.Find(u => u.Username == username);  
      CustomPrincipalSerializedModel serializeModel = new CustomPrincipalSerializedModel();
      
      serializeModel.FirstName = authUser.FirstName;
      serializeModel.LastName = authUser.LastName;
      JavaScriptSerializer serializer = new JavaScriptSerializer();
      string userData = serializer.Serialize(serializeModel);
      
      FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
        1,username,DateTime.Now,DateTime.Now.AddHours(8),false,userData);
      string encTicket = FormsAuthentication.Encrypt(authTicket);
      HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
      Response.Cookies.Add(faCookie);
}

Call the above method: From the ExternalLoginCallback method: 

C#
public ActionResult ExternalLoginCallback(string returnUrl)
{
      AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
        Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
      if (!result.IsSuccessful)
      {
        return RedirectToAction("ExternalLoginFailure");
      }
 
      if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: true))
      {
        CreateAuthenticationTicket(OAuthWebSecurity.GetUserName(
                        result.Provider, result.ProviderUserId));
        return RedirectToLocal(returnUrl);
      }
 
      if (User.Identity.IsAuthenticated)
      {
        // If the current user is logged in add the new account
        OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
        CreateAuthenticationTicket(User.Identity.Name);
        return RedirectToLocal(returnUrl);
      }
      else
      {
        // User is new, ask for their desired membership name
        string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
        ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
        ViewBag.ReturnUrl = returnUrl;
        return View("ExternalLoginConfirmation", 
          new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
      }
}

In Register method: 

C#
public ActionResult Register(RegisterModel model)
{
  if (ModelState.IsValid)
  {
    // Attempt to register the user
    try
    {
      WebSecurity.CreateUserAndAccount(
        model.UserName, 
        model.Password, 
        new {             
            UpdatedBy = 0, 
            UpdatedDate = DateTime.Today, 
            CreatedBy = 0, 
            CreatedDate = DateTime.Today 
          }
       );

      WebSecurity.Login(model.UserName, model.Password);
      CreateAuthenticationTicket(model.UserName);
      return RedirectToAction("Index", "Home");
    }
    catch (MembershipCreateUserException e)
    {
      ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
    }
}

In ExternalLoginConfirmation method: 

C#
...
OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
 OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);
 CreateAuthenticationTicket(model.UserName);
 return RedirectToLocal(returnUrl);    
...  

And in the Login method: 

C#
public ActionResult Login(LoginModel model, string returnUrl)
{
      if (ModelState.IsValid && WebSecurity.Login(model.UserName, 
                model.Password, persistCookie: model.RememberMe))
      {
        CreateAuthenticationTicket(model.UserName);
        return RedirectToLocal(returnUrl);
      }
 
      // If we got this far, something failed, redisplay form
      ModelState.AddModelError("", "The user name or password provided is incorrect.");
      return View(model);
}

It's now time to read the serialized data from our cookie and replace the HttpContext.Current.User. Do this by overriding the Application_PostAuthenticateRequest method in project's Global.asax.cs .  

C#
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
      HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
      if (authCookie != null)
      {
        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        if (authTicket.UserData == "OAuth") return;
        CustomPrincipalSerializedModel serializeModel = 
          serializer.Deserialize<CustomPrincipalSerializedModel>(authTicket.UserData);
        CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
        newUser.Id = serializeModel.Id;
        newUser.FirstName = serializeModel.FirstName;
        newUser.LastName = serializeModel.LastName;
        HttpContext.Current.User = newUser;
      } 
}

To access the attached data from pages: 

C#
@(User as CustomPrincipal).FullName

And from server:  

C#
@(User as CustomPrincipal).FullName

Points of Interest 

Nothing clever from what I did actually, just a simple tip. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)