Check if a user has permission to access a controller action (before accessing it) - asp.net-core

I have an action, TopSecret(), which has a security policy applied to it:
[Authorize(Policy = "Level2SecurityClearance")]
public IActionResult TopSecret()
I could check the user meets the requirements of the policy by doing this (authorizationService is of type IAuthorizationService)
bool isAuthorised = await authorizationService.AuthorizeAsync(User, "Level2SecurityClearance");
This action may have a different policy applied at some point in the future and I don't want to have to find all the places I generate links to it and update the code. Is it possible to test if a user can access a specific action?
Maybe something like this:
// Not a real method!!!
bool isAuthorised = authorizationService.IsAuthorisedForAction(User, "TopSecret", "SecretController");

You should look into developing Requirements
Here's an example for you using your criteria:
note: I'm assuming you're using Identity3 and your User has claims with the access
In a new class called Level2SecurityClearanceRequirement
public class Level2SecurityClearanceRequirement : AuthorizationHandler<Level2SecurityClearanceRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, Level2SecurityClearanceRequirement requirement)
{
if (context.User.HasClaim("TopSecret","yes")
context.Succeed(requirement);
return Task.FromResult(0);
}
}
In your controller method:
public async Task<IActionResult> BlahBlah() {
if (!await _authorizationService.AuthorizeAsync(User, nameof(PolicyName.Level2SecurityClearance), new Level2SecurityClearanceRequirement()))
return new ChallengeResult();
}
note that I'm using nameof() here so that you don't have any magic strings and all your resources are centralized.
In this case I have an enum:
public enum PolicyName {
Level2SecurityClearance
}
in your startup.cs:
in the ConfigureServices method
add the following:
services.AddAuthorization(options =>{
options.AddPolicy(nameof(PolicyName.Level2SecurityClearance), policy => { policy.AddRequirements(new Level2SecurityClearanceRequirement()); });
});
you can then use this requirement whereever you please and the checks are done in the requirement itself

Try this. Tested in ASP.NET Core 1.1
//somewhere in view
#if (await Url.HasAccess(urlActionContext))
{
<p>You have access</p>
}
Extension method
public static async Task<bool> HasAccess(this IUrlHelper urlHelper, UrlActionContext urlActionContext, string httpMethod = "GET" )
{
var httpContext = urlHelper.ActionContext.HttpContext;
var routeValues = new RouteValueDictionary(urlActionContext.Values);
routeValues["action"] = urlActionContext.Action;
routeValues["controller"] = urlActionContext.Controller;
var path = urlHelper.Action(urlActionContext);
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(new HttpRequestFeature()
{
Method = httpMethod,
Path = path,
});
var ctx = new DefaultHttpContext(features);
var routeContext = new RouteContext(ctx);
foreach (var entry in routeValues)
{
routeContext.RouteData.Values.Add(entry.Key, entry.Value);
}
var actionSelector = httpContext.RequestServices.GetRequiredService<IActionSelector>();
var provider = httpContext.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
var actionDescriptors = actionSelector.SelectCandidates(routeContext);
var actionDescriptor = actionSelector.SelectBestCandidate(routeContext, actionDescriptors);
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
//You need to implement your own AuthorizationHandler that
//checks the actionDescriptor. It will be in AuthorizationHandlerContext.Resource.
//In my case, I have custom Authorize attribute applied to the
//controller action and this attribute is available
//in actionDescriptor.FilterDescriptors
var ok = await authService.AuthorizeAsync(httpContext.User, actionDescriptor, "YOUR_POLICY");
return ok;
}

Related

Blazor WASM AuthenticationState using AAD - Claims null until Refresh

