ASP.NET MVC 5 Custom Role Provider Not Working? - asp.net

i have a problem with my custom role providor "string[] GetRolesForUser(string username)" method but the proccess dont go through it. My code is:
Controller (Just a piece of it):
[Authorize(Roles="Administrator")]
public class UsersController : AdminBaseController
{
private IUsersRepository users;
private IDepartmentsRepository departments;
public UsersController()
{
this.users = new UsersRepository(new TicketsContext());
this.departments = new DepartmentsRepository(new TicketsContext());
}
}
Custom Role Provider:
public class CustomRoleProvider : RoleProvider
{
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = GetRolesForUser(username);
return userRoles.Contains(roleName);
}
public override string[] GetRolesForUser(string username)
{
//Return if the user is not authenticated
if (!HttpContext.Current.User.Identity.IsAuthenticated)
return null;
//Return if present in Cache
var cacheKey = string.Format("UserRoles_{0}", username);
if (HttpRuntime.Cache[cacheKey] != null)
return (string[])HttpRuntime.Cache[cacheKey];
//Get the roles from DB
var userRoles = new string[] { };
var user = db.Users.Where(u => u.email == username).FirstOrDefault();
if (user != null)
{
if(user.access_level == 0)
{
userRoles = new[] { "Administrator" };
}
else
{
userRoles = new[] { "Normal" };
}
}
//Store in cache
HttpRuntime.Cache.Insert(cacheKey, userRoles, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinutes), Cache.NoSlidingExpiration);
// Return
return userRoles.ToArray();
}
}
Web.config
<!-- Custom Role Provider -->
<roleManager enabled="true" defaultProvider="TicketsRoleProvider">
<providers>
<add name="TicketsRoleProvider"
type="Tickets.CustomRoleProvider"
cacheTimeoutInMinutes="30" />
</providers>
</roleManager>
I cant get it to work, and i dont know why.
Can anyone help me pls ?
Thanks

I can't see anything obvious, but I would:
Remove the check for "HttpContext.Current.User.Identity.IsAuthenticated" in the GetRolesForUser method. This method should just get the roles for the username supplied as an argument.
To debug, I'd start by examining HttpContext.Current.User.GetType() inside a controller action method (one that isn't secured). Is it of type System.Web.Security.RolePrincipal?
You should implement IsUserInRole in your custom RoleProvider. The implementation can just check that the supplied role is in the array returned by GetRolesForUser. I don't think this is your problem though: IsUserInRole isn't used by RolePrincipal (though it is used by System.ServiceModel.Security.RoleProviderPrincipal if you use ASP.NET roles in a WCF service, so it's a good idea to implement it anyway.
UPDATE
You've confirmed HttpContext.Current.User is a RolePrincipal. I suggest you examine it closely using the debugger. In particular examine:
the ProviderName property, which should match the name of your custom RoleProvider.
the Identity property, which should match the name of the current user.
You might also try calling HttpContext.Current.User.IsInRole from code in your controller. The first time this is called, it should call your custom RoleProvider's GetRolesForUser method.
You can also try calling the RolePrincipal.SetDirty() method: this marks the cached role list as having been changed, and the next call to IsInRole or GetRoles should call your custom RoleProvider's GetRolesForUser method again.

Related

ASP.net MVC + ASP.net Identity Seeding roles and users

In Migrations Configuration class, there is Seed method, and it looks like:
protected override void Seed(DatabaseContext context)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
var userManager = new UserManager<User>(new UserStore<User>(context));
if (!roleManager.RoleExists("Administrator"))
{
roleManager.Create(new IdentityRole("Administrator"));
}
var user = new User {UserName = "someUserName"};
if (userManager.FindByName("someUserName") == null)
{
var result = userManager.Create(user, "password");
if (result.Succeeded)
{
userManager.AddToRole(user.Id, "Administrator");
}
}
}
And then in PM console I've run update-database, and it successfully seeded database, and in table AspNetUserRoles I can see that user someUserName and Administrator role are connected.
But when I put attribute [Authorize(Roles = "Administrator")] above controller I've been redirected to log in page as if my user wasn't in that role, so I've added line in my controller:
var x = _userManager.IsInRole(_userManager.FindByName("someUserName").Id, "Administrator");
And x is false. Why is this happening ? And how can I fix it ? And if this isn't proper way to seed database, which is ?
Try in your seed method
context.Configuration.LazyLoadingEnabled = true;
Here's my guess at what is going on when lazy loading is disabled, the Roles property of the IdentityUser / ApplicationUser object is null or empty when the UserManager or UserStore accesses it because that collection was not manually loaded. The code then carries on like no roles have been assigned to the user when in fact that collection simply was never loaded.
Try
context.SaveChanges();
at the end of Seed method.

