Building A Web App – Authentication / Authorization

Note: This is a continuation of a series of posts on the design and development of a web application for a non-profit organization. Previous posts:

During this post I will extend the authentication / authorization features that are generated when you create the default ASP.NET MVC application. Here is a screencast demonstrating the features we will be discussing in more detail.

The ASP.NET MVC controller and views for this blog can be download using the following link. To get started quickly, be sure to check out the included ‘ReadMe.txt’ file.

Download

What is authentication / authorization?

Authentication is the process of identifying a user. Authentication provides the answer to “who is this user?”. Authorization is the process of determining if the authenticated user has privileges to access a certain resource. Authentication typically happens once. Authorization is a continual process as the user navigates around attempting to access resources.

To help handle the processes of authentication and authorization, ASP.NET provides the membership and role providers. Here is a two part series on ASP.NET membership / role providers by Scott Allen. These providers interact with a data store to persist membership and role information. The provider for the default ASP.NET MVC application uses SQL Server as the data store. There are other providers and in fact you can create your own. Since user volume will be low, I will be using an XML membership provider, The membership / role provider is configured in the web.config as explained in the README.htm file distributed with the XML provider.

The membership data includes information about the system users: username, password, email for instance. This data is used in the authentication process. The following is an excerpt from the XML membership database:

<User
  id="2ee2db54-5254-4fc0-b387-2a3dde28d4f7"
  username="rcravens"
  email="my_email_address@gmail.com"
  locked="false"
  lastLogin="2010-01-10T21:32:27.4662789Z"
  lastActivity="2010-01-10T21:32:27.4662789Z"
  lastPasswordChange="0001-01-01T00:00:00"
  lastLockedOut="0001-01-01T00:00:00"
  createdOn="2010-01-09T08:17:02.5237559Z"
  active="true">
  <password>9gQcXwMV49ivsKzCtNrbHfN996keHlQMsdubxI9LG5Jigks0ku+siWsUWKSHi9PLehaUU2p9diGP9+oW1scmnA==</password>
</User>

As you can see from the above, the password is stored as a hash of the original password. Therefore the passwords cannot be stolen by grabbing this file. This also means that the passwords cannot (in theory) be recovered. Therefore password recovery involves resetting the users password and providing them with their new password.

Notice also the user has “active” and “locked” attributes. “Active” indicates the user is allowed to authenticate. If this is set to “false”, then authentication will always fail for that user. The “locked” attribute indicates the user has failed too many times (configurable in the web.config) at their credentials. This prevents mischievous people trying to guess a users credentials. An admin can reset either the “active” or “locked” attributes (discussed below).

The process of authorization is a much simpler task if all users who access a certain resource belong to a ‘role’ (admin for example). The data to allow role-based authorization is stored by the role provider. So for example you can do authorize users as follows:

  • All users can access the resource regardless of who they are. This would be a good authorization rule for a login screen.
  • All users can access the resource as long as they have been authenticated (they have a membership). This rule is good for pages that can only be accessed by members. For instance a change your password page.
  • Only users who belong to the “Admin” role can access the resource. This rule is good for configuration pages.

The ASP.NET MVC framework provides very easy to use attribute (‘Authorize’) that allows authorization rules to be applied to controllers and action methods. We will see some examples of these below.

The next sections discuss the more interesting details for some of the features of the authentication / authorization. Use the link above to download the code.

Services

It is generally a good idea to separate your code into areas of concern. Creating ‘service’ objects to fetch data for your controllers allows your controllers to be leaner. It also encapsulates all the ‘data fetching goodness’ into classes of their own right. This pattern also allows those services to be ‘faked’ or ‘mocked’ for testing.

Access to most of the membership / role provider features is via static members on either the ‘System.Web.Security.Membership’ class or the ‘System.Web.Security.Roles’ class. Since these functions are static using them directly in our controller would not be good for testability. We want to be able to inject fake membership / role providers into our controller for this purpose. Therefore an interface has been extracted and service wrappers created. The ASP.NET MVC team had already started this process for the membership provider. I have added methods to the already existing ‘IMembershipService’ and created a new ‘IRoleService’ interface.

public interface IMembershipService
{
    int MinPasswordLength { get; }
    int MinNonAplhaNumerics { get; }