New to Blazor and have been doing a hatchet job to get things working how I want.
I am using Blazor WASM with AAD for Authentication created based on this document MS Doc. I implemented the SecureAccountFactory class from the example and call a db where I get the associated user based on the AAD Guid, then add everything into Claims.
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(SecureUserAccount account,
RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity.IsAuthenticated)
{
var userIdentity = (ClaimsIdentity)initialUser.Identity;
var claims = userIdentity.Claims;
var principalId = claims.Where(x => x.Type == "oid").First();
//Get some user info from SQL
var User = await _UserService.Get(principalId.Value);
//Get user Roles from SQL and add to Claims
var UsersInRoles = await _UsersInRoleService.RolesByUserId(principalId.Value);
//Add the ClientId to Claims
userIdentity.AddClaim(new Claim("clientId", User.ClientId.ToString()));
foreach (var userrole in UsersInRoles)
{
userIdentity.AddClaim(new Claim("appRole", userrole.Role.Name));
}
}
return initialUser;
}
I then have a Profile Component that appears on every page as part of the MainLayout which should have some info about the current user, so I made a static class to retrieve this info.
public static class UserHelper
{
public static async Task<CurrentUserClaims> GetCurrentUserClaims(Task<AuthenticationState> authenticationStateTask)
{
AuthenticationState authenticationState;
authenticationState = await authenticationStateTask;
var AuthenticationStateUser = authenticationState.User;
var user = authenticationState.User;
var claims = user.Claims;
var clientClaim = claims.Where(x => x.Type == "clientId").First();
var principalId = claims.Where(x => x.Type == "oid").First();
return new CurrentUserClaims
{
ClientId = Convert.ToInt32(clientClaim.Value),
PrincipalId = Guid.Parse(principalId.Value),
user = user
};
}
}
In my ProfileComponent, I call CascadingParameter and then onParametersSet I query my Static class for the info from the current logged in user
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private string profilePath;
protected override async Task OnParametersSetAsync()
{
CurrentUserClaims UserClaims = await UserHelper.GetCurrentUserClaims(authenticationStateTask);
var principal = UserClaims.PrincipalId;
//... do stuff
}
The above all works, after a Refresh or once I route to any other page. The initial Load, after login on the home page shows that the below line always fails with 'Sequence contains no elements'
var clientClaim = claims.Where(x => x.Type == "clientId").First();
I am using Authorize to protect the pages and I will eventually be using the Roles to determine what to display to the user.
A: Surely there's a better way of doing the above. There are lots and lots of articles on creating a custom Auth which inherits AuthenticationState but every one I've seen adds the Claims manually as a fake user, so I don't see how to access the actual Claims.
B: I'm wondering if just using LocalStorage for the User info might be a simpler way to go but is it considered 'safe' or best practice?
Any pointers to a solution are appreciated.

ASP.NET Core Resolve Controller and call Action by name

I have a generic catch all controller/action that receive files, parse the json content and find out the controller name and action name to be called from that.
Here my previous .NET Framework (old ASP) implementation which worked great:
public async Task<ActionResult> Run(PackingSlip packingSlip, IEnumerable<HttpPostedFileBase> files)
{
var controllerName = packingSlip.service_name;
var actionName = packingSlip.service_object;
// get the controller
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
var ctrl = ctrlFactory.CreateController(this.Request.RequestContext, controllerName) as Controller;
var ctrlContext = new ControllerContext(this.Request.RequestContext, ctrl);
var ctrlDescAsync = new ReflectedAsyncControllerDescriptor(ctrl.GetType());
ctrl.ControllerContext = ctrlContext;
// get the action
var actionDesc = ctrlDescAsync.FindAction(ctrlContext, actionName);
// execute
ActionResult result;
if (actionDesc is AsyncActionDescriptor actionDescAsync)
result = await Task.Factory.FromAsync((asyncCallback, asyncState) => actionDescAsync.BeginExecute(ctrlContext, new Dictionary<string, object> { { "packingSlip", packingSlip }, { "files", files } }, asyncCallback, asyncState), asyncResult => actionDescAsync.EndExecute(asyncResult), null) as ActionResult;
else
result = actionDesc.Execute(ctrlContext, new Dictionary<string, object> { { "packingSlip", packingSlip }, { "files", files } }) as ActionResult;
// return the other action result as the current action result
return result;
}
Now with ASP.NET Core (or .NET 5), ControllerBuilder doesn't exist anymore and most of those things changed.
I tried to inject a IControllerFactory and use it, but can't find the proper way to use it to call an action knowing the "controllerName" and "actionName". It should also, like before, determine if it was an async action or not and act accordingly.
Found the answer by myself.
AspCore have an hidden barely documented extension method that registers controllers in the DI container: AddControllersAsServices.
services.AddMvc().AddControllersAsServices();
Then you can use IServiceProvider to resolve your controllers.