HttpContext.Current.User != HttpContext.User?

Is HttpContext.Current.User in global asax not the same as HttpContext.User in an action method? I assigned the user some roles, but they seem to get lost.
The code below shows what is happening. Both Asserts get hit when a user is logged on, first in global asax, then the action method. However they give different results.
First this:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// ... omitted some code to check user is authenticated
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = new string[] { "admin", "user" };
HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(identity, roles);
Assert(HttpContext.User.IsInRole("admin"));
}
Then this in my action method:
public ActionResult Index()
{
bool isAdmin = HttpContext.User.IsInRole("admin");
Assert(isAdmin); // this fails, isAdmin is false
// ...
}
I used the following resources
This SO answer
http://csharpdotnetfreak.blogspot.com/2009/02/formsauthentication-ticket-roles-aspnet.html
Your question tags say "aspnet-mvc (3 and 4)", so do you have the option of using the following to make your life easier? If you are using Simple Membership from the MVC 4 Internet Application template in VS2012 this will just work out of the box for you):
WebSecurity.CreateUserAndAccount(name, password) - to create a user
Roles.AddUserToRole (and AddUserToRoles) - add a user to a role
Roles.IsUserInRole - tests if a user is in a role
[Authorize(Roles = "admin")] - [Authorize] can enforce roles on an entire controller, or on an action
CreateUserAndAccount has the advantage that it's easy to set properties for the UserProfile as well, for example:
WebSecurity.CreateUserAndAccount(newUser.UserName, newUser.Password,
new { FullName = newUser.FullName, Email = newUser.Email, Timezone = newUser.TZ });
Roles.AddUserToRoles(newUser.UserName, new[] {"admin", "user"});
Edit, I realise the above doesn't answer your original question about .User property equivalence.
HttpContext in a Controller is a property: Controller.HttpContext. HttpContext in global.asax.cs is the static class, so that's why you use HttpContext.Current. They refer to the same thing.
If you run the following code, you can see they are apparently the "same principal". So the question is what happened to the roles you assigned?
protected void Application_AuthenticateRequest(object sender, EventArgs e) {
...
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = new string[] { "admin", "user" };
identity.Label = "test label";
System.Security.Principal.GenericPrincipal ppl = new System.Security.Principal.GenericPrincipal(identity, roles);
HttpContext.Current.User = ppl;
... }
public ActionResult Index() {
bool isAdmin = HttpContext.User.IsInRole("admin");
bool isAdmin2 = System.Web.HttpContext.Current.User.IsInRole("admin");
System.Web.Security.FormsIdentity identity = (System.Web.Security.FormsIdentity)HttpContext.User.Identity;
// The label is carried through from Application_AuthenticateRequest to Index.
string label = identity.Label;
}
The problem is, you assigned a GenericPrincipal to .User. Depending on the RoleProvider, this can be overwritten (e.g. by the RoleManagerModule) during PostAuthenticateRequest and (for example) turned into a RolePrincipal. This can then defer back to the database (again depending on provider) to get the roles, so over-writing your roles. If you do the work in Application_OnPostAuthenticateRequest you might be ok.

