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.
Related
I have a problem in generating token with Owin and Oauth2 (In Mvc Project).
Some of my application users have subset users and they want to login to their panel without knowing their password.
Problem:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public ApplicationOAuthProvider2(string publicClientId)
{
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//get current user
var user1 = HttpContext.Current.User;//null
var user2 = Thread.CurrentPrincipal;//null
var user3 = context.OwinContext.Authentication.User;//null
}
}
My OauthOptions:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
I can not get the current user in OauthProvider.
Is there any solution?
It's logical that the current user is null in GrantResourceOwnerCredentials. That's the point where you'll need to validate the credentials, username / password and set the user in the context.
It seems that you want to impersonate the (child)user. This is what you can do. Please note that this is pseudo code. Read the comments:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// assume that context contains username, password and childusername
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
// First validate the credentials of the parent:
var appUser = await userManager.FindAsync(context.UserName, context.Password);
// Check if user is valid AND user is parent of childUserName
// now find the user to impersonate
appUser = await userManager.FindByNameAsync(context.ChildUserName);
// If found, appuser is child user:
// you may add information so you know that the user was impersonated by a parent user.
var propertyDictionary = new Dictionary<string, string> { { "userName", appUser.UserName }, { "parent", context.UserName } };
var properties = new AuthenticationProperties(propertyDictionary);
var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
// Token is validated.
context.Validated(ticket);
// parent is now child user.
}
This is just the idea to impersonate the child. You'll need to add the flow for normal login: where child logs in or parent didn't specify a childUserName.
-- update --
Based on your comment I've updated the answer.
The access_token is selfcontained. You cannot change or update it. So you cannot switch the current subset user without having the parent user to login again. Since you cannot get a new or other access_token with the current access_token.
So there are two options: use the flow described above or add claims to the parent user. This will not set the current user, but you can add the current subset user in the url.
You can also add an additional header that contains the subsetUser. In that case you won't need to check the url.
If you want to add claim(s), I suggest you use ApplicationUser like the template:
public class ApplicationUser : IdentityUser
{
public List<string> SubsetUsers { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("subsetUsers", string.Join(",", SubsetUsers)));
return userIdentity;
}
}
Or something like this. I do not know how and where you persist the subset users.
To get the list of available subset users, assuming the subset user is from the url:
user = (System.Security.Claims.ClaimsIdentity)User.Identity;
var subset = user.FindFirstValue("subsetUsers").Split(',');
if(subset.Contains(UserNameFromUrl))
IsValid = true;
You cannot use the default AuthorizeAttribute to validate this, but you can add your own filter.
I have inherited an application with database. The database has following tables related to authentication and authorization.
User Table
UserName
Password
UserTypeId
UserType Table
UserTypeId
UserTypeDesc
The User Type table stores the roles for the user e.g. Admin, Editor, etc.
If I want to implement authorization like below
[Authorize(Roles="Admin, Editor")]
public IHttpActionResult GetOrders()
{
//Code here
}
Where and what should I code so that the roles are available to the authorize attribute ?
Edit
I already have a database. So I cannot use the AspNetUserRoles or AspNetRoles tables. I need to set the roles using my custom tables.
Edit2
As asked by #Nkosi, here is code snippet of how authentication is implemented. The actual implementation calls the business layer service and performs encryption and other stuff but I have simplified the snippet
public HttpResponseMessage Authenticate(User user)
{
var isValid = myRepository.Exists(a => a.UserName == user.UserName && a.Password == user.Password);
if(isValid)
{
FormsAuthentication.SetAuthCookie(user.UserName,false);
}
}
This method is called from the login page where user enters the UserName and Password
Using these Answers for reference
Having Trouble with Forms Authentication Roles
FormsAuthentication Roles without Membership
After having set the auth cookie on login like you did originally,
you do the following in your 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);
//get the user from your custom tables/repository
var user = myUserRepository.GetUserByEmail(ticket.Name);
if(user!=null){
var userTypeId = user.UserTypeId;
var role = myUserTypeRepository.GetUserTypeById(userTypeId);
if(role != null) {
//Assuming the roles for the user e.g. Admin, Editor, etc.
// is in the UserTypeDesc property
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role.UserTypeDesc));
}
}
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
System.Threading.Thread.CurrentPrincipal = claimsPrincipal ;
if (System.Web.HttpContext.Current != null) {
System.Web.HttpContext.Current.User = claimsPrincipal ;
}
}
}
The nice thing about how they implemented it is that it 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.
Your question was very easy. You just need to sync these 2 tables with AspNetUserRoles and AspNetRoles tables respectively. Actually, Authorize attribute by default checks these two tables. So your roles need to reflect in them. These tables are made by default by EF if you select MVC template project.
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.
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.
I have overridden the membership methods to create a custom membership.
In the account model I've overridden the method CreateUser:
public override MembershipUser CreateUser(string username, string password,
string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(
username, password, true);
OnValidatingPassword(args);
if (args.Cancel)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
{
status = MembershipCreateStatus.DuplicateEmail;
return null;
}
MembershipUser u = GetUser(username, false);
if (u == null)
{
UserRepository _user = new UserRepository();
// Here I call my new method which has fields I've created in the
// User table; I'm using entity framework.
_user.CreateUser(username, password, email);
status = MembershipCreateStatus.Success;
return GetUser(username, false);
}
else
{
status = MembershipCreateStatus.DuplicateUserName;
}
return null;
}
public MembershipUser CreateUser(string username, string password,
string email)
{
using (CustomMembershipDB db = new CustomMembershipDB())
{
User user = new User();
user.UserName = username;
user.Email = email;
user.PasswordSalt = CreateSalt();
user.Password = CreatePasswordHash(password, user.PasswordSalt);
user.CreatedDate = DateTime.Now;
user.IsActivated = false;
user.IsLockedOut = false;
user.LastLockedOutDate = DateTime.Now;
user.LastLoginDate = DateTime.Now;
//Generate an email key
// user.NewEmailKey = GenerateKey();
db.AddToUsers(user);
db.SaveChanges();
//send mail
// SendMail(user);
return GetUser(username);
}
}
Now here I need to add more two fields like first name and last name but how can I pass it to the above method?
As the override method CreateUser will give me an error if I add parameters like firstname and last name into it :(
You need to implement Custom Membership User. Here is a sample implementation:
http://msdn.microsoft.com/en-us/library/ms366730.aspx
Also take a look at this thread:
Implement Custom MembershipUser and Custom MembershipProvider
Implementing Custom MembershipUser
You can leave the AspNetUsers table intact, and create a new table to store the extra information (linked to the original one). This way you'll not break any existing code in the membership provider.
The original AspNetUsers table has:
[Id],[Email],[EmailConfirmed],[PasswordHash],[SecurityStamp],[PhoneNumber],[PhoneNumberConfirmed],[TwoFactorEnabled],[LockoutEndDateUtc],[LockoutEnabled],[AccessFailedCount],[UserName]
The new table to store extra data can have for example:
[Id],[UserId][DateOfBirth],[Biography], etc.
Where [UserId] is the foreign key to AspNetUsers table.
One advantage of this approach, is that you can create multiple types of users, each storing its related info in a different table, while common data is still in the original table.
How to:
First update the RegisterViewModel to contain the extra data you want.
Update the Register method in the Account Controller, here's the original method updated with the code to insert new profile data:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Start of new code ----------------------------------------
// Get Id of newly inserted user
int userId = user.Id; // Get Id of newly inserted user
// Create a profile referencing the userId
AddUserProfile(userId, model);
// End of new code ----------------------------------------
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
return View(model);
}
Implement the AddUserProfile(int userId, RegisterViewModel model) method as you wish. You'll collect the extra data from the model object along with the userId and save the new profile object in the DB.
Make a class that inherits from MembershipProvider and implement methods that are identical by just calling the SqlMembershipProvider but change others that you want a different Functionality.
Take a look at this article SQLite 3.0 Membership and Role Provider for ASP.NET 2.0
UPDATE:
The Membership system in ASP.NET was designed to create a standardized
API for working with user accounts, a task faced by many web
applications (refer back to Part 1 of this article series for a more
in-depth look at Membership). While the Membership system encompasses
core user-related properties - username, password, email address, and
so on - oftentimes additional information needs to be captured for
each user. Unfortunately, this additional information can differ
wildly from application to application.
Rather than add additional user attributes to the Membership system,
Microsoft instead created the Profile system to handle additional user
properties. The Profile system allows the additional, user-specific
properties to be defined in the Web.config file and is responsible for
persisting these values to some data store.
Reference: Examining ASP.NET's Membership, Roles, and Profile - Part 6
This is how I have accomplished somthing like this. I added event onCreatedUser to CreateUserWizard and when you press CreateUser button it loads method
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
MembershipUser mu = Membership.GetUser(CreateUserWizard1.UserName);
int idOfInsertedUser = (int)mu.ProviderUserKey;
TextBox tb1 = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("FirstName";
string firstName= tb1.Text;
TextBox tb2 = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("LastName";
string lastName= tb2.Text;
// now you have values of two more fields, and it is time to call your Database methods for inserting them in tables of choice...
}