How to Create a Custom Membership Provider for ASP.NET MVC 2

How do I create a custom membership provider for ASP.NET MVC 2?

I have created a new project containing a custom membership provider and overrode the ValidateUser method from the MembershipProvider abstract class:

public class MyMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
// this is where you should validate your user credentials against your database.
// I've made an extra class so i can send more parameters
// (in this case it's the CurrentTerritoryID parameter which I used as
// one of the MyMembershipProvider class properties).

var oUserProvider = new MyUserProvider();
return oUserProvider.ValidateUser(username,password,CurrentTerritoryID);
}
}

Then I connected that provider to my ASP.NET MVC 2 project by adding a reference and pointing it out from my web.config:

<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider"
applicationName="MyApp"
Description="My Membership Provider"
passwordFormat="Clear"
connectionStringName="MyMembershipConnection"
type="MyApp.MyMembershipProvider" />
</providers>
</membership>

I do need to create a custom class that inherits the RoleProvider abstract class and overrides the GetRolesForUser method.
The ASP.NET MVC Authorizing uses that method to find out which roles are assigned to the current logged-on user and makes sure the user is permitted to access the controller action.

Here are the steps we need to take:

1) Create a custom class that inherits the RoleProvider abstract class and overrides the GetRolesForUser method:

public override string[] GetRolesForUser(string username)
{
SpHelper db = new SpHelper();
DataTable roleNames = null;
try
{
// get roles for this user from DB...

roleNames = db.ExecuteDataset(ConnectionManager.ConStr,
"sp_GetUserRoles",
new MySqlParameter("_userName", username)).Tables[0];
}
catch (Exception ex)
{
throw ex;
}
string[] roles = new string[roleNames.Rows.Count];
int counter = 0;
foreach (DataRow row in roleNames.Rows)
{
roles[counter] = row["Role_Name"].ToString();
counter++;
}
return roles;
}

2) Connect the role provider with the ASP.NET MVC 2 application via our web.config:

<system.web>
...

<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear />
<add name="MyRoleProvider"
applicationName="MyApp"
type="MyApp.MyRoleProvider"
connectionStringName="MyMembershipConnection" />
</providers>
</roleManager>

...
</system.web>

3) Set the Authorize(Roles="xxx,yyy") above the wanted Controller / Action:

[Authorization(Roles = "Customer Manager,Content Editor")]
public class MyController : Controller
{
......
}

That's it! Now it works!

4) Optional: set a custom Authorize attribute so we can redirect an unwanted role to an AccessDenied Page:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class MyAuthorizationAttribute : AuthorizeAttribute
{
/// <summary>
/// The name of the master page or view to use when rendering the view on authorization failure. Default
/// is null, indicating to use the master page of the specified view.
/// </summary>
public virtual string MasterName { get; set; }

/// <summary>
/// The name of the view to render on authorization failure. Default is "Error".
/// </summary>
public virtual string ViewName { get; set; }

public MyAuthorizationAttribute ()
: base()
{
this.ViewName = "Error";
}

protected void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}

public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}

if (AuthorizeCore(filterContext.HttpContext))
{
SetCachePolicy(filterContext);
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole("SuperUser"))
{
// is authenticated and is in the SuperUser role
SetCachePolicy(filterContext);
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add("Message", "You do not have sufficient privileges for this operation.");
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}

protected void SetCachePolicy(AuthorizationContext filterContext)
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
}

Now we can use our own made attribute to redirect our users to access denied view:

[MyAuthorization(Roles = "Portal Manager,Content Editor", ViewName = "AccessDenied")]
public class DropboxController : Controller
{
.......
}

That's it!
Super duper!

Here are some of the links I've used to get all this info:

Custom role provider:
http://davidhayden.com/blog/dave/archive/2007/10/17/CreateCustomRoleProviderASPNETRolePermissionsSecurity.aspx

I hope this info helps!

ASP.NET MVC Custom Membership Provider - How to overload CreateUser?

Isn't it obvious? You're referencing Membership, not MyMembershipProvider when you call Membership.CreateUser.

It looks like Membership is a static class that internally calls into your membership provider, so there is no real way to change this other than creating your own "MyMembership" static class that does the same thing. Since you can't inherit from static classes, you would have to either call into Membership.XXX from your static version, or implement the functionality yourself.

Your own MyMembership class could then have any methods you wanted it to have.

Another option would be to do something like this:

((MyMembershipProvider)Membership.Provider).CreateUser()

I don't like this approach though because it requires you to remember to call the provider CreateUser rather than the Membership.CreateUser. Plus, casting is often a code smell (unless encapsulated, which is why i recommended your own MyMembership static class).

Custom Membership Provider and Domain-Driven-Design

Asp.net user management features are super invasive.

They even spam database with profile tables and what not.

