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

How to create custom memberships for ASP.NET MVC 2 based on the ASP.NET membership provider?

+57
c # asp.net-mvc membership-provider custom-membershipprovider
May 05 '10 at 6:43 a.m.
source share
4 answers

I created a new project containing a custom membership provider and overriding the ValidateUser method from the abstract MembershipProvider 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 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 this provider to my ASP.NET MVC 2 project, adding a link and pointing it to 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 need to create my own class that inherits the abstract RoleProvider class and overrides the GetRolesForUser method. Authorization ASP.NET MVC uses this method to determine which roles are assigned to the currently logged-in user and allows the user to allow access to the controller action.

Here are the steps we need to take:

1) Create your own class that inherits the abstract RoleProvider 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 through 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 Authorize (Roles = "xxx, yyy") over the desired controller / action:

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

What is it! Now it works!

4) Optional: set your own Authorize attribute so that we can redirect the unwanted role to the 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 attribute to redirect our users to access the restricted view:

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

What is it! Super Duper!

Here are some of the links that I used to get all this information:

Special Role Provider: http://davidhayden.com/blog/dave/archive/2007/10/17/CreateCustomRoleProviderASPNETRolePermissionsSecurity.aspx

I hope this information helps!

+115
May 6 '10 at 10:41
source share

It can also be used with much less code, I'm not quite sure that this method is safe, but works great with any database you use.

in global.asax

 protected void Application_AuthenticateRequest(object sender, EventArgs e) { if (HttpContext.Current.User != null) { if (HttpContext.Current.User.Identity.IsAuthenticated) { if (HttpContext.Current.User.Identity is FormsIdentity) { FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity; FormsAuthenticationTicket ticket = id.Ticket; // Get the stored user-data, in this case, our roles string userData = ticket.UserData; string[] roles = userData.Split(','); HttpContext.Current.User = new GenericPrincipal(id, roles); } } } } 

what it does is that it reads roles from authCookie that were made from FormsAuthenticationTicket

and the input logic looks like this:

 public class dbService { private databaseDataContext db = new databaseDataContext(); public IQueryable<vwPostsInfo> AllPostsAndDetails() { return db.vwPostsInfos; } public IQueryable<role> GetUserRoles(int userID) { return (from r in db.roles join ur in db.UsersRoles on r.rolesID equals ur.rolesID where ur.userID == userID select r); } public IEnumerable<user> GetUserId(string userName) { return db.users.Where(u => u.username.ToLower() == userName.ToLower()); } public bool logOn(string username, string password) { try { var userID = GetUserId(username); var rolesIQueryable = GetUserRoles(Convert.ToInt32(userID.Select(x => x.userID).Single())); string roles = ""; foreach (var role in rolesIQueryable) { roles += role.rolesName + ","; } roles.Substring(0, roles.Length - 2); FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( 1, // Ticket version username, // Username associated with ticket DateTime.Now, // Date/time issued DateTime.Now.AddMinutes(30), // Date/time to expire true, // "true" for a persistent user cookie roles, // User-data, in this case the roles FormsAuthentication.FormsCookiePath);// Path cookie valid for // Encrypt the cookie using the machine key for secure transport string hash = FormsAuthentication.Encrypt(ticket); HttpCookie cookie = new HttpCookie( FormsAuthentication.FormsCookieName, // Name of auth cookie hash); // Hashed ticket // Set the cookie expiration time to the tickets expiration time if (ticket.IsPersistent) cookie.Expires = ticket.Expiration; // Add the cookie to the list for outgoing response HttpContext.Current.Response.Cookies.Add(cookie); return true; } catch { return (false); } } } 

i stores roles in my database with two tables: table: Role, which has columns: roleID and roleName and table: UsersRoles, which has columns: userID and roleID, this allows you to use multiple roles for several users and easily make your own logic for adding / removing roles from users, etc. This allows you to use [Authorize (Roles = "Super Admin")]. hope this helps.

edit: forgot to do a password check, but you just added an if to the logOn method, which checks if the username and password are checked and if it does not return false

+8
May 27 '10 at 10:10
source share

I used the source code of the NauckIt.PostgreSQL provider as a base and changed it to suit my needs.

+1
May 05 '10 at 22:00
source share



All Articles