MVC 3 Authorize custom roles

I am new MVC 3 user and I am trying to make admin through SQL database.
First of all, I have Customer entity and admin can be defined through admin field which is boolean type in Customer entity.
I want to make to access admin only in Product page, not normal customer.
And I want to make [Authorize(Roles="admin")] instead of [Authorize].
However, I don't know how can I make admin role in my code really.
Then in my HomeController, I written this code.
public class HomeController : Controller
{
[HttpPost]
public ActionResult Index(Customer model)
{
if (ModelState.IsValid)
{
//define user whether admin or customer
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["rentalDB"].ToString());
String find_admin_query = "SELECT admin FROM Customer WHERE userName = '" + model.userName + "' AND admin ='true'";
SqlCommand cmd = new SqlCommand(find_admin_query, conn);
conn.Open();
SqlDataReader sdr = cmd.ExecuteReader();
//it defines admin which is true or false
model.admin = sdr.HasRows;
conn.Close();
//if admin is logged in
if (model.admin == true) {
Roles.IsUserInRole(model.userName, "admin"); //Is it right?
if (DAL.UserIsVaild(model.userName, model.password))
{
FormsAuthentication.SetAuthCookie(model.userName, true);
return RedirectToAction("Index", "Product");
}
}
//if customer is logged in
if (model.admin == false) {
if (DAL.UserIsVaild(model.userName, model.password))
{
FormsAuthentication.SetAuthCookie(model.userName, true);
return RedirectToAction("Index", "Home");
}
}
ModelState.AddModelError("", "The user name or password is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
And DAL class is
public class DAL
{
static SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["rentalDB"].ToString());
public static bool UserIsVaild(string userName, string password)
{
bool authenticated = false;
string customer_query = string.Format("SELECT * FROM [Customer] WHERE userName = '{0}' AND password = '{1}'", userName, password);
SqlCommand cmd = new SqlCommand(customer_query, conn);
conn.Open();
SqlDataReader sdr = cmd.ExecuteReader();
authenticated = sdr.HasRows;
conn.Close();
return (authenticated);
}
}
Finally, I want to make custom [Authorize(Roles="admin")]
[Authorize(Roles="admin")]
public class ProductController : Controller
{
public ViewResult Index()
{
var product = db.Product.Include(a => a.Category);
return View(product.ToList());
}
}
These are my source code now. Do I need to make 'AuthorizeAttribute' class?
If I have to do, how can I make it? Could you explain to me? I cannot understand how to set particular role in my case.
Please help me how can I do. Thanks.
I know this question is a bit old but here's how I did something similar. I created a custom authorization attribute that I used to check if a user had the correct security access:
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public sealed class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
// Get the roles from the Controller action decorated with the attribute e.g.
// [AccessDeniedAuthorize(Roles = MyRoleEnum.UserRole + "," + MyRoleEnum.ReadOnlyRole)]
var requiredRoles = Roles.Split(Convert.ToChar(","));
// Get the highest role a user has, from role provider, db lookup, etc.
// (This depends on your requirements - you could also get all roles for a user and check if they have the correct access)
var highestUserRole = GetHighestUserSecurityRole();
// If running locally bypass the check
if (filterContext.HttpContext.Request.IsLocal) return;
if (!requiredRoles.Any(highestUserRole.Contains))
{
// Redirect to access denied view
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
}
}
Now decorate the Controller with the custom attribute (you can also decorate individual Controller actions):
[AccessDeniedAuthorize(Roles="user")]
public class ProductController : Controller
{
[AccessDeniedAuthorize(Roles="admin")]
public ViewResult Index()
{
var product = db.Product.Include(a => a.Category);
return View(product.ToList());
}
}
Your Role.IsInRole usage isn't correct. Thats what the
[Authorize(Roles="Admin")] is used for, no need to call it.
In your code you are not setting the roles anywhere. If you want to do custom role management you can use your own role provider or store them in the auth token as shown here:
http://www.codeproject.com/Articles/36836/Forms-Authentication-and-Role-based-Authorization
note the section:
// Get the stored user-data, in this case, user roles
if (!string.IsNullOrEmpty(ticket.UserData))
{
string userData = ticket.UserData;
string[] roles = userData.Split(',');
//Roles were put in the UserData property in the authentication ticket
//while creating it
HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(id, roles);
}
}
However an easier approach here is to use the built in membership in asp.net.
Create a new mvc project using the 'internet application' template and this will all be setup for you. In visual studio click on the "asp.net configuration" icon above solution explorer. You can manage roles here and assignment to roles.