When I had to implement users management of my application, I successfully avoided all that mess and still was able to use asp.net in-built roles, user identities etc. Moving away from all that though cause my domain is getting smart enough to decide what can be seen and done so it makes no sense to duplicate that in UI client.

So... yeah. Still have zero problems with this approach. Haven't changed anything for ~4 months.

Works like a charm.

VB ASP .NET MVC Custom Membership Provider

You need to write a class that inherits from MembershipProvider and override the methods you are interested in:

Public Class MyCustomMembershipProvider
Inherits System.Web.Security.MembershipProvider

Public Overrides Property ApplicationName As String
Get

End Get
Set(value As String)

End Set
End Property

Public Overrides Function ChangePassword(username As String, oldPassword As String, newPassword As String) As Boolean

End Function

Public Overrides Function ChangePasswordQuestionAndAnswer(username As String, password As String, newPasswordQuestion As String, newPasswordAnswer As String) As Boolean

End Function

Public Overrides Function CreateUser(username As String, password As String, email As String, passwordQuestion As String, passwordAnswer As String, isApproved As Boolean, providerUserKey As Object, ByRef status As System.Web.Security.MembershipCreateStatus) As System.Web.Security.MembershipUser

End Function

Public Overrides Function DeleteUser(username As String, deleteAllRelatedData As Boolean) As Boolean

End Function

Public Overrides ReadOnly Property EnablePasswordReset As Boolean
Get

End Get
End Property

Public Overrides ReadOnly Property EnablePasswordRetrieval As Boolean
Get

End Get
End Property

Public Overrides Function FindUsersByEmail(emailToMatch As String, pageIndex As Integer, pageSize As Integer, ByRef totalRecords As Integer) As System.Web.Security.MembershipUserCollection

End Function

Public Overrides Function FindUsersByName(usernameToMatch As String, pageIndex As Integer, pageSize As Integer, ByRef totalRecords As Integer) As System.Web.Security.MembershipUserCollection

End Function

Public Overrides Function GetAllUsers(pageIndex As Integer, pageSize As Integer, ByRef totalRecords As Integer) As System.Web.Security.MembershipUserCollection

End Function

Public Overrides Function GetNumberOfUsersOnline() As Integer

End Function

Public Overrides Function GetPassword(username As String, answer As String) As String

End Function

Public Overloads Overrides Function GetUser(providerUserKey As Object, userIsOnline As Boolean) As System.Web.Security.MembershipUser

End Function

Public Overloads Overrides Function GetUser(username As String, userIsOnline As Boolean) As System.Web.Security.MembershipUser

End Function

Public Overrides Function GetUserNameByEmail(email As String) As String

End Function

Public Overrides ReadOnly Property MaxInvalidPasswordAttempts As Integer
Get

End Get
End Property

Public Overrides ReadOnly Property MinRequiredNonAlphanumericCharacters As Integer
Get

End Get
End Property

Public Overrides ReadOnly Property MinRequiredPasswordLength As Integer
Get

End Get
End Property

Public Overrides ReadOnly Property PasswordAttemptWindow As Integer
Get

End Get
End Property

Public Overrides ReadOnly Property PasswordFormat As System.Web.Security.MembershipPasswordFormat
Get

End Get
End Property

Public Overrides ReadOnly Property PasswordStrengthRegularExpression As String
Get

End Get
End Property

Public Overrides ReadOnly Property RequiresQuestionAndAnswer As Boolean
Get

End Get
End Property

Public Overrides ReadOnly Property RequiresUniqueEmail As Boolean
Get

End Get
End Property

Public Overrides Function ResetPassword(username As String, answer As String) As String

End Function

Public Overrides Function UnlockUser(userName As String) As Boolean

End Function

Public Overrides Sub UpdateUser(user As System.Web.Security.MembershipUser)

End Sub

Public Overrides Function ValidateUser(username As String, password As String) As Boolean

End Function
End Class

And then you register your custom provider in web.config:

<membership defaultProvider="MyMembership">
<providers>
<clear />
<add
name="MyMembership"
type="MvcApplication1.MyCustomMembershipProvider, MvcApplication1" enablePasswordRetrieval="false"
/>
</providers>
</membership>

Now from within your controllers you simply use the Membership class. For example in your LogOn action that was generated by the default template when you created your project you don't need to change absolutely anything:

<HttpPost()> _
Public Function LogOn(ByVal model As LogOnModel, ByVal returnUrl As String) As ActionResult
If ModelState.IsValid Then
If Membership.ValidateUser(model.UserName, model.Password) Then
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe)
If Url.IsLocalUrl(returnUrl) AndAlso returnUrl.Length > 1 AndAlso returnUrl.StartsWith("/") _
AndAlso Not returnUrl.StartsWith("//") AndAlso Not returnUrl.StartsWith("/\\") Then
Return Redirect(returnUrl)
Else
Return RedirectToAction("Index", "Home")
End If
Else
ModelState.AddModelError("", "The user name or password provided is incorrect.")
End If
End If

