So @thomas seems to have a good answer, but it wraps your requirement for using enums more, taking it into roles that IPricipal
will understand. My solution is bottom-up, so you can use the thomas solution on top of mine to implement IPrincipal
I really needed something similar to what you want, and I was always afraid by using forms authentication (yes, you are scared too, and I know that, but listen to me). Therefore, I always issued my own cheap authentication using forms, but a lot of things changed when I studied mvc (in the last couple of weeks) Forms auth is very separate and very flexible. Essentially, you don't really use auth forms, but just include your own logic in the system.
So, this is how I dealt with this, ( beware, I'm a student myself ).
Summary:
- You are going to override some auth classes to authenticate your users (you can even mock this)
- Then you will create
IIdentity
. - Download the
GenericPrincipal
list of roles in rows (I know no magic lines ... keep reading)
Once you do this, MVC understands enough to give you what you want! Now you can use [Authorize(Roles = "Write,Read")]
on any controller, and MVC will do almost everything. Now, without the magic lines, all you need to do is create a wrapper around this attribute.
Long answer
You are using the Internet application template that comes with MVC. So first you start by creating an MVC project, in a new dialog box, say you want an Internet application .
When validating the application, it will have one main class that overrides forms authentication. IMembershipService
Remove the localhipProvider __provider_ variable and in this class you should add logic to the ValidateUser
method if possible. (Try adding fake authentication to one user / pass) Also see the default vv application created in VS.
Introduce IIdentity
public class MyIdentity : IIdentity { public MyIdentity(string username) { _username = username;
Remember that we still use the default web application template that comes with the MVC project.
So, now AccountController.LogOn () should look like this:
[HttpPost] public virtual ActionResult LogOn(LogOnModel model, string returnUrl) { if (ModelState.IsValid) { if (MembershipService.ValidateUser(model.UserName, model.Password)) { FormsService.SignIn(model.UserName, model.RememberMe); FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(model.UserName, model.RememberMe, 15); string encTicket = FormsAuthentication.Encrypt(ticket); this.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)); if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } } else { ModelState.AddModelError("", "The user name or password provided is incorrect."); } }
So what you do is set a form ticket, for example a session, and then we will read it for each request, for example: Put this in Global.asax.cs
public override void Init() { this.PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest); base.Init(); } void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e) { HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName]; if (authCookie != null) { string encTicket = authCookie.Value; if (!String.IsNullOrEmpty(encTicket)) { FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encTicket); MyIdentity id = new MyIdentity(ticket.Name);
I asked one question: how effective and correct is the method above here .
Ok, now you're almost done, you can decorate your controllers as follows: [Authorize(Roles="RoleA,RoleB")]
(more on the lines later)
There is one small problem here, if you decorate the controller using AuthorizeAttribute
, and the registered user does not have specific permission, instead of specifying "access denied" by default, the user will be redirected to the login page to log in again. You fix it as follows (I changed this from SO answer):
public class RoleAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { // Returns HTTP 401 // If user is not logged in prompt if (!filterContext.HttpContext.User.Identity.IsAuthenticated) { base.HandleUnauthorizedRequest(filterContext); } // Otherwise deny access else { filterContext.Result = new RedirectToRouteResult(@"Default", new RouteValueDictionary{ {"controller","Account"}, {"action","NotAuthorized"} }); } } }
Now all you do is add another wrapper around AuthorizeAttribute
to support strong types that will translate into strings that Principal expects. See this article for more details .
I plan to update my application to use strong types later, then I will update this answer.
I hope this helps.