Customize the SimpleMembership in ASP.NET MVC 4.0
As we know, .NET 4.5 have come up to us, and come along with a lot of new interesting features as well. Visual Studio 2012 was also introduced some days ago. They made us feel very happy with cool improvement along with us. Performance when loading code editor is very good at the moment (immediate after click on the solution). I explore some of cool features at these days. Some of them like Json.NET integrated in ASP.NET MVC 4.0, improvement on asynchronous action, new lightweight theme on Visual Studio, supporting very good on mobile development, improvement on authentication…
I reviewed them, and found out that in this version of
.NET Microsoft was not only developed new feature that
suggest from community but also focused on improvement
performance of existing features or components. Besides
that, they also opened source more projects, like Entity
Framework, Reactive Extensions, ASP.NET Web Stack… At the
moment, I feel Microsoft want to open source more and more
their projects.
Today, I am going to dive in deep on new
SimpleMembership model. It is really good because in this
security model, Microsoft actually focus on development
needs. As we know, in the past, they introduce some of
provider supplied for coding security like
MembershipProvider, RoleProvider… I don’t need to talk but
everyone that have ever used it know that they were actually
hard to use, and not easy to maintain and unit testing. Why?
Because every time you inherit it, you need to override all
methods inside it. Some people try to abstract it by
introduce more method with virtual keyword, and try to
implement basic behavior, so in the subclass we only need to
override the method that need for their business. But to me,
it’s only the way to work around. ASP.NET team and Web
Matrix knew about it, so they built the new features based
on existing components on .NET framework. And one of
component that comes to us is SimpleMembership and
SimpleRole. They implemented the Façade pattern on the top
of those, and called it is WebSecurity. In the web, we can
call WebSecurity anywhere we want, and make a call to inside
wrapper of it.
I read a lot of them on web blog, on technical news,
on MSDN as well. Matthew Osborn had an excellent article
about it at
his blog. Jon Galloway had an article like this at
here. He analyzed why old membership provider not fixed well to
ASP.NET MVC and how to get over it.
Those are very good to me. It introduced to me about
how to doing SimpleMembership on it, how to doing it on new
ASP.NET MVC web application. But one thing, those didn’t
tell me was how to doing it on existing security model (that
mean we already had Users and Roles on legacy system, and
how we can integrate it to this system), that’s a reason I
will introduce it today. I have spent couples of hours to
see what’s inside this, and try to make one example to
clarify my concern. And it’s lucky that I can make it
working well.
The first thing, we need to create new ASP.NET MVC
application on Visual Studio 2012. We need to choose
Internet type for this web application. ASP.NET MVC actually
creates all needs components for the basic membership and
basic role. The cool feature is DoNetOpenAuth come along
with it that means we can log-in using facebook, twitter or
Windows Live if you want. But it’s only for LocalDb, so we
need to change it to fix with existing database model on SQL
Server. The next step we have to make SimpleMembership can
understand which database we use and show it which column
need to point to for the ID and UserName. I really like this
feature because SimpleMembership on need to know about the
ID and UserName, and they don’t care about rest of it.
I assume that we have an existing database model like

So we will point it in code like

The codes for it, we put on
InitializeSimpleMembershipAttribute like
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute { private static SimpleMembershipInitializer _initializer; private static object _initializerLock = new object(); private static bool _isInitialized; public override void OnActionExecuting(ActionExecutingContext filterContext) { // Ensure ASP.NET Simple Membership is initialized only once per app start LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock); } private class SimpleMembershipInitializer { public SimpleMembershipInitializer() { try { WebSecurity.InitializeDatabaseConnection("DefaultDb", "User", "Id", "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); } } } }
And decorating it in the AccountController as below
[Authorize] [InitializeSimpleMembership] public class AccountController : Controller
In this case, assuming that we need to override the
ValidateUser to point this to existing User database table,
and validate it. We have to add one more class like
public class CustomAdminMembershipProvider : SimpleMembershipProvider { // TODO: will do a better way private const string SELECT_ALL_USER_SCRIPT = "select * from [dbo].[User]private where UserName = '{0}'"; private readonly IEncrypting _encryptor; private readonly SimpleSecurityContext _simpleSecurityContext; public CustomAdminMembershipProvider(SimpleSecurityContext simpleSecurityContext) : this(new Encryptor(), new SimpleSecurityContext("DefaultDb")) { } public CustomAdminMembershipProvider(IEncrypting encryptor, SimpleSecurityContext simpleSecurityContext) { _encryptor = encryptor; _simpleSecurityContext = simpleSecurityContext; } public override bool ValidateUser(string username, string password) { if (string.IsNullOrEmpty(username)) { throw new ArgumentException("Argument cannot be null or empty", "username"); } if (string.IsNullOrEmpty(password)) { throw new ArgumentException("Argument cannot be null or empty", "password"); } var hash = _encryptor.Encode(password); using (_simpleSecurityContext) { var users = _simpleSecurityContext.Users.SqlQuery( string.Format(SELECT_ALL_USER_SCRIPT, username)); if (users == null && !users.Any()) { return false; } return users.FirstOrDefault().Password == hash; } } }
SimpleSecurityDataContext at here
public class SimpleSecurityContext : DbContext { public DbSet<User> Users { get; set; } public SimpleSecurityContext(string connStringName) : base(connStringName) { this.Configuration.LazyLoadingEnabled = true; this.Configuration.ProxyCreationEnabled = false; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Configurations.Add(new UserMapping()); } }
And Mapping for User as below
public class UserMapping : EntityMappingBase<User> { public UserMapping() { this.Property(x => x.UserName); this.Property(x => x.DisplayName); this.Property(x => x.Password); this.Property(x => x.Email); this.ToTable("User"); } }
One important thing, you need to modify the web.config to point to our customize SimpleMembership
<membership defaultProvider="AdminMemberProvider" userIsOnlineTimeWindow="15"> <providers> <clear/> <add name="AdminMemberProvider" type="CIK.News.Web.Infras.Security.CustomAdminMembershipProvider, CIK.News.Web.Infras" /> </providers> </membership> <roleManager enabled="false"> <providers> <clear /> <add name="AdminRoleProvider" type="CIK.News.Web.Infras.Security.AdminRoleProvider, CIK.News.Web.Infras" /> </providers> </roleManager>
The good thing at here is we don’t need to modify the code
on AccountController. We only need to modify on
SimpleMembership and Simple Role (if need).
Now build
all solutions, run it. We should see a screen like this
If I login to Twitter button at the bottom of this page,
we will be transfer to twitter authentication page
You have to waiting for a moment 
Afterwards it will transfer you back to your admin
screen
You can find all source codes at my
MSDN code. I will really happy if you guys feel free to put some
comments as below. It will be helpful to improvement my code
in the future. Thank for all your readings.