    bool ValidateUser(string userName, string password);
    MembershipCreateStatus CreateUser(string userName, string password, string email);
    bool ChangePassword(string userName, string oldPassword, string newPassword);
    bool ResetPassword(string userName, string adminUserName, out string newPassword);
    MembershipUserCollection GetAllUsers();
    MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords);
    MembershipUser GetUser(string userName, bool userIsOnline);
    void UpdateUser(MembershipUser user);
    bool DeleteUser(string userName, bool deleteAllRelatedData);
}

public interface IRoleService
{
    string[] GetAllRoles();
    string[] GetUsersInRole(string role);
    void RemoveUserFromRole(string userName, string roleName);
    void RemoveUsersFromRole(string[] userNames, string role);
    void RemoveUsersFromRoles(string[] userNames, string[] roleNames);
    bool DeleteRole(string roleName, bool throwOnPopulatedRole);
    bool RoleExists(string roleName);
    void CreateRole(string roleName);
    bool IsUserInRole(string userName, string roleName);
    void AddUserToRole(string userName, string roleName);
    void AddUsersToRoles(string[] userName, string[] roleName);
}

The implementation of these interfaces is contained in the ‘MembershipService’ and ‘RoleService’ classes. I will not show all that code here. If you are interested, download the code. As you can imagine the code is mostly boring…mapping the interface method to a call on one of the static .NET framework classes (‘Membership’ or ‘Roles’) mentioned earlier. A couple of enhancements deserve mention.

First, my membership provider implementation takes leverages an ‘IEmailService’ object. The constructor for used for testing has this as a parameter to allow it to be faked.

public interface IEmailService
{
    string LastErrorMessage { get; }
    bool Send(string fromAddress, string fromName, string subject, string body, bool isHtml, string toAddress);
    bool Send(string fromAddress, string fromName, string subject, string body, bool isHtml, string[] toAddresses);
}

The ‘IEmailService’ object allows the membership provider to send emails. This is leveraged in the password reset workflow. The implementation leverages the ‘System.Net.Mail’ objects.

public class EmailService : IEmailService
{
    public static string InfoEmailAddress = "info@yoursite.org";
    public static string DoNotReplyEmailAddress = "do_not_reply@yoursite.org";


    private readonly string _host;
    private readonly int _port;

    public string LastErrorMessage { get; set;}

    public EmailService(string host, int port)
    {
        _host = host;
        _port = port;
    }

    public bool Send(string fromAddress, string fromName, string subject, string body, bool isHtml, string toAddress)
    {
        return Send(fromAddress, fromName, subject, body, isHtml, new[] {toAddress});
    }

    public bool Send(string fromAddress, string fromName, string subject, string body, bool isHtml, string[] toAddresses)
    {
        bool result = true;

        MailMessage msg = new MailMessage
                              {
                                  From = new MailAddress(fromAddress, fromName),
                                  Subject = subject,
                                  Body = body,
                                  IsBodyHtml = isHtml
                              };
        msg.To.Add(string.Join(",", toAddresses));

        SmtpClient smtp = new SmtpClient(_host, _port);
        try
        {
            smtp.Send(msg);
        }
        catch (Exception ex)
        {
            LastErrorMessage = ex.ToString();
            result = false;
        }

        msg.Dispose();

        return result;
    }
}

Then the ‘ResetPassword’ implementation uses this email service to send the new password directly back to the user.

public bool ResetPassword(string userName, string adminUserName, out string newPassword)
{
    MembershipUser user = GetUser(userName, false);

    // Generate a new password
    //
    newPassword = user.ResetPassword();

    string body =
        @"

        Your password to the Heartland Farm Sanctuary website has
        been reset by the site admin. Your new credentials are
        below. Please log in and reset your password as soon as
        possible.

        username = " + user.UserName + @"
        password = " + newPassword + @"

        site url: http://heartlandfarmsanctuary.org

        reset by: " + adminUserName;


    return _emailService.Send(
                                    EmailService.DoNotReplyEmailAddress,
                                    "HFS Password Reset",
                                    "Your HFS password has been reset...",
                                    body,
                                    false,
                                    user.Email
                                    );

}

Secondly, I added a bit of logic to the ‘CreateUser’ method to ensure that the first user is always belongs to the ‘Admin’ role.

public MembershipCreateStatus CreateUser(string userName, string password, string email)
{
    int totalRecords;
    _provider.GetAllUsers(0, 1, out totalRecords);

    MembershipCreateStatus status;
    MembershipUser user =_provider.CreateUser(userName, password, email, null, null, true, null, out status);

    if(totalRecords==0)
    {
        // This was the first user....make them an admin
        //
        if(!Roles.RoleExists("Admin"))
        {
            Roles.CreateRole("Admin");
        }
        Roles.AddUserToRole(userName, "Admin");
    }
    return status;
}

