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 */;
Related
How to specify restrictions to user in domain driven.
I am using asp.net web api application and that application service uses from web api.
[ApiController]
public class TicketController : ControllerBase
{
....
....
[HttpPost]
[Route("change-date")]
public async Task<IActionResult> ChangeTicketDate(TicketChangeCommand command)
{
var response = await _ticketService.ChangeTicketDate(command, User.Identity.Name);
return Ok(response);
}
....
....
}
To prevent the tickets, I am sending the authenticated username to _ticketService. Because a user may change another user ticket. So I have two rules for ticket date change logic.
1- User should be owner of ticket.
2- Or User sholud be in Administrator role.
(These rules also will be using other services. For example User can only change his own password.)
public class TicketService : ITicketService
{
....
public TicketChange ChangeTicketDate(TicketChangeCommand command, string requestedUsername){
// 1. Check requested user is ticket creator or Administrator
}
....
}
To check the user role and owner, should I create a domain service? Should I create a domain authorization service to check roles?
As I see this in DDD you should have some method in your DomainModel class to do this operation e.g. ChangeTicketDate
Now if you store the information of the ticket creator when you create the ticket "which I believe you do" the validation should be part of DomainModel itself e.g.
ChangeTicketDate(string userName)
then the DomainService "Or ApplicationService" should just pass this value to the Domain Model class and the validation should be in the Domain Class
However, in some cases you may need to implement some validations before the domain model or let's say it's not related to the Entity itself, those validations should be in your Application Service
I guess the following piece of code will explain my idea better
public class TicketService : ITicketService
{
// ....
// I expect that you'll be able to get some information about the current user
public CurrentUserData CurrentUser {get; private set;}
public TicketChange ChangeTicketDate(TicketChangeCommand command){
// 1. Load the Aggregate Root from the Data StoreCheck
var ticket = db.GetById(ticketId);
// 2. Do some Application level checks
// 3. Check if the current user is Admin
if(CurrentUser.IsAdmin)
ticket.ChangeTicketDate();
else
ticket.ChangeTicketDate(CurrentUser.Username);
}
....
}
//Example of Domain Model class
public class Ticket : Entity<Guid>
{
public UserInfo Creator {get; private set;}
public void ChangeTicketDate(string requestedUsername){
if(!string.IsNullOrEmpty(requestedUsername) && requestedUsername != Creator.UserName)
threw new ValidationException("You're not allowed to do ChangeTicketDate");
}
....
}
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
I believe I understand the basics of sessionless/stateless REST but I am having problems with implementation in Asp.Net Web Api 2 because I haven't used it before. I have set up ApiControllers that use a custom System.Web.Http.AuthorizeAttribute like this.
public class ApiAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization != null)
{
//Set identity??
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
I have a database that contains users and I need to use them for getting privileges to get/post/put/delete things but dont want to use a session. I have never worked with the asp.net Identity so I am not familiar with its features and capabilities.
My idea for implementation is to use user credentials or api secret signing to authenticate and get privileges for a user for every request. The question is, by using a AuthorizeAttribute or something similar, how do i give the controller(s) during that one request the user information if their credentials were correct?
UPDATE:
Is using this.User (ApiController.User) session based or can it be used for that single request. If so, how does one set it
You can use HttpContext.Items to hold user data for current requests.
After setting identity based on Auth header , you can have
System.Web.HttpContext.Current.Items["userdata"]=userDataObject,
Another approach would be write your own action filter for authentication (but take utmost care)and pass data to controller.
public class MyAuthAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//do authorization here
base.OnActionExecuting(filterContext);
// Create object parameter.
filterContext.ActionParameters["userdata"] = new User("John", "Smith");
}
}
then in controller
[MyAuthAttribute]
ActionResult SomeAction(User userdata) //this will have user data
{
}
It looks like that using IPrincipal and setting HttpContext.Current.User will allow the ApiControllers to access that user through using
this.User
with web api not having access to the session
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization != null)
{
//Check user credentials/token here
//Get internal user
IPrincipal principal = new MyOwnCustomPrincipal(internalUser);
HttpContext.Current.User = principal;
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
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.
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 };
}
}