ASP.NET MVC Authentication Cookie Not Being Retrieved

I am having a hard time implementing "Remember Me" functionality in an MVC application with a custom principal. I have boiled it down to ASP.NET not retrieving the authentication cookie for me. I have included a snapshot below from Google Chrome.
Shows the results of Request.Cookies that is set within the controller action and placed in ViewData for the view to read. Notice that it is missing the .ASPXAUTH cookie
Shows the results from the Chrome developer tools. You can see that .ASPXAUTH is included here.
What may be the issue here? Why does ASP.NET not read this value from the cookie collection?
My application uses a custom IPrincipal. BusinessPrincipalBase is a CSLA object that ust implements IPrincipal. Here is the code for that:
[Serializable()]
public class MoralePrincipal : BusinessPrincipalBase
{
private User _user;
public User User
{
get
{
return _user;
}
}
private MoralePrincipal(IIdentity identity) : base(identity)
{
if (identity is User)
{
_user = (User)identity;
}
}
public override bool Equals(object obj)
{
MoralePrincipal principal = obj as MoralePrincipal;
if (principal != null)
{
if (principal.Identity is User && this.Identity is User)
{
return ((User)principal.Identity).Equals(((User)this.Identity));
}
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool Login(string username, string password)
{
User identity = User.Fetch(username, password);
if (identity == null || !identity.IsAuthenticated)
{
identity = (User)User.UnauthenicatedIdentity;
}
MoralePrincipal principal = new MoralePrincipal(identity);
Csla.ApplicationContext.User = principal;
Context.Current.User = identity;
return identity != null && identity.IsAuthenticated;
}
public static void Logout()
{
IIdentity identity = User.UnauthenicatedIdentity;
MoralePrincipal principal = new MoralePrincipal(identity);
ApplicationContext.User = principal;
Context.Current.User = identity as User;
}
public override bool IsInRole(string role)
{
if (Context.Current.User == null || Context.Current.Project == null)
{
return false;
}
string userRole = Context.Current.User.GetRole(Context.Current.Project.Id);
return string.Compare(role, userRole, true) == 0;
}
The application also uses a custom membership provider. Here is the code for that.
public class MoraleMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
bool result = MoralePrincipal.Login(username, password);
HttpContext.Current.Session["CslaPrincipal"] = ApplicationContext.User;
return result;
}
#region Non-Implemented Properties/Methods
public override string ApplicationName
{
get
{
return "Morale";
}
set
{
throw new NotImplementedException();
}
}
// Everything else just throws a NotImplementedException
#endregion
}
I do not think that any of this is related because the bottom line is that the Request.Cookies does not return the authentication cookie. Is it related to the size of the cookie? I heard there are issues to the size of the cookie.
UPDATE: It seems that the issue revolves around subdomains. This site was being hosted with a subdomain and the cookie domain was left blank. Does anyone have any pointers on how I can get the auth cookie to work with all domains (e.g. http://example.com, http://www.example.com, and http://sub.example.com)?
If you are trying to store the actual User object in the cookie itself, it is probably too big to store as a cookie. I am not too familiar with the MVC authentication stuff, but in web forms I generally do the following:
FormsAuthentication.RedirectFromLoginPage(user_unique_id_here, false);
The second parameter is for the persistency you are looking for.
From there I create a custom context (UserContext) that I populate via HttpModule that gives me access to all the user and role information.
Since I do not develop in MVC (yet) or CSLA, I'm not sure how much more help I can be. If I were you, I would also ditch the custom membership provider. You might as well just call MoralePrincipal.Login directly in your Authentication controller.
The rememberMe stuff should be set by the FormsAuthenticationService (in MVC2) or the FormsAuthentication static class in MVC1, if you're using the 'regular' AccountController's code. If you changed that code, did you remember to add in the (optional) boolean param indicating whether to use a persistent cookie or not?
It sounds to me like you're getting a session cookie, but not a persistent cookie.

Asp.net Security: IIdentity.IsAuthenticated default implementation

I am writing my own custom Identity class which implements IIdentity. I don't need to change the default method IsAuthenticated but so now I was wondering how does the default IIdentity determines if it should return true or false?
I thought to find the answer in the FormsAuthenticationTicket I am using but not sure if that is correct.
Thanks in advance,
Pickels
There is no 'default IIdentity' in the context of an ASP.Net handler.
There is a GenericIdentity that is pass to a GenericPrincipal which is the default User for an ASP.Net handler, and it's behavior is that if it is instantiated with a non-empty username then it is authenticated.
e.g.
public virtual bool IsAuthenticated
{
get
{
return !this.m_name.Equals("");
}
}
That said, the determination of IsAuthenticated is completely arbitrary and the class implementing IIdentity is fully responsible for implementing this logic.
Typically, there is no use case for instantiating an un-authenticated principal/identity as this is done automatically by the asp.net runtime, thus implementing your custom IIdentity with a 'dumb' IsAuthenticated that returns true should be appropriate in most cases.
Also, while fully implementing IPrincipal and IIdentity is trivial, you could also simply derive from GenericPrincipal and GenericIdentity reducing the amount of code you need to maintain.
In the context of FormsAuthentication you will only have a ticket if the user is authenticated and the User will be an instance of RolePrincipal with an identity of type FormsIdentity and it's implementation of IsAuthenticated is super complex ;-) ...
public bool IsAuthenticated
{
get
{
return true;
}
}
Hope that helps clear things up.
I use a custom UserPrinciple to embed more information about the current user into my pages than the standard GenericPrinciple allows. I didn't find a need to implement my own IIdentity as you can easily leverage the built in FormsIdentity similar to my fashion (I'm not sure if this is divergent from standard practices of Auth for .NET it's worked great in practice for myself though). I did create a custom GuestIdentity that returns a hardcoded IsAuthenticated = false perhaps this could be replaced by just GenericPrinciple I'm not sure off hand if it's abstract or not.
public class UserPrincipal : IPrincipal
{
private readonly IIdentity _identity;
public UserPrincipal()
{
_identity = new GuestIdentity();
var guest = //my custom object
User = guest;
}
public UserPrincipal(HttpContext context)
{
var ident = context.User.Identity as FormsIdentity;
string msg1 = "Context.User.Identity is null for authenticated user.";
if (ident == null) throw new ApplicationException(msg1);
_identity = ident;
string msg2 = "Forms Identity Ticket is null";
if (ident.Ticket == null) throw new AccessViolationException(msg2);
var userData = ident.Ticket.UserData;
...
User = jsonSerializer.Deserialize<User>(userJson);
}
#region IPrincipal Members
public bool IsInRole(string role)
{
return User.Roles.FirstOrDefault(x => x.RoleName == role) != null;
}
public IIdentity Identity
{
get { return _identity; }
}
#endregion
}
Random aside, you can cache data in the Forms Authentication ticket like extended UserData, if you follow this type of idea though make sure you have logic in place that can correctly expire stale data since it's stored on the client computer.

Resources