This following blog post will go through how to get custom user data in to the System.Web.Security.FormsAuthenticationTicket after a user has been authenticated by your application.

Adding user data to a FormsAuthenticationTicket

I'd like to add a bit of a disclaimer here in that this is my first blog post and there is a big reason that I became a developer instead of an English professor. So on that note let's see the code.

I'm starting with an empty ASP.NET MVC 3 project and that is ususally my preference when starting any new project. I feel that the other templates tend to just bloat the project to the point where I end up removing more items than I keep.

We're going to start off by editing the web.config file inside of the system.web node in order to define our forms authentication loginurl, protection all just means that the cookie will be encrypted and validated on each request to verify its authenticity.

<authentication mode="Forms">
  <forms loginUrl="~/Account/SignIn"
         protection="All"
         cookieless="UseCookies"
         slidingExpiration="false"
         timeout="30"
         name="customcookie-auth" />
</authentication>

The next step in this example is to create the Account Controller and SignIn actions. I'm going to skip through giving you step by step instructions on how to create a controller inside of Visual Studio. I figure this is something that everyone has done before and if you need more help there will be a link to the source code on BitBucket.

After creating the Account Controller lets make the SignIn get and post actions. The get will just show the default SignIn view.

[HttpGet]
public ViewResult SignIn()
{
     return View("SignIn");
}

Now we're going to implement the SignIn post action that will require the username and custom data to be passed in to it. Just to let you know the reason to have the custom user data as an input on the form is to see that your custom data being supplied in the input is persisted on subsequent requests to the server. I've used this to store my unique identifier for my users in other applications. We're going to make a model that defines what the SignIn post action expects to be passed to it.

public class SignInModel
{
    [Required(ErrorMessage = "Required")]
    [DisplayName("User Name:")]
    public string UserName { get; set; }

    [Required(ErrorMessage = "Required")]
    [DisplayName("Custom user data:")]
    public string CustomUserData { get; set; }
}

The next thing that will be required is that we'll need to define a custom IIdentity that will be in charge of parsing the authentication cookie that will be supplied by the users browser on each request sent to our server after authentication. This will make our life easier when we try to access the userData that we supplied when generating the FormsAuthenticationTicket. I should let you know if you have multiple pieces of data that you wish to store in the user data property than designate a character to split the individual pieces of data and handle the split of the data in the CustomIdentity constructor.

public class CustomIdentity : MarshalByRefObject, System.Security.Principal.IIdentity
{
#region Private variables

private readonly FormsAuthenticationTicket _ticket;
private readonly string _uniqueIdentifier;

#endregion

#region Constuctor

/// <summary>
/// Initialize the identity with the FormsAuthenticationTicket in order
/// to read custom userdata.
/// </summary>
/// <param name="ticket"></param>
public CustomIdentity(FormsAuthenticationTicket ticket)
{
    _ticket = ticket;
    _uniqueIdentifier = _ticket.UserData;
}

#endregion

#region Properties

/// <summary>
/// Gets the AuthenticationType being used.
/// </summary>
public string AuthenticationType
{
    get { return "Forms"; }
}

/// <summary>
/// Gets a flag indicating if the user is Authenticated or not.
/// </summary>
public bool IsAuthenticated
{
    get { return true; }
}

/// <summary>
/// Gets the user name associated with the FormsAuthenticationTicket.
/// </summary>
public string Name
{
    get { return _ticket.Name; }
}

/// <summary>
/// Gets the FormsAuthenticationTicket associated with this User.
/// </summary>
public FormsAuthenticationTicket Ticket
{
    get { return _ticket; }
}

/// <summary>
/// Gets the user's unique identifier stored in the FormsAuthenticationTicket.
/// </summary>
public string UniqueIdentifier
{
    get
    {
        return _uniqueIdentifier;
    }
}

#endregion
}

We'll next need to modify the Global.asax file in order to override the default FormsIdentity that gets created. We are setting the current HttpContext and thread identity to the CustomIdentity we just created. This will allow us to pull our userdata back out of the cookie in a strongly typed manor from the HttpContext or Thread.

protected void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
    // Get a reference to the current User
    IPrincipal currentUser = HttpContext.Current.User;

    // If we are dealing with an authenticated forms authentication request
    if (currentUser.Identity.IsAuthenticated
        && currentUser.Identity.GetType() != typeof(CustomIdentity))
    {
        // Create a CustomIdentity based on the FormsAuthenticationTicket           
        var identity = new CustomIdentity(((FormsIdentity)currentUser.Identity).Ticket);

        // Create the CustomPrincipal
        var user = new GenericPrincipal(identity, null);

        // Attach the CustomPrincipal to HttpContext.User and Thread.CurrentPrincipal
        HttpContext.Current.User = user;
        Thread.CurrentPrincipal = user;
    }
}

I will set up the FormsAuthenticationTicket based on the Web.config settings that we defined along with the data specified on the SignIn form. The key part in the following code is that we make sure to encrypt the FormsAuthenticationTicket that we just created in order to not have our username and custom user data be in clear text when sent to the users browser. We'll be setting the FormsAuthenticationTicket's information based on what was setup in the web.config. Just make sure when doing this that you make the cookie HttpOnly so the clients browser will not have access to the cookie via javascript.

    [HttpPost]
    public ActionResult SignIn(SignInModel model)
    {
        if (!ModelState.IsValid)
        {
            return View("SignIn");
        }

        DateTime cookieIssuedDate = DateTime.Now;

        var ticket = new FormsAuthenticationTicket(0,
            model.UserName,
            cookieIssuedDate,
            cookieIssuedDate.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
            false,
            model.CustomUserData,
            FormsAuthentication.FormsCookiePath);

        string encryptedCookieContent = FormsAuthentication.Encrypt(ticket);

        var formsAuthenticationTicketCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedCookieContent)
        {
            Domain = FormsAuthentication.CookieDomain,
            Path = FormsAuthentication.FormsCookiePath,
            HttpOnly = true,
            Secure = FormsAuthentication.RequireSSL
        };

        System.Web.HttpContext.Current.Response.Cookies.Add(formsAuthenticationTicketCookie);

        return RedirectToAction("Home");
    }

We should be able to access the username and custom data that was supplied in the FormsAuthentication from our home view like this:

Your username is: <strong>@User.Identity.Name</strong>
<br />
Your custom data is: <strong>@(((CustomIdentity)User.Identity).UniqueIdentifier)</strong>
<br />
@Html.ActionLink("Click here to refresh this page and verify the userdata is persisted.", "Home")

I've just started working with Mercurial and BitBucket so hopefully this will be easier to push my code samples out for you to view or download. Custom Cookie Example

Happy coding and please let me know what you think of my information in my posts. If there are too many gaps or too much information in the posts then let me know.

asp.netmvcformsauthenticationformsauthenticationticketuserdata
Posted by: Justin Michaels
Last revised: 04 May, 2012 06:08 PM

Comments

No comments yet. Be the first!

blog comments powered by Disqus