When the first user is added, the ‘Admin’ role is created and the ‘Admin’ role is added to the user. In other code I ensure that the ‘Admin’ role can never be deleted and that you cannot delete yourself (an Admin). This guarantees that the system will always have at least one ‘Admin’.

Controller

The ASP.NET MVC controller for this project is still the ‘Account’ controller that is generated in the default MVC application. However, action methods have been added to provide new features. For the most part the code is straight-forward and I will not go into all the details. If you are interested, please use the download.

Now that we are using a roles provider we can further refine our security policy. This is easily done using the MVC ‘Authorize’ attribute as shown in the following examples:

public ActionResult Register(){...}

[Authorize]
public ActionResult ChangePassword(){...}

[Authorize(Roles = "Admin")]
public ActionResult DeleteUser(string userName){...}

The first example does not use the ‘Authorize’ attribute at all. Thus the ‘Register’ action method can be accessed by anyone…which is what you want. The ‘ChangePassword’ action method can only be accessed by users who have logged in and been successfully authenticated. How else would the method know who’s password to change? The ‘DeleteUser’ action method not only requires authentication, but the user also needs to belong to the ‘Admin’ role. Using the ‘Authorize’ attribute in this way provides a granular approach to security.

Views and View-Models

The complete code for the ASP.NET MVC views and the view-models is available for download. I won’t go into all the HTML details. Instead, I will provide you with some screenshots for the views.

Registration

Registration is the process of collecting user information so they can become a member of your website. Here is a registration screenshot:

imageThe code that generates this can be found in the “Register.aspx” view. The controller action methods are called “Register” (one for HTTP GET and one for HTTP POST).

Log On

imageThis page handles user authentication. If they successfully log on then the system knows who the user is and their roles.

Change Password

image

This page allows the users to changer their password themselves. They will need their current password to accomplish this. If they do not remember, then an ‘Admin’ user can reset their password using the following page.

User and Role Admin

image

I will cover the admin page and associated code in a bit more detail. The following action method handles the request for the admin page:

[Authorize(Roles="Admin")]
public ActionResult Admin()
{
    AdminViewModel viewModel = new AdminViewModel
               {
                    Users = Membership.GetAllUsers(),
                    Roles = Roles.GetAllRoles()
               };

    return View(viewModel);
}

Notice that this request is protected using the ‘Authorize’ attribute and only users who belong to the ‘Admin’ role are allowed access. If the current user does not belong to the ‘Admin’ role, they will be redirected to the log on page. This action method creates an instance of the ‘AdminViewModel and passes it to the view.

public class AdminViewModel
{
    public MembershipUserCollection Users { get; set; }
    public string[] Roles { get; set; }
}

The view code that generates the above screen shot can be found in the “Admin.aspx” file and is provided here for reference:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<Heartland.Models.Admin.AdminViewModel>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Membership Admin</title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="<%= Url.Content("~/Scripts/jquery-1.3.2.min.js") %>"></script>
    <script type="text/javascript" src="<%= Url.Content("~/Scripts/main.js") %>"></script>
