Adding Authentication to an ASP.Net MVC Application

I started out developing an ASP.Net MVC which did not require authentication during the prototyping stage.

But sooner or later, authentication became a requirement, and so I needed to add authentication, membership providers, etc. as provided by the ASP.Net framework.

First, I came across this article on asp.net, which contains really detailed information on the membership database schema. (There’s another detailed introduction by 4guysfromrolla – it’s dealing with .Net 2 (2007!), but still very useful)

To install the database, you need to run

C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe

provide connection details for your SQL Server database, and have your membership schema installed.

Once installed, you can created users and roles directly from SQL Server Management studio:

Locate the stored procedure aspnet_Membership_CreateUser, right click, “Script Stored Procedure as”, “EXECUTE to”, “New Query Window”, and edit the parameter values

DECLARE @RC int
DECLARE @ApplicationName nvarchar(256) = 'myapp'
DECLARE @UserName nvarchar(256) = 'username'
DECLARE @Password nvarchar(128) = 'mypwd'
DECLARE @PasswordSalt nvarchar(128) = 'mysalt'
DECLARE @Email nvarchar(256)
DECLARE @PasswordQuestion nvarchar(256)
DECLARE @PasswordAnswer nvarchar(128)
DECLARE @IsApproved bit = 1
DECLARE @CurrentTimeUtc datetime = getdate()
DECLARE @CreateDate datetime = getdate()
DECLARE @UniqueEmail int
DECLARE @PasswordFormat int = 0
DECLARE @UserId uniqueidentifier

EXECUTE @RC = [dbo].[aspnet_Membership_CreateUser] 
   @ApplicationName
  ,@UserName
  ,@Password
  ,@PasswordSalt
  ,@Email
  ,@PasswordQuestion
  ,@PasswordAnswer
  ,@IsApproved
  ,@CurrentTimeUtc
  ,@CreateDate
  ,@UniqueEmail
  ,@PasswordFormat
  ,@UserId OUTPUT
GO

Note that the @ApplicationName must be the same for all procedure calls (relating to the same application) and must match the applicationName values in web.config.

The stored procedure will create records in aspnet_Applications, aspnet_Users and aspnet_Memberships.

The values of @PasswordFormat are 0=Plaintext, 1=Hashed, 2=Encrypted, and match the allowed values of passwordFormat=”” in web.config.

Next, I created the required roles:

DECLARE @RC int
DECLARE @ApplicationName nvarchar(256) = 'myapp'
DECLARE @RoleName nvarchar(256) = 'foo-role'

EXECUTE @RC = [dbo].[aspnet_Roles_CreateRole] 
   @ApplicationName
  ,@RoleName
GO

and added the users to the correct roles

DECLARE @RC int
DECLARE @ApplicationName nvarchar(256) = 'myapp'
DECLARE @UserNames nvarchar(4000) = 'username'
DECLARE @RoleNames nvarchar(4000) = 'foo-role'
DECLARE @CurrentTimeUtc datetime = getdate()

EXECUTE @RC = [dbo].[aspnet_UsersInRoles_AddUsersToRoles] 
   @ApplicationName
  ,@UserNames
  ,@RoleNames
  ,@CurrentTimeUtc
GO

The step deals with the (already existing) sections of web.config, namely <authentication>, <membership>, <profile> and <roleManager>

Set the value of <forms loginUrl=””> to point to your login form. Any controller method having an [Authorize] attribute will redirect to the login URL if invoked without login.

The connectionStringName value must be set to a connection string name as defined in the <connectionStrings> entry that points to the database we initialized by calling aspnet_regsql.exe.

The [Authorize] attribute needs to be added to controller methods that must not be accessible to unauthorized users.

The LogOn controller method is quite minimalistic, as it only calls ValidateUser() and then sets the username, and looks like this:

[HttpPost]
public ActionResult LogOn(string UserName, string Password, bool RememberMe)
{
  if (Membership.ValidateUser(UserName, Password))
  {
    FormsAuthentication.RedirectFromLoginPage(UserName, RememberMe);

    if (!string.IsNullOrEmpty(ReturnUrl))
      return Redirect(ReturnUrl);

    return Redirect("~/");
  }

  return View("LogOn");
}

So we can log in, and we can display the user name as implemented by default in LogOnUserControl.ascx.

We need roles! To make roles work, you need to set <roleManager enabled=”true”>. Otherwise the exception

ProviderException: The Role Manager feature has not been enabled

is raised when accessing the Roles class. (see SO and SO)

Additionally, I did not want to hard-code the roles all over the place throughout the application, but rather check a limited set of (probably configurable) roles.

So based on this asp.net forum post and this question on SO the solution was to copy the AuthorizeAttribute from CodePlex, remove the Users and Roles from the class, and change the AuthorizeCore() method (of the authorization attribute base class) to

private bool AuthorizeCore(HttpContextBase httpContext)
{
  if (httpContext == null)
    throw new ArgumentNullException("httpContext");

  IPrincipal user = httpContext.User;
  if (!user.Identity.IsAuthenticated) return false;

  return IsInRole(user);
}

protected virtual bool IsInRole(IPrincipal user)
{
  throw new NotImplementedException();
}

Derived from this base class, we simple define one authorization attribute per pre-defined role using Roles.IsUserInRole()

public class AuthorizeWriteAttribute : AuthorizeBaseAttribute
{
  protected override bool IsInRole(IPrincipal user)
  {
    return user.IsInRole("write-role");
  }
}

public class AuthorizeReadAttribute : AuthorizeBaseAttribute
{
  protected override bool IsInRole(IPrincipal user)
  {
  return user.IsInRole("read-role") || user.IsInRole("write-role");
  }
}

and apply these attributes to the respective controller methods.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.