ASP.NET MVC - an alternative to a role provider?

I try to avoid using the role provider and the membership provider, as in my opinion it is too clumsy, and therefore I am trying to make my own β€œversion”, which is less awkward and more manageable / flexible. Now my question is ... is there an alternative to a role provider that is decent? (I know that I can personalize the role, membership provider, etc.)

More manageable / flexible, I mean that I am limited to using the static Roles class and do not directly implement at my service level, which interacts with the database context, instead I must use the static Roles class, which has its own database context, etc. e. also table names are terrible.

Thanks in advance.

+46
authentication authorization asp.net-mvc asp.net-membership
Jan 29 2018-11-21T00:
source share
5 answers

I am in the same boat as you - I always hated RoleProviders. Yes, they are great if you want to put things on a small website, but they are not very realistic. The main drawback I have always found is that they bind you directly to ASP.NET.

The way I went on a recent project is to identify a pair of interfaces that are part of the service level (NOTE: I simplified them quite a bit, but you can easily add to them):

public interface IAuthenticationService { bool Login(string username, string password); void Logout(User user); } public interface IAuthorizationService { bool Authorize(User user, Roles requiredRoles); } 

Then your users can have a Roles listing:

 public enum Roles { Accounting = 1, Scheduling = 2, Prescriptions = 4 // What ever else you need to define here. // Notice all powers of 2 so we can OR them to combine role permissions. } public class User { bool IsAdministrator { get; set; } Roles Permissions { get; set; } } 

For your IAuthenticationService , you can have a basic implementation that does standard password verification, and then you can have a FormsAuthenticationService that does a little more, like setting a cookie, etc. For your AuthorizationService d you need something like this:

 public class AuthorizationService : IAuthorizationService { public bool Authorize(User userSession, Roles requiredRoles) { if (userSession.IsAdministrator) { return true; } else { // Check if the roles enum has the specific role bit set. return (requiredRoles & user.Roles) == requiredRoles; } } } 

In addition to these basic services, you can easily add services to reset passwords, etc.

Since you are using MVC, you can do authorization at the action level using ActionFilter :

 public class RequirePermissionFilter : IAuthorizationFilter { private readonly IAuthorizationService authorizationService; private readonly Roles permissions; public RequirePermissionFilter(IAuthorizationService authorizationService, Roles requiredRoles) { this.authorizationService = authorizationService; this.permissions = requiredRoles; this.isAdministrator = isAdministrator; } private IAuthorizationService CreateAuthorizationService(HttpContextBase httpContext) { return this.authorizationService ?? new FormsAuthorizationService(httpContext); } public void OnAuthorization(AuthorizationContext filterContext) { var authSvc = this.CreateAuthorizationService(filterContext.HttpContext); // Get the current user... you could store in session or the HttpContext if you want too. It would be set inside the FormsAuthenticationService. var userSession = (User)filterContext.HttpContext.Session["CurrentUser"]; var success = authSvc.Authorize(userSession, this.permissions); if (success) { // Since authorization is performed 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 or not a page should be served from the cache. var cache = filterContext.HttpContext.Response.Cache; cache.SetProxyMaxAge(new TimeSpan(0)); cache.AddValidationCallback((HttpContext context, object data, ref HttpValidationStatus validationStatus) => { validationStatus = this.OnCacheAuthorization(new HttpContextWrapper(context)); }, null); } else { this.HandleUnauthorizedRequest(filterContext); } } private void HandleUnauthorizedRequest(AuthorizationContext filterContext) { // Ajax requests will return status code 500 because we don't want to return the result of the // redirect to the login page. if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) { filterContext.Result = new HttpStatusCodeResult(500); } else { filterContext.Result = new HttpUnauthorizedResult(); } } public HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) { var authSvc = this.CreateAuthorizationService(httpContext); var userSession = (User)httpContext.Session["CurrentUser"]; var success = authSvc.Authorize(userSession, this.permissions); if (success) { return HttpValidationStatus.Valid; } else { return HttpValidationStatus.IgnoreThisRequest; } } } 

What you can decorate on your controller actions:

 [RequirePermission(Roles.Accounting)] public ViewResult Index() { // ... } 

The advantage of this approach is that you can also use dependency injection and an IoC container to connect to the network. In addition, you can use it for several applications (and not just for ASP.NET). You must use ORM to determine the appropriate schema.

If you need more information about FormsAuthorization/Authentication services or where to go from here, let me know.

EDIT: To add a "security trim" you can do this with HtmlHelper. It probably needs a little more ... but you get the idea.

 public static bool SecurityTrim<TModel>(this HtmlHelper<TModel> source, Roles requiredRoles) { var authorizationService = new FormsAuthorizationService(); var user = (User)HttpContext.Current.Session["CurrentUser"]; return authorizationService.Authorize(user, requiredRoles); } 

And then inside your view (using Razor syntax here):

 @if(Html.SecurityTrim(Roles.Accounting)) { <span>Only for accounting</span> } 

EDIT: UserSession will look something like this:

 public class UserSession { public int UserId { get; set; } public string UserName { get; set; } public bool IsAdministrator { get; set; } public Roles GetRoles() { // make the call to the database or whatever here. // or just turn this into a property. } } 

Thus, we do not disclose the password hash and all other details inside the session of the current user, since they really are not needed for the life of the user's session.

+87
Jan 29 '11 at 15:12
source share

I have implemented a role provider based on @TheCloudlessSky post here. There are several things that I thought I could add and share what I did. First, if you want to use the RequirepPermission class for your action filters as an attribute, you need to implement the ActionFilterAttribute class for the RequirepPermission class.

IAuthenticationService and IAuthorizationService Interface Classes

 public interface IAuthenticationService { void SignIn(string userName, bool createPersistentCookie); void SignOut(); } public interface IAuthorizationService { bool Authorize(UserSession user, string[] requiredRoles); } 

FormsAuthenticationService class

 /// <summary> /// This class is for Form Authentication /// </summary> public class FormsAuthenticationService : IAuthenticationService { public void SignIn(string userName, bool createPersistentCookie) { if (String.IsNullOrEmpty(userName)) throw new ArgumentException(@"Value cannot be null or empty.", "userName"); FormsAuthentication.SetAuthCookie(userName, createPersistentCookie); } public void SignOut() { FormsAuthentication.SignOut(); } } 

UserSession calss

 public class UserSession { public string UserName { get; set; } public IEnumerable<string> UserRoles { get; set; } } 

Another point is the FormsAuthorizationService class and how we can assign the user httpContext.Session["CurrentUser"] . My approach in this situation is to create a new instance of the userSession class and directly assign the user from httpContext.User.Identity.Name variable, as you can see in the FormsAuthorizationService class.

 [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter { #region Fields private readonly IAuthorizationService _authorizationService; private readonly string[] _permissions; #endregion #region Constructors public RequirePermissionAttribute(string requiredRoles) { _permissions = requiredRoles.Trim().Split(',').ToArray(); _authorizationService = null; } #endregion #region Methods private IAuthorizationService CreateAuthorizationService(HttpContextBase httpContext) { return _authorizationService ?? new FormsAuthorizationService(httpContext); } public void OnAuthorization(AuthorizationContext filterContext) { var authSvc = CreateAuthorizationService(filterContext.HttpContext); // Get the current user... you could store in session or the HttpContext if you want too. It would be set inside the FormsAuthenticationService. if (filterContext.HttpContext.Session == null) return; if (filterContext.HttpContext.Request == null) return; var success = false; if (filterContext.HttpContext.Session["__Roles"] != null) { var rolesSession = filterContext.HttpContext.Session["__Roles"]; var roles = rolesSession.ToString().Trim().Split(',').ToList(); var userSession = new UserSession { UserName = filterContext.HttpContext.User.Identity.Name, UserRoles = roles }; success = authSvc.Authorize(userSession, _permissions); } if (success) { // Since authorization is performed 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 or not a page should be served from the cache. var cache = filterContext.HttpContext.Response.Cache; cache.SetProxyMaxAge(new TimeSpan(0)); cache.AddValidationCallback((HttpContext context, object data, ref HttpValidationStatus validationStatus) => { validationStatus = OnCacheAuthorization(new HttpContextWrapper(context)); }, null); } else { HandleUnauthorizedRequest(filterContext); } } private static void HandleUnauthorizedRequest(AuthorizationContext filterContext) { // Ajax requests will return status code 500 because we don't want to return the result of the // redirect to the login page. if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) { filterContext.Result = new HttpStatusCodeResult(500); } else { filterContext.Result = new HttpUnauthorizedResult(); } } private HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) { var authSvc = CreateAuthorizationService(httpContext); if (httpContext.Session != null) { var success = false; if (httpContext.Session["__Roles"] != null) { var rolesSession = httpContext.Session["__Roles"]; var roles = rolesSession.ToString().Trim().Split(',').ToList(); var userSession = new UserSession { UserName = httpContext.User.Identity.Name, UserRoles = roles }; success = authSvc.Authorize(userSession, _permissions); } return success ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest; } return 0; } #endregion } internal class FormsAuthorizationService : IAuthorizationService { private readonly HttpContextBase _httpContext; public FormsAuthorizationService(HttpContextBase httpContext) { _httpContext = httpContext; } public bool Authorize(UserSession userSession, string[] requiredRoles) { return userSession.UserRoles.Any(role => requiredRoles.Any(item => item == role)); } } 

then in your controller, after user authentication, you can get roles from the database and assign it to the roles session:

 var roles = Repository.GetRolesByUserId(Id); if (ControllerContext.HttpContext.Session != null) ControllerContext.HttpContext.Session.Add("__Roles",roles); FormsService.SignIn(collection.Name, true); 

After the user logs out, you can clear the session

 FormsService.SignOut(); Session.Abandon(); return RedirectToAction("Index", "Account"); 

The caveat in this model is that when a user logs on to the system, if the role is assigned to the user, authorization does not work if he does not log out and does not return to the system.

Another thing is that there is no need to have a separate class for roles, since we can get roles directly from the database and install it in the role session in the controller.

After you finish implementing all of these codes, the last step is to bind this attribute to your methods in your controller:

 [RequirePermission("Admin,DM")] public ActionResult Create() { return View(); } 
+5
Jun 17 2018-11-11T00:
source share

If you use Castle Windsor's Injection Dependency Injection, you can enter RoleProviders lists that can be used to determine user rights from any source you want to implement.

http://ivida.co.uk/2011/05/18/mvc-getting-user-roles-from-multiple-sources-register-and-resolve-arrays-of-dependencis-using-the-fluent-api/

+2
Jun 18 2018-11-11T00:
source share

You do not need to use a static class for roles. For example, SqlRoleProvider allows you to define roles in a database.

Of course, if you want to get roles from your own level of service, it is not so difficult to create your own role provider - in fact, there are not many methods for implementation.

+1
Jan 29 '11 at 1:58 p.m.
source share

You can implement your own membership and provider role by overriding the corresponding interfaces.

If you want to start from scratch, these types are usually implemented as a custom http module that stores user credentials in either an httpcontext or session. In any case, you probably want to set a cookie with some kind of authentication token.

0
Jan 29 '11 at 2:00
source share



All Articles