I'm trying to authenticate a user using forms authentication in MVC 4 (I'm using RavenDB so I can't use the standard membership providers). Then later I'm using the User.IsInRole()
method or AuthorizeAttribute
to verify the user is in a staff role.
Here's where I set the ticket on successful authentication (at the moment in UserController.cs
):
FormsAuthenticationTicket ticket =
new FormsAuthenticationTicket(
1,
model.Email,
DateTime.Now,
DateTime.Now.AddDays(1),
false,
model.Email);
string hashedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie =
new HttpCookie(
FormsAuthentication.FormsCookieName,
hashedTicket);
HttpContext.Response.Cookies.Add(cookie);
Here's where I check the ticket for each request (Global.asax
):
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
var user = this.UserService.GetUserByEmail(authTicket.Name);
var identity = new GenericIdentity(authTicket.Name, "Forms");
var principal = new GenericPrincipal(identity, user.Roles);
HttpContext.Current.User = principal;
}
}
If I then put a debug point on one of my action methods (CalendarController.cs), I get isStaff
equals false
:
public ActionResult Index()
{
var user = HttpContext.User;
bool isStaff = user.IsInRole(Role.Staff);
return View();
}
Just for completion (Roles.cs, just a temporary class to test things):
public static class Role
{
public static string Staff
{
get { return "Staff"; }
}
public static string Manager
{
get { return "Manager"; }
}
}
Can anyone help give me a point as to what I might be missing? It looks as though the roles I set are disappearing by the time I get to the action method.
Thanks guys for helping me with this, what I've come up with (included below) works great! It auto-logs users straight in through the login screen if they have a valid ticket (cookie) and also handles Claims based roles using the ClaimsIdentity
and ClaimsPrincipal
objects, without putting the roles in the user's cookie. It also handles authentication in the Global.asax.cs
file without having to resort to putting in custom authorize attributes.
UserController.cs
public ActionResult Login()
{
LoginViewModel model = new LoginViewModel();
if ((HttpContext.User != null) &&
(HttpContext.User.Identity.IsAuthenticated))
{
return RedirectToAction("Index", "Home");
}
return View(model);
}
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
bool isAuthenticated = this.userService.IsPasswordValid(model.Email, model.Password);
if (!isAuthenticated)
{
ModelState.AddModelError("AuthError", Resources.User.Login.AuthError);
return View(model);
}
FormsAuthentication.SetAuthCookie(model.Email, model.RememberUser);
return RedirectToAction("Index", "Home");
}
Global.asax.cs
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsIdentity formsIdentity = new FormsIdentity(ticket);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(formsIdentity);
var user = this.UserService.GetUserByEmail(ticket.Name);
foreach (var role in user.Roles)
{
claimsIdentity.AddClaim(
new Claim(ClaimTypes.Role, role));
}
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
HttpContext.Current.User = claimsPrincipal;
}
}
Since you are using Raven I am assuming you made your own custom MembershipProvider and RoleProvider; and modified the web.config to use them. You should have an entry similar to this:
<membership defaultProvider="MyMembershipProvider">
<providers>
<add name="MyMembershipProvider" type="namespace.MyMembershipProvider, providerAssemblyName" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="DefaultRoleProvider">
<providers>
<add connectionStringName="DefaultConnection" applicationName="/" name="DefaultRoleProvider" type="namespace.MyRoleProvider, providerAssemblyName" />
</providers>
</roleManager>
If you are using .NET Framework version 4.5 it uses claims-based security and you do not need to store the roles in the cookie. Instead roles are just another claim that is stored in the ClaimsPrincipal. All principals now inherit from ClaimsPrincipal and it is stored for the users session in
System.Web.HttpContext.Current.User as ClaimsPrincipal
If your membership and role providers are setup correctly ASP.NET should use them to populate the roles in ClaimsPrincipal and then check the claims when you check IsInRole.
You can also retrieve roles from the ClaimsPrincipal for roles like this.
principal.FindAll(ClaimTypes.Role).Select(p => p.Value);
And you can add roles to a ClaimsPrincipal like this.
List<Claim> claims = new List<Claim>();
foreach (string role in roles)
claims.Add(new Claim(ClaimTypes.Role, role));
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Forms"));
Now you can just set your cookie after authentication like this.
FormsAuthentication.SetAuthCookie(username, false);
you are not creating the FormsAuthenticationTicket inserting roles info:
var ticket = new FormsAuthenticationTicket(
1, //ticket version
userName,
DateTime.Now,
DateTime.Now.Add(timeout), //timeout
true, //persistent cookies
roles,// <---ROLES not model.Email
FormsAuthentication.FormsCookiePath);
------EDIT-----
Forget what I said: i think you are calling IsInRole() too early or user.Roles has the wrong value (maybe spaces in strings: isinrole uses StringComparison.OrdinalIgnoreCase) or you should use FormsIdentity instead of GenericIdentity.
What the debugger says?
For reference: http://pastebin.com/jkqqcg28
(this is the starting model I use for handling authentication)