How to allow multiple roles to access route through RouteClaimsRequirement

In a regular type scenario, where a Route is available, say to only "Premium" users, ocelot.global.json would have RouteClaimsRequirement like this:
"RouteClaimsRequirement" : { "Role" : "Premium" }
This would get translated to a KeyValuePair<string, string>(), and it works nicely.
However, if I were to open a route to 2 types of users, eg. "Regular" and "Premium", how exactly could I achieve this?
I found a way through overriding of default Ocelot middleware. Here are some useful code snippets:
First, override the default AuthorizationMiddleware in Configuration() in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var config = new OcelotPipelineConfiguration
{
AuthorisationMiddleware
= async (downStreamContext, next) =>
await OcelotJwtMiddleware.CreateAuthorizationFilter(downStreamContext, next)
};
app.UseOcelot(config).Wait();
}
As you can see, I am using a custom OcelotJwtMiddleware class up there. Here is that class, pasted:
public static class OcelotJwtMiddleware
{
private static readonly string RoleSeparator = ",";
public static Func<DownstreamContext, Func<Task>, Task> CreateAuthorizationFilter
=> async (downStreamContext, next) =>
{
HttpContext httpContext = downStreamContext.HttpContext;
var token = httpContext.Request.Cookies[JwtManager.AuthorizationTokenKey];
if (token != null && AuthorizeIfValidToken(downStreamContext, token))
{
await next.Invoke();
}
else
{
downStreamContext.DownstreamResponse =
new DownstreamResponse(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
};
private static bool AuthorizeIfValidToken(DownstreamContext downStreamContext, string jwtToken)
{
IIdentityProvider decodedObject = new JwtManager().Decode<UserToken>(jwtToken);
if (decodedObject != null)
{
return downStreamContext.DownstreamReRoute.RouteClaimsRequirement["Role"]
?.Split(RoleSeparator)
.FirstOrDefault(role => role.Trim() == decodedObject.GetRole()) != default;
}
return false;
}
}
JwtManager class here is just my small utility made using the default Jwt NuGet package, nothing special. Also, JWT is being stored as a Cookie, which is not safe, but doesn't matter here. If you happen to copy paste your code, you will have small errors relating to this, but just switch it out with your own implementations of auth tokens.
After these 2 snippets were implemented, ocelot.global.json can have RouteClaimsRequirement such as this:
"RouteClaimsRequirement" : { "Role" : "Premium, Regular" }
This will recognize both clients with Regular in their Cookies, as well as those with Premium.

.net core - How to return 403 on AuthorizationHandler?

I implemented my custom AuthorizationHandler.
On that i check i the user can resolved and is active.
If the user isn't active then i would like to return an 403 status.
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserRequirement requirement)
{
var userId = context.User.FindFirstValue( ClaimTypes.NameIdentifier );
if (userId != null)
{
var user = await _userManager.GetUserAsync(userId);
if (user != null)
{
_httpContextAccessor.HttpContext.AddCurrentUser(user);
if (user.Active)
{
context.Succeed(requirement);
return;
}
else
{
_log.LogWarning(string.Format("User ´{1}´ with id: ´{0} isn't active", userId, user.UserName), null);
}
}
else
{
_log.LogWarning(string.Format("Can't find user with id: ´{0}´", userId), null);
}
} else
{
_log.LogWarning(string.Format("Can't get user id from token"), null);
}
context.Fail();
var response = _httpContextAccessor.HttpContext.Response;
response.StatusCode = 403;
}
But i receive a 401. Can you please help me?
Could you check that on the end of your function? I'm using that in my custom middleware to rewrite status code to 401 in some cases but in your scenario should also work
var filterContext = context.Resource as AuthorizationFilterContext;
var response = filterContext?.HttpContext.Response;
response?.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = 403;
//await response.Body.WriteAsync(message, 0, message.Length); only when you want to pass a message
});
According to the Single Responsibility Principle , we should not use the HandleRequirementAsync() method to redirect reponse , we should use middleware or Controller to do that instead . If you put the redirect logic in HandleRequirementAsync() , how about if you want to use it in View page ?
You can remove the redirection-related code to somewhere else (outside) , and now you inject an IAuthorizationService to authorize anything as you like , even a resource-based authorization :
public class YourController : Controller{
private readonly IAuthorizationService _authorizationService;
public YourController(IAuthorizationService authorizationService)
{
this._authorizationService = authorizationService;
}
[Authorize("YYY")]
public async Task<IActionResult> Index()
{
var resource /* = ... */ ;
var x = await this._authorizationService.AuthorizeAsync(User,resource , "UserNameActiveCheck");
if (x.Succeeded)
{
return View();
}
else {
return new StatusCodeResult(403);
}
}
}
in .NET core 6.0 you can use the Fail method
AuthorizationHandlerContext.Fail Method
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AppAuthorizationRequirement requirement)
{
context.Fail(); //Use this
}