</head>
<body>
    <div id="commands">
        <a href='<%= Url.Action("Index", "Home") %>'>Go to HFS.</a>
    </div>
    <div class="page round border">

        <div id="users" class="content-area">
            <h1 class="headline">Users</h1>
            <% if (TempData["UserError"] != null){%>
                <div class="info">
                    <%= TempData["UserError"]%>
                </div>
            <%}%>
            <div>
            <% if(Model.Users.Count > 0){ %>
                <table class="users">
                    <tr>
                        <th>Username</th>
                        <th>Email</th>
                        <th>Is Online?</th>
                        <th>Approved</th>
                        <th>Locked</th>
                        <th>Password</th>
                        <th>Roles</th>
                        <th></th>
                    </tr>
                    <% foreach(MembershipUser user in Model.Users){ %>
                    <tr>
                        <td><%= Html.Encode(user.UserName) %></td>
                        <td><%= Html.Encode(user.Email) %></td>
                        <% if(user.IsOnline){ %>
                            <td class="isOnline">Yes</td>
                        <% }else{ %>
                            <td>Offline -
                                <%
                                    var offlineSince = (DateTime.Now - user.LastActivityDate);
                                    Response.Write(Math.Floor(offlineSince.TotalDays) + " days.");
                                %>
                            </td>
                        <% } %>
                        <td>
                            <a href='<%= Url.Action("ToggleApproved", "Account", new{userName=user.UserName}) %>'>
                                <%=Html.Encode(user.IsApproved) %>
                            </a>
                        </td>
                        <td>
                            <%if(user.IsLockedOut) {%>
                                <%= Html.ActionLink("True", "UnlockUser", "Account", null, new {userName = user.UserName}) %>
                            <%} else {%>
                                <%= Html.Encode(user.IsLockedOut) %>
                            <%}%>
                        </td>
                        <td>
                            <a href='<%= Url.Action("ResetPassword", "Account", new{userName=user.UserName}) %>'>Reset</a>
                        </td>
                        <td>
                            <% foreach (string role in Model.Roles){
                                string rolesForUser = string.Join(",", Roles.GetRolesForUser(user.UserName));
                            %>
                                <span class='<%= rolesForUser.Contains(role)?"member":"nonmember" %>'>
                                    <a href='<%= Url.Action("ToggleRole", "Account", new{role=role, userName=user.UserName}) %>'><%= Html.Encode(role) %></a>
                                </span>
                            <%}%>
                        </td>
                        <td>
                            <a class="delete" href='<%= Url.Action("DeleteUser", "Account", new{userName=user.UserName}) %>'>
                                delete
                            </a>
                        </td>
                    </tr>
                    <% } %>
                </table>
            <% }else{ %>
                <p>No users have registered.</p>
            <% } %>
            </div>
        </div>

        <div id="roles" class="content-area">
            <h1 class="headline">Roles</h1>
            <% if (TempData["RoleError"] != null){%>
                <div class="info">
                    <%= TempData["RoleError"]%>
                </div>
            <%}%>
            <div>
            <% if(Model.Roles.Length > 0){ %>
                <ul>
                <% foreach (string role in Model.Roles){ %>
                    <li>
                        <%= Html.Encode(role) %>
                        <% if(role!="Admin"){ %>
                            [ <a class="delete" href='<%= Url.Action("DeleteRole", "Account", new{role=role}) %>'>delete</a> ]
                        <% } %>
                    </li>
                <% } %>
                </ul>
            <% }else{ %>
                No roles exist.
            <% } %>
                <div class="commands">
                    New role:
                    <%    using (Html.BeginForm("AddRole", "Account", FormMethod.Post)){%>

                    <input id="role" name="newRole" type="text" />
                    <input id="addRole" type="submit" value="Add" />

                    <%}%>
                </div>
            </div>
        </div>

    </div>
    <script type="text/javascript">
        $(document).ready(function() {
            // verify the delete intent
            $(".delete").click(function(e) {
                if (!confirm("Do you want to perform this delete operation?")) {
                    return false;
                }
                return true;
            });
        });
    </script>
</body>
</html>

The code for the page above includes everything except the CSS. Normally the JavaScript at the bottom is included in a separate script file. This makes sense when you have a lot of script and you want to leverage browser caching. In this case, there is not a lot of script and I am not too worried about performance in this case.

There are three HTML ‘div’ elements that are immediate children of the ‘body’ tag. The first contains a link that leads back to the website. The second contains the user data area. And the third contains the roles data area.

The user data area is generated using an HTML table. This is the correct use of a ‘table’…I have tabular data that I am displaying. Data for each user is displayed on one row. In addition, various links provide features that allow an ‘Admin’ to configure that user’s account. The following configurations are provided:

  • Approved – This allows an ‘Admin’ to disable a user’s account. Users with a disabled account will be shown the following message when they log in with the correct credentials. You cannot disable your own account.

image

  • Locked – A user who attempts to log in and fails too many (configurable) times will have their account ‘locked’. This feature will allow an ‘Admin’ to unlock this user.
  • Password – A user who forgets their password can request to have their password reset. This feature allows the ‘Admin’ to reset the users password. The new password is automatically emailed to the user’s email address.
  • Roles – This feature allows the ‘Admin’ to add / remove the user from the existing roles. You cannot remove yourself from the ‘Admin’ role.
  • Delete – This feature allows the ‘Admin’ to delete the user. You cannot delete yourself.

 Summary

With a little bit of work the default ‘Account’ controller / views generated by the ASP.NET MVC wizard can be enhanced to leverage both membership and role providers. This allows a more granular security policy for your authentication / authorization rules.

Comments
  1. bouzidi
  2. Bob Cravens

Leave a Reply

Your email address will not be published. Required fields are marked *

*