Reputation: 164
I have done a lot of research but am still not sure if I am doing this correctly. The best resource I found was here
http://leastprivilege.com/2015/10/12/the-state-of-security-in-asp-net-5-and-mvc-6-authorization/
Given an ApplicationUser class extended to include a list of Authorized account numbers I want to restrict the user to only view statements (and other actions based) on their authorized accounts). I would think this is a very common design however most of the articles on the net refer to previous versions of identity.
(PS I am injecting UserManager in the Controller constructor)
Here is my action
public IActionResult GetStatement(int accountNo,DateTime startDate,DateTime endDate)
{
var user = userManager.Users
.Include(u => u.AuthorisedAccounts)
.Where(u => u.Id == User.GetUserId())
.FirstOrDefault();
if (user.AuthorisedAccounts != null)
{
foreach (var account in user.AuthorisedAccounts)
{
if (account.AccountNo == accountNo)
return View(statementService.GetStatement(accountNo, startDate, endDate, 0));
}
}
return HttpUnauthorized();
}
I cant help feeling there is a better way? Basically I want to authorize based on the action parameter."accountNo"
Any hints on what approach to take.
Upvotes: 3
Views: 1321
Reputation: 56500
In this case you'd use resource based, with the account being the resource. The documentation for this is at https://docs.asp.net/en/latest/security/authorization/resourcebased.html
To start with you'd define an operation of Read,
public static class Operations
{
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = "Read" };
}
Now you'd have a policy for AccountAccess
public class AccountAuthorizationHandler : AuthorizationHandler<
OperationAuthorizationRequirement, Account>
{
IUserManager _userManager;
public AccountAuthorizationHandler(IUserManager userManager)
{
_userManager = userManager;
}
protected override void Handle(AuthorizationContext context,
OperationAuthorizationRequirement requirement,
Account resource)
{
// Pull the user ID claim out from the context.User
var userId = context.User.....
// Get the current user's account numbers.
var user = userManager.Users
.Include(u => u.AuthorisedAccounts)
.Where(u => u.Id == userId)
.FirstOrDefault();
}
// Now check if the user's account numbers match the resource accountNumber, and
// also check the operation type, in case you want to vary based on create, view etc.
if (user.AuthorisedAccounts.Contains(resource.AccountId &&
requirement.Name == "View")
{
context.Succeed(requirement);
}
}
After that register your policy in the DI container, within configure services;
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization();
services.AddSingleton<IAuthorizationHandler,
AccountAuthorizationHandler>();
}
In your controller you inject the AuthorizationService;
public class AccountController : Controller
{
IAuthorizationService _authorizationService;
public AccountController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
}
Then, within your controller, after you've loaded the account resource you'd do something like
public async Task<IActionResult> View(int accountId)
{
Account account = accountManager.Find(accountId);
if (account == null)
{
return new HttpNotFoundResult();
}
if (await _authorizationService.AuthorizeAsync(User, account, Operations.Read))
{
return View(account);
}
else
{
return new ChallengeResult();
}
}
Upvotes: 3
Reputation: 1887
If you wanted to define your authorization policy in a way that lets you use it for an [Authorize]
attribute, you could alter the approach seen in this article:
services.AddAuthorization(options =>
{
// inline policies
options.AddPolicy("AccessByAccountNumber", policy =>
{
policy.RequireDelegate((context, requirement) =>
{
var httpContext = (context as dynamic).HttpContext;
// Proceed to grab the account number from the request values
// and compare it against the user object stored in 'context.User'
});
});
});
The downside of this is that you would need to ensure that all of the actions where you use the attribute do so in a consistent way, i.e. using the same name for the action parameter or route parameter every time.
@blowdart's example of using the IAuthorizationService
in the controller action demonstrates a way of using and re-using a policy which allows for the parameter names to be decoupled from the policy itself. Though we can see that the IAuthorizationService
does not offer a strongly-typed generic method, it would seem to offer a less brittle approach to implementing resource-based policies.
Upvotes: 0