How to pass Enum as string parameter to Authorize attribute? - c#

It's my first project to ASP.NET Core Authentication and Authorization and I get this error when I'm trying to pass Enum to [Authorize] attribute :
Error CS1503 Argument 1: cannot convert from
'BasicAuthAPI.Entities.Role' to
'string'
Here is my controller method which gives this error:
[Authorize(Role.Admin)]
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
Role enum:
public enum Role
{
Admin,
User
}
User Entity:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public Role Role { get; set; }
[JsonIgnore]
public string PasswordHash { get; set; }
}
And the _userService which I have mentioned in controller:
public class UserService : IUserService
{
private DataContext _context;
private IJwtUtils _jwtUtils;
private readonly AppSettings _appSettings;
public UserService(
DataContext context,
IJwtUtils jwtUtils,
IOptions<AppSettings> appSettings)
{
_context = context;
_jwtUtils = jwtUtils;
_appSettings = appSettings.Value;
}
public AuthenticateResponse Authenticate(AuthenticateRequest model)
{
var user = _context.Users.SingleOrDefault(x => x.Username == model.Username);
// validate
if (user == null || !BCryptNet.Verify(model.Password, user.PasswordHash))
throw new AppException("Username or password is incorrect");
// authentication successful so generate jwt token
var jwtToken = _jwtUtils.GenerateJwtToken(user);
return new AuthenticateResponse(user, jwtToken);
}
public IEnumerable<User> GetAll()
{
return _context.Users;
}
public User GetById(int id)
{
var user = _context.Users.Find(id);
if (user == null) throw new KeyNotFoundException("User not found");
return user;
}
}
How can I pass the Admin Role to [Authorize] attribute?

Either use string constants
public static class Role
{
public static string Admin = "Admin";
public static string User = "User";
}
or you can use nameof
[Authorize(nameof(Role.Admin))]

You can just call .ToString()
[Authorize(Role.Admin.ToString())]
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
Looking at the answer from Alexander I have found the following SO post which highlights the difference between nameof and ToString: What is the difference between MyEnum.Item.ToString() and nameof(MyEnum.Item)?

Related

C# Identity modify custom properties

Hello I Have created custom ApiUser class which inherits from IdentityUser.
public class ApiUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
The question it is set on Creating new user, but how do I change it/update it later?
UserManager<ApiUser> userManager
_userManager does not seem to have any methotds to do so.
[HttpPost]
[Route("UpdateUserDetails")]
public async Task<IActionResult> UpdateUser(ApiUserUpdate apiUserUpdate)
{
var user = await _userManager.FindByEmailAsync(apiUserUpdate.Email);
if(user == null)
{
return BadRequest("No user found with given email");
}
user.FirstName = apiUserUpdate.FirstName;
user.LastName = apiUserUpdate.LastName;
await _userManager.UpdateAsync(user);
return Ok("user updated");
}

OverrideAuthorization attribute in .NETCore

