ASP.NET MVC Authorize attribute with a parameter - c#

I am beginning to decorate my controller classes with
[Authorize(Roles = #"DOMAIN\ADGroup")]
What would be the best method to change that explicit string to a parameter that collects the role assignment from a database, thus allowing flexibility in role assignment that an Admin area can sit on top off.
For example say I have three roles, for arguments sake
ReadOnly
ReadandWrite
Admin
And I want to map those roles to Multiple AD groups
For example
ReadOnly --> DOMAIN\Group1, DOMAIN\Group2, DOMAIN\Group3
ReadandWrite--> DOMAIN\GroupWrite, DOMAIN\GroupManagers
Admin --> DOMAIN\DomainAdmins
This will be editable, I can modify the mapping from role to any AD group I choose in the Admin area of my application.
How can my Authorize attributes take advantage of this?

You can extend the AuthorizeAttribute class. I did it like the following:
public class ExtendedAuthorizeAttribute : AuthorizeAttribute
{
protected string permission;
protected string group;
public ExtendedAuthorizeAttribute(string Permission, string Group)
{
permission = Permission;
group = Group;
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
var can = PermissionManager.Can(httpContext.User, permission, group);
if(can.HasValue)
return can.Value;
return base.AuthorizeCore(httpContext);
}
}

Make your own role provider that returns a list of your ReadOnly etc roles based on the current user's AD roles. Then you can use the Authorize attribute to refer to those roles instead.

Related

How to check if a user is in a role in Asp.Net Core Identity Framework

I've an app that will have multiples level of organization, and for each level, there will be rights(admin-reader-...).
I want to create(and maintain) a list of roles for each user, but it means that a lot of those roles name will be dynamic, like {id-of-the-organization]-admin.
Therefore, I cannot just do the usual Authorize:
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlAllPanelController : Controller
{
public IActionResult SetTime() =>
Content("Administrator || PowerUser");
[Authorize(Roles = "Administrator")]
public IActionResult ShutDown() =>
Content("Administrator only");
}
I would like to have something like
public class ControlAllPanelController : Controller
{
[Authorize]
public IActionResult SetTime(Guid organizationId) {
someService.Authorize(organizationId+"-SetTime");//Throw exception or return boolean
//... rest of my logic
}
}
Not sure how to achieve this? I've seen example of this with the IAuthorize service, but this was requiring to provide policies name, which I don't have for this case(Or maybe there is one by default but I don't know its name. `
I've seen that the ClaimsPrincipal has a IsInRole, but I'm not totally sure it get the latest information from Asp.Net Core Identity Framwork(from the user manager) (only what is stored inside the token?)?
You can use HttpContext to look at the claims in the JWT.
I have recently been working with authorizations in .NET API and this is what I done:
var identity = this.HttpContext.User.Identities.FirstOrDefault();
var role = identity.Claims.FirstOrDefault(x => x.Type == "role").Value;
if (role != "Admin")
{
return Unauthorized("You don't have to correct permissons to do this.");
}
So I'm getting the Identity details, then searching the claims for the role claim.
As a side note, Im using this in a controller inheriting from ControllerBase so I believe HttpContext is a property of this class so no need to inject it if you're using this. Else, you'd probably have to use it via DI, but should all work the same.

Windows Authentication - require additional password for special users

I am developing an intranet asp.net core web api application. The requirements for authentications are:
REQ1 - when user which is trying to access the website is not in Active Directory's special group (let's name it "commonUsers") it is simply not authorized
REQ2 - when user which is trying to access the website is in Active Directory's group "commonUsers" is is authorized and a web resource is returned
REQ3 - when user which is trying to access the website is in Active Directory's group "superUser", it need to be prompted for his domain password once again (because it tries to access some very restricted resources)
Now, what I have so far:
My service is hosted using http.sys server in order to support windows authentication.
I am using claims transformer middlewere in order to check the user's Active Directory group, let's say something like this:
public class ClaimsTransformer : IClaimsTransformation {
private readonly IAuthorizationService _authorizationService;
public ClaimsTransformer(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
_authorizationService.Authorize(principal as IHmiClaimsPrincipal);
return Task.FromResult(principal);
}}
I have specified a special policies also in my service configuration, for instance something like that:
services.AddAuthorization(options =>
{
options.AddPolicy("TestPolicy", policy =>
policy.RequireClaim(ClaimTypes.Role, "TestUser"));
options.AddPolicy("TestPolicy2", policy =>
policy.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
I am using [Authorize] attribute with specific policy in order to restrict access to specific resources based on policies
Now the question is, how should I satisfy REQ3?
I think I would try to use MVC Filters : https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#authorization-filters
Filters run after all Middleware, but before the Action. This will allow you to control the redirect to credentials page just for specific actions or controllers. Whilst normally this is not the recommended method for authorization, I think it fits your requirements for a hybrid secondary authentication.
public class SuperUserFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Cookies.TryGetValue("SuperUserCookie", out string cookieVal))
{
if (!IsValidCookie(cookieVal))
context.Result = LoginPage(context);
}
else
{
context.Result = LoginPage(context);
}
}
private bool IsValidCookie(string cookieVal)
{
//validate cookie value somehow
// crytpographic hash, store value in session, whatever
return true;
}
private ActionResult LoginPage(AuthorizationFilterContext context)
{
return new RedirectToActionResult("SuperUser", "Login",
new {redirectUrl = context.HttpContext.Request.GetEncodedUrl()});
}
}
Then you create a Login Controller
public class LoginController : Controller
{
[HttpGet]
public IActionResult SuperUser(string redirectUrl)
{
// return a page to enter credentials
// Include redirectUrl as field
}
[HttpPost]
public IActionResult SuperUser(LoginData loginData)
{
// Validate User & Password
Response.Cookies.Append("SuperUserCookie", "SomeValue");
return Redirect(loginData.RedirectUrl);
}
}
Then you can decorate specific actions (or controllers) as required:
public class MyController : Controller
{
[HttpGet]
[SuperUserFilter]
public IActionResult MySensitiveAction()
{
// Do something sensitive
}
}
I'm guessing you are try to implement two step authentication for some of your resource.
To do that you must use multiple authentication scheme and Authorize policies,
but it's difficult because windows authentication is not controllable. we need to use some trick to know this is your second login.
authentication
The Default Authenticaiton Scheme : Windows, it's the basic scheme for authenticate a windows user.
Second Cookies base Authentication scheme : SuperUserTwoStep. we need this to goto our custom login logic.
Authorize
the Authorize policies for specified scheme.
a login page for login to SuperUserTwoStep scheme.
//startup
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme)
.AddCookie("SuperUserTwoStep",op=>op.LoginPath = "/account/superuser2steplogin");
services.AddAuthorization(op =>
{
op.AddPolicy("SuperUser", b => b.AddAuthenticationSchemes("SuperUserTwoStep")
.RequireAuthenticatedUser()
.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
// login
public static IDictionary<string, string> States { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
[Route("/account/superuser2steplogin")]
public async Task<IActionResult> LoginTwoStepConfirm(string returnUrl, [FromServices]IAuthorizationService authorizationService,
[FromServices]IAuthorizationPolicyProvider policyProvider)
{
var winresult = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (winresult.Succeeded)
{
if (States.TryGetValue(winresult.Principal.Identity.Name, out _))
{
States.Remove(winresult.Principal.Identity.Name);
var principal = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity(winresult.Principal.Claims,"twostepcookie"));
await HttpContext.SignInAsync("SuperUserTwoStep", principal);
return Redirect(returnUrl);
}
else
{
States[winresult.Principal.Identity.Name] = "1";
return Challenge(IISDefaults.AuthenticationScheme);
}
}
else
{
return Challenge(IISDefaults.AuthenticationScheme);
}
}
[Authorize("SuperUser")]
public IActionResult YourSecurePage()
{
return Content("hello world");
}
the most difficult thing is to track that this is the second time to login, I try to use cookie , but it doen't work, so I crate a static IDitionary<string,string> to track ,maybe use distributed cache is better
I think in my opinion you should consider using: Policy-based authorization with Requirements, basically you have different authorization requirements that you want to treat them on and AND basis
REQ1 and REQ2 and REQ3
Here you have the link to the documentation: Requirements
But you need to understand that identity != permissions, the guys that introduce this concept of policies to Microsoft created a project named: PolicyServer and it is opensource: PolicyServer Git and they created a pattern there of how you should use your policies. Basically, you have external and internal users that are authenticated against your AD, all internal users should have permissions assigned to a role. And you only decorate your controller action with the permission rule you created for that policy
[Authorize("PerformSurgery")]
public async Task<IActionResult> PerformSurgery()
{
// omitted
}
To understand the code and how they evaluate a policy, I think you should see the video they have online on the website: Policy Server
Hope this helps

MVC Custom Authorize Attribute From Database

I made my own custom Role table in my database and I wanted to also create a custom authorize attribute along with it.
Here is what I have so far, but I'm not really sure how to proceed:
private List<RoleModel> Roles;
private IRoleRepository repo;
private ICustomerRepository cust;
public bool CheckRoles(string UserId)
{
try
{
Roles = repo.GetAll().ToList();
CustomerModel Customer = cust.Get(UserId);
int CustomerRole = Customer.RoleId;
RoleModel role = Roles.First(x => x.id == CustomerRole);
}
catch(Exception e)
{
return false;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
string UserId = filterContext.HttpContext.User.Identity.GetUserId();
}
If anyone can help me finish this I would greatly appreciate it.
Thanks!
I think that create a custom AuthorizeAttribute is not a good idea. It is a good practice to use the standard AuthorizeAttribute.
It is a common case to have its own Role table. It is better to override how to set the roles of the identity of your principal and to use the Roles property of AuthorizeAttribute. Set the role as a claim once when the user is logging; it will be better than retrieve the role from database in the custom Authorize attribute at each request.
Set your claim CalimTypes.Role, and then protect your controllers/actions with :
[Authorize(Roles = "admin")]

How to handle Microsoft Authentication in ASP.NET Entity Framework

I've currently got a very simple setup running. This setup consists of an entity-framework project, and an IIS server. The IIS is configured to use windows authentication.
Now in my project, I want to allow only certain users to gain access to certain controllers. Within the organisation I'm working for there's a "permissions" system, a table that contains what users are allowed to access what data. So, I want to get the e-mail with which the user logged in, and check that against the database to see if he has permission.
My plan for doing this was to make a seperate piece of code, that's not accessable from the web, that contains the function "boolean hasPermissions(String email, byte permissions)". But I've got no idea where to place this, nor can I find any information on this. Is what I have in mind the right approach? And if, then how to execute this approach correctly?
You should use windows authentication, using IPrincipal , you will have a user object that you could ask IsInRole for specific role based security instead of bits / booleans
read all about it at Asp.net windows authentication
and how to implement IPrincipal Implement custom security
Code sample:
User object:
public class User : IPrincipal
{
private readonly IPrincipal _user;
public IIdentity Identity { get; private set; }
public User (IPrincipal user)
{
Identity = user.Identity;
_user = user;
}
public bool IsInRole(string role)
{
return _user.IsInRole(role);
}
}
In MVC add a filter
public class CustomAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
var user= new User (HttpContext.Current.User);
Thread.CurrentPrincipal = user;
}
}
And add that filter to your FilterConfig using
filters.Add(new CustomAuthenticationAttribute());
Then, when using the user object within your application
var user = new User(Thread.CurrentPrincipal);
if(user.IsInRole("admin")) /* do the choka choka */;

Implicit role assignment using MVC authorization

I am using my own custom Authorize attribute MyAuthorizeAttribute : AuthorizeAttribute to do authorization which is working great. The problem is with role assignment. In the security model i am working with if a user has Admin role User and Manager roles are contained in admin. So
[MyAuthorize(Roles = "Admin")]
is allowed to access all methods. So the intuitive solution is to assign "Admin" on every controller. But is it the cleanest solution?
Also what if i have tens of roles to work with. Code line will look like below:
[MyAuthorize(Roles = "Admin, Role A, Role B... Role Z")]
And what if down the road I decide to rename one of the role name should I use role id's instead?.
If i screen for roles in my custom authorize methods then i run the risk of creating a huge switch statement in that function, like below:
switch(Controller Accessed)
{
case Index: //if user, admin, or any other role exist
case Manage: //if admin role exists
}
Making it easier to rename in the future
If you expect the role names to change, you will get better tool support when you refactor if you use constants or enums.
public static class Roles
{
public const string UserAdmin = "User Administrator";
public const string SuperAdmin = "Super Administrator";
public const string RegularUser = "Regular User";
}
or
public enum Roles
{
UserAdmin,
SuperAdmin,
RegularUser
}
Re-using common groups of permissions
If you find yourself using the same roles in permissions, you can simplify it either by subclassing your MyAuthorize filter or the MVC base controller. Any class that inherits BaseAdminController below would also have the MyAuthorize filter applied.
[MyAuthorize(Roles = Roles.UserAdmin, Roles.SuperAdmin)]
public abstract class BaseAdminController : Controller {
}
or
public AdminAuthorize : MyAuthorize
{
public AdminAuthorize()
{
base.Roles = new[] { Roles.UserAdmin, Roles.SuperAdmin };
}
}

Categories

Resources