Service Stack - Custom authentication on one route

In my current application, I am using Service Stack with JWT's for security. Security has been implemented and works perfectly. Trouble is, I would like to secure one route differently from the others. There is a document the logged in user retrieves, I want to make sure the document they are retrieving is theirs and not someone else's. It is very sensitive data. I would like to secure it differently because something like PostMan could be used with a valid token to retrieve any document, I want to prevent this. The users id is in the token, I would like to match it against the document that is being retrieved if possible. The current security is implemented like so:
public class AppHost: AppHostBase
{
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JsonWebTokenAuthProvider("myKey", "myAudience"),
}));
}
}
JsonWebTokenAuthProvider is a custom class where security was implemented, this all works perfectly. Here is the code:
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
// first validate the token, then get roles from session
string header = request.oauth_token;
// if no auth header, 401
if (string.IsNullOrEmpty(header))
{
throw HttpError.Unauthorized(MissingAuthHeader);
}
string[] headerData = header.Split(' ');
// if header is missing bearer portion, 401
if (!string.Equals(headerData[0], "BEARER", StringComparison.OrdinalIgnoreCase))
{
throw HttpError.Unauthorized(InvalidAuthHeader);
}
// swap - and _ with their Base64 string equivalents
string secret = SymmetricKey.Replace('-', '+').Replace('_', '/');
string token = headerData[1].Replace("\"", "");
// set current principal to the validated token principal
Thread.CurrentPrincipal = JsonWebToken.ValidateToken(token, secret, Audience, true, Issuer);
string lanId = GetLanID(Thread.CurrentPrincipal.Identity.Name);
string proxyAsLanId = request.Meta.ContainsKey(META_PROXYID) ? request.Meta[META_PROXYID] : null;
if (HttpContext.Current != null)
{
// set the current request's user the the decoded principal
HttpContext.Current.User = Thread.CurrentPrincipal;
}
// set the session's username to the logged in user
session.UserName = Thread.CurrentPrincipal.Identity.Name;
session.Roles = GetApplicableRoles(lanId, proxyAsLanId);
authService.Request.SetItem("lanID", lanId);
authService.Request.SetItem("proxyAsLanId", proxyAsLanId);
return OnAuthenticated(authService, session, null, null);
}
I looked up RequestFilterAttribute found here, but I do not think that is what I want. Ideally, if the check fails I would like to return a 401 (unauthorized) if possible.
What is the best way to do this?
If you just want to handle one route differently than you can just add the validation in your single Service, e.g:
public object Any(MyRequest dto)
{
var lanId = base.Request.GetItem("lanId");
if (!MyIsValid(lanId))
throw HttpError.Unauthorized("Custom Auth Validation failed");
}
You could do the same in a RequestFilter, e.g:
public class CustomAuthValidationAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object responseDto)
{
var lanId = req.GetItem("lanId");
if (!MyIsValid(lanId))
{
res.StatusCode = (int) HttpStatusCode.Unauthorized;
res.StatusDescription = "Custom Auth Validation failed";
res.EndRequest();
}
}
}
And apply it to a single Service:
[CustomAuthValidation]
public object Any(MyRequest dto)
{
//...
}
Or a collection of Services, e.g:
[CustomAuthValidation]
public class MyAuthServices : Service
{
public object Any(MyRequest1 dto)
{
//...
}
public object Any(MyRequest2 dto)
{
//...
}
}