In the controller code below, only users who are in the "Administrator" role can access the GetData() action method, because of the controller-level AuthorizeAttribute. But I also want users who only are in "Manager" role to have access to the GetData() action method.
[Authorize(Roles = "Administrator")]
Public class AdminController : Controller
{
[Authorize(Roles = "Administrator, Manager")]
public IActionResult GetData()
{
}
}
Is there an option like OverrideAuthorization attribute available in .NET Core framework to achieve this requirement?
Was able to find a solution after long time of analysis on the Authorization assemblies.
In the startup.cs file, add the Authorization as follows:
services.AddAuthorization(options =>
{
var roles = new List<string>{ Role.Administrator, Role.Manager};
var requirement =
new List<IAuthorizationRequirement> {new AdminManagerAuthorizationOverrideOthers(roles) };
var sharedAuthentication =
new AuthorizationPolicy(requirement,
new List<string>());
options.AddPolicy(name: "AdminManager", policy: sharedAuthentication);
options.AddPolicy(name: "Administrator", configurePolicy: policy => policy.RequireAssertion(e =>
{
if (e.Resource is AuthorizationFilterContext afc)
{
var noPolicy = afc.Filters.OfType<AuthorizeFilter>().Any(p =>
p.Policy.Requirements.Count == 1 &&
p.Policy.Requirements.Single() is AdminManagerAuthorizationOverrideOthers);
if (noPolicy)
return true;
}
return e.User.IsInRole(Role.Administrator);
}));
});
Create a class in any namespace that Inherits "RolesAuthorizationRequirement" from "Microsoft.AspNetCore.Authorization.Infrastructure" namespace as follows:
public class AdminManagerAuthorizationOverrideOthers : RolesAuthorizationRequirement
{
public AdminManagerAuthorizationOverrideOthers(IEnumerable<string> allowedRoles) : base(allowedRoles)
{
}
}
Then, decorate the controller and action method as follows:
[Authorize(Policy = "Administrator")]
Public class AdminController : Controller
{
public IActionResult GetData()
{
}
[Authorize(Policy = "AdminManager")]
public IActionResult AdministratorOnly()
{
}
}
Ideally, you want to narrow down the restriction to Action Method, because in Controller Initialization step, it checks Controller's Authorize filter first before Action filters.
[Authorize(Roles = "Administrator, Manager")]
Public class AdminController : Controller
{
public IActionResult GetData()
{
}
[Authorize(Roles = "Administrator")]
public IActionResult AdministratorOnly()
{
}
}
In ASP.NET Core 2.1 you can do it. Check this: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
You can also lock down a controller but allow anonymous,
unauthenticated access to individual actions.
[Authorize(Roles = "Admin,Employee")] // admin or employee
public class XController : Controller
{
[Authorize(Roles = "Admin")] // only admin
public ActionResult ActionX() { ... }
[AllowAnonymous] // anyone
public ActionResult ActionX() { ... }
}
All above is right, i just want to give a full example easy for all
My case is Asp.Net Core 3.1
Startup.js (ConfigureServices):
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.User.RequireUniqueEmail = false; // óíèêàëüíûé email
config.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 -._#+";
config.SignIn.RequireConfirmedEmail = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserManager<UserManager<ApplicationUser>>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddDefaultTokenProviders();
services.AddAuthorization(options =>
{
options.AddPolicy("User", policy => {
policy.RequireClaim("User");
});
options.AddPolicy("Admin", policy => {
policy.RequireRole("Admin");
});
});
services.AddScoped<IAuthorizationHandler, RolesAuthorizationHandler>();
Startup.js (Configure):
app.UseAuthentication();
app.UseAuthorization();
Controller:
[Authorize(Policy = "Admin")]
public class RoleController : Controller
Handler-Example:
public class RolesAuthorizationHandler : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationHandler
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<ApplicationUser> _userManager;
public RolesAuthorizationHandler(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
RolesAuthorizationRequirement requirement)
{
if (context.User == null || !context.User.Identity.IsAuthenticated)
{
context.Fail();
return Task.CompletedTask;
}
var validRole = false;
if (requirement.AllowedRoles == null ||
requirement.AllowedRoles.Any() == false)
{
validRole = true;
}
else
{
var claims = context.User.Claims;
//var userName = claims.FirstOrDefault(c => c.Type == "UserName").Value;
var allowedRoles = requirement.AllowedRoles;
var loggedInUserTask = _userManager.GetUserAsync(context.User);
loggedInUserTask.Wait();
var user = loggedInUserTask.Result;
var roles = _userManager.GetRolesAsync(user);
roles.Wait();
var roleList = roles.Result;
validRole = roleList.Where(p => allowedRoles.Contains(p.ToString())).Any();
}
if (validRole)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
I While updating a project that used to exist, I moved the old user table to the user table in the new identity database. Later, I defined roles at the table level for them, and with the RoleManager I wrote in this way, I left his next administration to the step. Quite successful. In my case, many people probably updated their old projects. However, I did not have such a post and wanted to share it.
The following section is for them:
public class RoleAssignViewModel
{
public string RoleId { get; set; }
public string RoleName { get; set; }
public bool HasAssign { get; set; }
}
public class RoleViewModel
{
[Required(ErrorMessage = "Fill the role.")]
[Display(Name = "Role Name")]
public string Name { get; set; }
}
[Authorize(Policy = "Admin")]
public class RoleController : Controller
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<ApplicationUser> _userManager;
public RoleController(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
public async Task<IActionResult> RoleAssign(string id)
{
ApplicationUser user = await _userManager.FindByIdAsync(id);
List<IdentityRole> allRoles = _roleManager.Roles.ToList();
List<string> userRoles = await _userManager.GetRolesAsync(user) as List<string>;
List<RoleAssignViewModel> assignRoles = new List<RoleAssignViewModel>();
allRoles.ForEach(role => assignRoles.Add(new RoleAssignViewModel
{
HasAssign = userRoles.Contains(role.Name),
RoleId = role.Id,
RoleName = role.Name
}));
return View(assignRoles);
}
[HttpPost]
public async Task<ActionResult> RoleAssign(List<RoleAssignViewModel> modelList, string id)
{
ApplicationUser user = await _userManager.FindByIdAsync(id);
foreach (RoleAssignViewModel role in modelList)
{
if (role.HasAssign)
await _userManager.AddToRoleAsync(user, role.RoleName);
else
await _userManager.RemoveFromRoleAsync(user, role.RoleName);
}
return RedirectToAction("Index", "User");
}
public IActionResult RoleList()
{
return View(_roleManager.Roles.ToList());
}
public async Task<IActionResult> DeleteRole(string id)
{
IdentityRole role = await _roleManager.FindByIdAsync(id);
IdentityResult result = await _roleManager.DeleteAsync(role);
if (result.Succeeded)
{
//Başarılı...
}
return RedirectToAction("Index");
}
public async Task<IActionResult> CreateRole(string id)
{
if (id != null)
{
IdentityRole role = await _roleManager.FindByIdAsync(id);
return View(new RoleViewModel
{
Name = role.Name
});
}
return View();
}
[HttpPost]
public async Task<IActionResult> CreateRole(RoleViewModel model, string id)
{
IdentityResult result = null;
if (id != null)
{
IdentityRole role = await _roleManager.FindByIdAsync(id);
role.Name = model.Name;
result = await _roleManager.UpdateAsync(role);
}
else
result = await _roleManager.CreateAsync(new IdentityRole { Name = model.Name });
if (result.Succeeded)
{
//Başarılı...
}
return View();
}
//[Authorize]
public IActionResult UserRoleList()
{
return View(_userManager.Users);
}
}
Found something here I am using: https://github.com/dotnet/aspnetcore/issues/8149#issuecomment-471927034
/// <summary>
/// https://github.com/dotnet/aspnetcore/issues/8149#issuecomment-471927034
/// </summary>
public class OverrideFilter : ActionFilterAttribute
{
public Type Type { get; set; }
}
public class OverrideFilterProvider : IFilterProvider
{
public int Order => 1;
public void OnProvidersExecuted(FilterProviderContext context) { }
public void OnProvidersExecuting(FilterProviderContext context)
{
if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
{
//Check whether the method has any OverrideFilter
var overrideFilters = context.Results.Where(filterItem => filterItem.Filter is OverrideFilter).ToList();
foreach (var overrideFilter in overrideFilters)
{
//Remove the filters of the corresponding type, but with smaller scope
context.Results.RemoveAll(filterItem =>
filterItem.Descriptor.Filter.GetType() == ((OverrideFilter)overrideFilter.Filter).Type &&
filterItem.Descriptor.Scope < overrideFilter.Descriptor.Scope);
}
}
}
}
public class OverrideAuthorization : OverrideFilter
{
public OverrideAuthorization()
{
Type = typeof(AuthorizeFilter);
}
}
/// <summary>
/// https://stackoverflow.com/questions/16606281/linq-to-remove-certain-elements-from-a-ilistt-based-on-a-ilistint
/// </summary>
public static class IListExt
{
public static int RemoveAll<T>(this IList<T> list, Predicate<T> match)
{
int count = 0;
for (int i = list.Count - 1; i >= 0; i--)
{
if (match(list[i]))
{
++count;
list.RemoveAt(i);
}
}
return count;
}
}
Finally we inject it as follows (I am not sure this is the right wat to inject it, but it works);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFilterProvider, OverrideFilterProvider>());
Use like
[Authorize(Policy = "ControllerPolicy")
public class MyController : Controller
{
[OverrideAuthorization]
[Authorize(Policy = "ActionPolicy")]
public IActionResult MyAction()
{
//Only ActionPolicy will be applied, while ControllerPolicy will be ignored
}
}

.Net Core get Objects of ApplicationUser

I've added a few specific Properties to the ApplicationUser of the standart project.
some of the added Properties are of custom classes.
Since i use EntityFramework, it creates a dbtable for users and one for each custom class.
i added the Properties to my ManageController and Views and adding these Properties to the specific dbtable works, but i cant access them. in the dbo.AspNetUsers there is a column added, that is called after the attribute + ID (In my example "NameID").
Now if i am loading the user in my ManageController, every normal Attribute is loaded, but the custom ones are null.
My Question is, how can i load the custom objects (that are really stored in the other table).
ApplicationUser.cs:
namespace refProject.Models
{
public class ApplicationUser : IdentityUser
{
public Name Name { get; set; }
}
}
ManageController.cs
//other usings
using refProject.Models;
using refProject.Models.ManageViewModels;
namespace refProject.Controllers
{
[Authorize]
public class ManageController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
//other managers
public ManageController(
UserManager<ApplicationUser> userManager,
//other managers
)
{
_userManager = userManager;
//other managers
}
//
// GET: /Manage/Index
[HttpGet]
public async Task<IActionResult> Index(ManageMessageId? message = null)
{
ViewData["StatusMessage"] =
message == ManageMessageId.ChangeNameSuccess ? "Your name has been changed."
: message == ManageMessageId.SetNameSuccess ? "Your name has been set."
: "";
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var model = new IndexViewModel
{
//other Properties
//
//
// THIS ONE IS NULL
//
//
Name = user.Name
//other Properties
};
return View(model);
}
// GET: /Manage/ChangeName
[HttpGet]
public IActionResult ChangeName()
{
return View();
}
//
// POST: /Manage/ChangeName
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeName(ChangeNameViewModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if(user != null)
{
Name NewName = new Name();
NewName.FirstName = model.NewFirstName;
NewName.LastName = model.NewLastName;
user.Name = NewName;
IdentityResult result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangeNameSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/SetName
[HttpGet]
public IActionResult SetName()
{
return View();
}
//
// POST: /Manage/SetName
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SetName(SetNameViewModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if(user != null)
{
Name NewName = new Name();
NewName.FirstName = model.NewFirstName;
NewName.LastName = model.NewLastName;
user.Name = NewName;
IdentityResult result = await _userManager.UpdateAsync(user);
if(result.Succeeded)
{
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetNameSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
#region Helpers
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
public enum ManageMessageId
{
Error,
ChangeNameSuccess,
SetNameSuccess,
}
private Task<ApplicationUser> GetCurrentUserAsync()
{
return _userManager.GetUserAsync(HttpContext.User);
}
#endregion
}
}
Name.cs
namespace refProject.Models
{
public class Name
{
public int ID { get; set; }
public string fTitle { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string lTitle { get; set; }
public override string ToString()
{
return fTitle + " " + FirstName + " " + LastName + " " + lTitle;
}
}
}
That is a known issue. It is not considered a bug, but rather a design decision.
The recommended way is to access the user through DbContext rather than from the UserManager implementation.
"Just to add a bit more detail: as a performance optimization ASP.NET Core Identity currently only loads the entities related to a user or a role as needed to satisfy API calls. I.e. it won't load related entities (not even the built-in ones) eagerly on a method call like such as FindByName() because the find methods are only required to return the root.
At this point issuing queries against the DbContext is the recommended
way to load related data. If you want to abstract this from the
application code you can extend both the Identity store and manager
classes to add methods to retrieve and return your custom related
data."
Comment link
You could change your GetCurrentUserAsync method as follows:
private ApplicationUser GetCurrentUserAsync()
{
return _userManager.Users.Include(x => x.Name).FirstOrDefault(x => x.Id == _userManager.GetUserId(User));
}

AutoFac with UserPrincipal and Roles

I have a Custom UserPrincipal and I have an User Entity.
The User Entity holds a collection of Permissions.
I want to make my UserPrincipal aware of those Permissions and all I want to do is to inject the IPrincipal in the construtors I need and then call the IsInRole() method.
How do I achieve that with AutoFac? I'm not finding any reference of how to make the UserPrincipal aware of the User Entity. I could solve that by addidng the permissions to Claims and then get those claims in the IsInRole, but I don't know if that is a good idea?
EDIT:
After some tests I got to this solution:
public interface IUserPrincipal : IPrincipal
{
Guid Id { get; }
Guid? BossId { get; }
string DisplayName { get; }
string Email { get; }
List<string> Permissions { get; }
}
public class UserPrincipal : IUserPrincipal
{
private readonly User _user;
public UserPrincipal(User user, IIdentity identity)
{
_user = user;
Identity = identity;
}
public bool IsInRole(string role)
{
return Permissions.Contains(role);
}
public IIdentity Identity { get; }
public Guid Id => _user.Id;
public Guid? BossId => _user.BossId;
public string DisplayName => _user.Name;
public string Email => _user.Name;
public List<string> Permissions
{
get
{
return _user.Permissions.Select(p => p.Name).ToList();
}
}
}
public User GetCurrentUser()
{
var user = GetUserByEmail(emailAddress);
if (user == null)
{
using (var unitOfWork = _unitOfWorkFactory.CreateUnitOfWork())
{
user = CreateNewUser(unitOfWork, emailAddress, displayName);
}
}
Thread.CurrentPrincipal = new UserPrincipal(user, Thread.CurrentPrincipal.Identity);
return user;
}
And then with AutoFac:
builder.Register(c => new UserPrincipal(c.Resolve<IUserService>().GetCurrentUser(), Thread.CurrentPrincipal.Identity)).As<IPrincipal>().InstancePerRequest();

UserManager's async methods giving "Incorrect number of arguments for call to method Boolean Equals"

I've made custom User and UserStore classes.
Now I'm trying to register or login with a user, but I get the error
'Incorrect number of arguments supplied for call to method Boolean
Equals(System.String, System.String, System.StringComparison)'
The error is on line 411:
Line 409: }
Line 410: var user = new User() { UserName = model.Email, Email = model.Email };
--> Line 411: IdentityResult result = await UserManager.CreateAsync(user);
Line 412: if (result.Succeeded)
Line 413: {
I get this same error on
UserManager.FindAsync(user), UserManager.CreateAsync(user,password)
Now the error doesn't occur when I log in with an External Login, like Google, which also uses methods from UserManager. Entering the email works as well, but when the user has to be created from an External Login with the inserted email, it gives the CreateAsync error too.
EDIT The error also occurs on UserManager.Create(User user)
This is probably because the method in UserManager does an Equal on the user object's id, while it is expecting a string and mine is an int. But because I can't get a stacktrace inside the UserManager, and I have no idea how to override this method in the UserManager, I do not know how to solve this?
How can I fix this? Do I need to create my own UserManager? Or do I need another solution entirely?
My userstore code:
public class UserStore :
IUserStore<User>,
IUserPasswordStore<User>,
IUserSecurityStampStore<User>,
IUserEmailStore<User>,
IUserLoginStore<User>
{
private readonly NFCMSDbContext _db;
public UserStore(NFCMSDbContext db)
{
_db = db;
}
public UserStore()
{
_db = new NFCMSDbContext();
}
#region IUserStore
public Task CreateAsync(User user)
{
if (user == null)
throw new ArgumentNullException("user");
_db.Users.Add(user);
_db.Configuration.ValidateOnSaveEnabled = false;
return _db.SaveChangesAsync();
}
public Task DeleteAsync(User user)
{
if (user == null)
throw new ArgumentNullException("user");
_db.Users.Remove(user);
_db.Configuration.ValidateOnSaveEnabled = false;
return _db.SaveChangesAsync();
}
public Task<User> FindByIdAsync(string userId)
{
int userid;
if(int.TryParse(userId,out userid))
throw new ArgumentNullException("userId");
return _db.Users.Where(u => u.UserId == userid).FirstOrDefaultAsync();
}
(...)
User.cs:
public sealed class User : IUser<int>
{
public User()
{
UserLogins = new List<UserLogin>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
public string Email {get; set; }
public bool IsEmailConfirmed { get; set; }
int IUser<int>.Id
{
get { return UserId; }
}
public ICollection<UserLogin> UserLogins { get; private set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
Did you change your UserManager type to UserManager to tell it that your user key is now int instead of string?

Categories