' If we got this far, something failed, redisplay form
Return View(model)
End Function

All calls to Membership will now use your custom membership provider that you registered in web.config.

ASP.NET MVC2 Custom Membership Provider w/ Data Provider Interface Issue

I ended up using Assembly.CreateInstance like so:

Factory:

    public class DataProviderFactory
{
public IDataProvider GetProvider(string assemblyName, string type, string connectionString)
{
var assembly = System.Reflection.Assembly.Load(new AssemblyName(assemblyName));
return (IDataProvider)assembly.CreateInstance(type, true, BindingFlags.CreateInstance, null, new object[]{ connectionString }, null, null);
}
}

RoleProvider:

 public class MvcRoleProvider : RoleProvider
{
public string ConnectionString { get; set; }

private IDataProvider _dataProvider;

public override void Initialize(string name, NameValueCollection config)
{
DataProviderFactory factory = new DataProviderFactory();
_dataProvider = factory.GetProvider(config["dataProviderAssembly"], config["dataProviderType"], ConfigurationManager.AppSettings[config["connectionStringName"]]);

base.Initialize(name, config);
}

With the following settings in web.config:

<roleManager enabled="true" defaultProvider="MvcRoleProvider">
<providers>
<clear />
<add name="MvcRoleProvider"
applicationName="MyWebNameSpace"
type="MvcMembership.MvcRoleProvider"
dataProviderType="MyWebNameSpace.MyWebProject.DataProviderClassName" dataProviderAssembly="EmailService.Web.EmailAdmin"
connectionStringName="MyMembershipConnection" />
</providers>
</roleManager>

Custom Membership Provider or Profile Provider in mvc asp.net

One, Membership providers and Profile providers serve two different purposes. The Membership provider provides a user list and authentication functionality. The Profile provider provides a way to store application-specific data associated with each user.

Whether you need a custom provider depends on what type of datastore you want to use. The two built-in Membership providers allow for using an Active Directory domain (generally only appropriate if you're developing an application for a corporate intranet) or an MS SQL Server Database. The one built-in Profile provider uses MS SQL. If you want to use a different type of datastore, for example a PostgreSQL database, then you'll need a custom provider for that particular datastore.

How can you inject an asp.net (mvc2) custom membership provider using Ninject?

This is how I was able to do this:

1) I created a static helper class for Ninject

public static class NinjectHelper
{
public static readonly IKernel Kernel = new StandardKernel(new FooServices());

private class FooServices : NinjectModule
{
public override void Load()
{
Bind<IFooRepository>()
.To<EntityFooRepository>()
.WithConstructorArgument("connectionString",
ConfigurationManager.ConnectionStrings["FooDb"].ConnectionString);
}
}
}

2) Here is my Membership override:

    public class FooMembershipProvider : MembershipProvider
{
private IFooRepository _FooRepository;

public FooMembershipProvider()
{
NinjectHelper.Kernel.Inject(this);
}

[Inject]
public IFooRepository Repository
{
set
{
_FooRepository = value;
}
}
...

With this approach it doesn't really matter when the Membership provider is instantiated.

How can I attach a custom membership provider in my ASP.NET MVC application?

Hopefully I can add some additional clarity over the other answers as they really don't explain what's going on which isn't going to help your confusion.

First up, implement your custom provider which from the sound of things you've done already, so I'll just throw up a little code snippet and won't go into any further detail here:

using System.Web.Security;

public class MyCustomMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
if (username.Equals("BenAlabaster") && password.Equals("Elephant"))
return true;

return false;
}

/* Override all the other methods required to extend MembershipProvider */
}

Then you configure your provider in your web.config making sure to populate the attributes that configure the base MembershipProvider:

<membership defaultProvider="MyCustomMembershipProvider">      
<providers>
<clear />
<add name="MyCustomMembershipProvider"
type="MyNamespace.MyCustomMembershipProvider"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="10"
minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""
applicationName="/" />
</providers>
</membership>

The next bit I think you're overthinking, the actual tie-in to your web application. Whereas in a WebForms app you kind of have to code the rest for yourself - the MVC framework does the rest for you - all you need to do is add the [Authorize] attribute to your action method and the framework will check to see if you're logged in, and if not redirect you to the login page. The login page will find your custom provider because that's what's configured in the web.config and will log your user in. You can access information about the logged in user from your controllers by referencing the User object:

public class WhateverController : Controller
{
[Authorize]
public ActionResult WhateverAction()
{
ViewData["LoggedInAs"] = string.Format("You are logged in as {0}.", User.Identity.Name);
Return View();
}
}

So this action requires that the user is logged in and presents the user information to the Whatever/WhateverAction.aspx view to be displayed on the page.



Related Topics



Leave a reply



Submit