Feature-flag based authorization with Autofac - c#

I am trying to roll out authorization in an entire environment and would like to feature flag this for quick rollback if it goes south. Once we know all services are aligned with OAuth this feature will be removed and become permanent. I have chosen the IAutofacAuthorizationFilter to inject an object to determine the feature flag state which a typical attribute doesn't offer.
I'd like to enable the default behavior as if I had decorated the controller with [Authorize] if the feature is true otherwise let the methods execute without it, but I'm having trouble enabling the default behavior from inside a IAutofacAuthorizationFilter where there is no base class to override like await base.OnAuthorizationAsync(actionContext, cancellationToken); inside a AuthorizeAttribute.
What I have working so far:
public class FeatureBasedAuthorizeAttribute : IAutofacAuthorizationFilter
{
private readonly IFeatureManager _featureManager;
public FeatureBasedAuthorizeAttribute(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_featureManager.IsEnabled<EnableAppAuthorization>())
{
// Return result of default ASP.Net authorization here... How?
}
// Return without Authorization (current state)
await Task.FromResult(0);
}
}
// Wire up in startup.cs
builder.Register(c => new FeatureBasedAuthorizeAttribute(c.Resolve<IFeatureManager>()))
.AsWebApiAuthorizationFilterForAllControllers()
.InstancePerRequest();

Ultimately time away from the screen solved it for me. My solution was this:
public async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_featureManager.IsEnabled<EnableAppAuthorization>())
{
// Return result of default ASP.Net authorization
var authorizeAttribute = new AuthorizeAttribute();
await authorizeAttribute.OnAuthorizationAsync(actionContext, cancellationToken);
}
// Return without Authorization (current state)
await Task.FromResult(0);
}

Related

Custom Authorization Filtering Minimal API .Net 6

I'm exploring Minimal APIs in .Net 6, and trying to apply a custom Authorization Filter to the endpoint (via Attributes or Extensions).
But it seems to me, I am doing something wrong, or it's simply not designed to work in that way (and it's sad if so).
Couldn't find anything in the docs besides the default usage of [Authorize] attribute in Minimal APIs.
Here is the Filter
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//Checking tokens
}
And if I try to apply it at Controller level, it works fine
[CustomAuthorize]
public class CustomController : ControllerBase
{
//Necessary routing
}
But if I switch to Minimap APIs notation and try to use attributes
app.MapGet("/customEndpoint",
[CustomAuthorize] async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id));
or even an extension method
app.MapGet("/customEndpoint",
async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id)).WithMetadata(new CustomAuthorizeAttribute());
It just doesn't work. The filter doesn't even being constructed.
What did I miss or did wrong?
Thx in advance
You can write a custom authorization filter for Minimal API in .NET 6.0
Here is how I tend to approach it - by using Policy-based authorization in ASP.NET Core
Step 1: Create a Requirement
A requirement implements IAuthorizationRequirement
public class AdminRoleRequirement : IAuthorizationRequirement
{
public AdminRoleRequirement(string role) => Role = role;
public string Role { get; set; }
}
Note: A requirement doesn't need to have data or properties.
Step 2: Create a Requirement Handler
A requirement handler implements AuthorizationHandler<T>
public class AdminRoleRequirementHandler : AuthorizationHandler<AdminRoleRequirement>
{
public AdminRoleRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (context.User.HasClaim(c => c.Value == requirement.Role))
{
context.Succeed(requirement);
}
else
{
_httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
_httpContextAccessor.HttpContext.Response.ContentType = "application/json";
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "Unauthorized. Required admin role." });
await _httpContextAccessor.HttpContext.Response.CompleteAsync();
context.Fail();
}
}
private readonly IHttpContextAccessor _httpContextAccessor;
}
Note: HandleRequirementAsync method returns no value. The status of either success or failure is indicated by calling context.Succeed(IAuthorizationRequirement requirement) and passing the requirement that has been successfully validated or by calling context.Fail() to indicate AuthorizationHandlerContext.HasSucceeded will never return true, even if all requirements are met.
Step 3: Configure Your Policy in the Authorization Service
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("AMIN", p => p.AddRequirements(new AdminRoleRequirement("AMIN")));
});
Step 4: Add Your Requirement Handler to DI
builder.Services.AddSingleton<IAuthorizationHandler, AdminRoleRequirementHandler>();
Step 5: Apply Policy to Endpoints
app.MapGet("/helloworld", () => "Hello World!").RequireAuthorization("AMIN");
I think you won't be able to inject action filter in minimal api, you can use 3 alternative approches.
Create a custom middleware and inject it in startup class, it would check every request and do the intended work as you filter is doing. You can put a check for the request path there if you only need to validate a specific controller/endpoint.
The second approach is you can inject httpcontext in minimal api like this, from that extract jwt token and validate that, if found not ok reject that request.
app.MapGet("/customEndpoint", async (HttpContext context, ICustomService service) =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.isNullOrEmpty(token) || <not a valid token>) return Results.Unauthorized();
// do some work
return Results.Ok(result);
});
as #Dai suggested, you can extract token in this way also
AuthenticationHeaderValue.TryParse(context.Request.Headers["Authorization"], out var parsed ) && parsed.Scheme == "BearerOrWhatever" ? parsed.Parameter : null
You can register the filter globally from startup.cs.

Cancellation Token Injection

I'd like to be able to pass cancellation tokens via dependency injection instead of as parameters every time. Is this a thing?
We have an asp.net-core 2.1 app, where we pass calls from controllers into a maze of async libraries, handlers and other services to fulfil the byzantine needs of the fintech regulatory domain we service.
At the top of the request, I can declare that I want a cancellation token, and I'll get one:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
return Ok();
}
Now, I want to be a good async programmer and make sure my cancellationToken gets passed to every async method down through the call chain. I want to make sure it gets passed to EF, System.IO streams, etc. We have all the usual repository patterns and message passing practices you'd expect. We try to keep our methods concise and have a single responsibility. My tech lead gets visibly aroused by the word 'Fowler'. So our class sizes and function bodies are small, but our call chains are very, very deep.
What this comes to mean is that every layer, every function, has to hand off the damn token:
private readonly ISomething _something;
private readonly IRepository<WeirdType> _repository;
public SomeMessageHandler(ISomething<SomethingElse> something, IRepository<WeirdType> repository) {
_something = something;
_repository = repository;
}
public async Task<SomethingResult> Handle(ComplexThing request, CancellationToken cancellationToken) {
var result = await DoMyPart(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
result.SomethingResult = await _something.DoSomethingElse(result, cancellationToken);
return result;
}
public async Task<SomethingResult> DoMyPart(ComplexSubThing request, CancellationToken cancellationToken) {
return await _repository.SomeEntityFrameworkThingEventually(request, cancellationToken);
}
This goes on ad infinitum, as per the needs of our domain complexity. It seems like CancellationToken appears more times in our codebase than any other term. Our arg lists are often already too long (i.e. more than one) as it is, even though we declare a million object types. And now we have this extra little cancellation token buddy hanging around in every arg list, every method decl.
My question is, since Kestrel and/or the pipeline gave me the token in the first place, it'd be great if I could just have something like this:
private readonly ISomething _something;
private readonly IRepository<WeirdType> _repository;
private readonly ICancellationToken _cancellationToken;
public SomeMessageHandler(ISomething<SomethingElse> something, ICancellationToken cancellationToken) {
_something = something;
_repository = repository;
_cancellationToken = cancellationToken;
}
public async Task<SomethingResult> Handle(ComplexThing request) {
var result = await DoMyPart(request);
_cancellationToken.ThrowIfCancellationRequested();
result.SomethingResult = await _something.DoSomethingElse(result);
return result;
}
public async Task<SomethingResult> DoMyPart(ComplexSubThing request) {
return await _repository.SomeEntityFrameworkThingEventually(request);
}
This would then get passed around via DI composition, and when I had something that needs the token explicitly I could do this:
private readonly IDatabaseContext _context;
private readonly ICancellationToken _cancellationToken;
public IDatabaseRepository(IDatabaseContext context, ICancellationToken cancellationToken) {
_context = context;
_cancellationToken = cancellationToken;
}
public async Task<SomethingResult> DoDatabaseThing() {
return await _context.EntityFrameworkThing(_cancellationToken);
}
Am I nuts? Do I just pass the damn token, every damn time, and praise the async gods for the bounty that has been given? Should I just retrain as a llama farmer? They seem nice. Is even asking this some kind of heresy? Should I be repenting now? I think for async/await to work properly, the token has to be in the func decl. So, maybe llamas it is
First of all, there are 3 injection scopes: Singleton, Scoped and Transient. Two of those rule out using a shared token.
DI services added with AddSingleton exist across all requests, so any cancellation token must be passed to the specific method (or across your entire application).
DI services added with AddTransient may be instantiated on demand and you may get issues where a new instance is created for a token that is already cancelled. They'd probably need some way for the current token to be passed to [FromServices] or some other library change.
However, for AddScoped I think there is a way, and I was helped by this answer to my similar question - you can't pass the token itself to DI, but you can pass IHttpContextAccessor.
So, in Startup.ConfigureServices or the extension method you use to register whatever IRepository use:
// For imaginary repository that looks something like
class RepositoryImplementation : IRepository {
public RepositoryImplementation(string connection, CancellationToken cancellationToken) { }
}
// Add a scoped service that references IHttpContextAccessor on create
services.AddScoped<IRepository>(provider =>
new RepositoryImplementation(
"Repository connection string/options",
provider.GetService<IHttpContextAccessor>()?.HttpContext?.RequestAborted ?? default))
That IHttpContextAccessor service will be retrieved once per HTTP request, and that ?.HttpContext?.RequestAborted will return the same CancellationToken as if you had called this.HttpContext.RequestAborted from inside a controller action or added it to the parameters on the action.
I think you are thinking in a great way, I do not think you need to regret or repent.
This is a great idea, I also thought about it, and I implement my own solution
public abstract class RequestCancellationBase
{
public abstract CancellationToken Token { get; }
public static implicit operator CancellationToken(RequestCancellationBase requestCancellation) =>
requestCancellation.Token;
}
public class RequestCancellation : RequestCancellationBase
{
private readonly IHttpContextAccessor _context;
public RequestCancellation(IHttpContextAccessor context)
{
_context = context;
}
public override CancellationToken Token => _context.HttpContext.RequestAborted;
}
and the registration should be like this
services.AddHttpContextAccessor();
services.AddScoped<RequestCancellationBase, RequestCancellation>();
now you can inject RequestCancellationBase wherever you want, and the better thing is that you can directly pass it to every method that expects CancellationToken this is because of public static implicit operator CancellationToken(RequestCancellationBase requestCancellation)
this solution helped me, hope it is helpful for you also

How to write an asynchronous Policy handler, injecting a scoped service

I'm trying to write a custom policy for an ASP.NET Core 3.1 web application, using a custom Identity storage provider.
I've tried to wrap my head around the fact that policies in ASP.NET Core are designed to take user informations from an HttpContext object, when I read this in a MSDN Article:
once you hold a reference to the user, you can always find the username from the claims and run a query against any database or external service
I started writing my own policy (as of now a simple role requirement) injecting the UserManager into the constructor:
public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
private UserManager<AppUser> UserManager;
public RoleHandler(UserManager<AppUser> usermanager)
{
UserManager = usermanager;
}
}
Now I have a couple problems:
INJECTING A SCOPED SERVICE IN A SINGLETON
Policies are supposed to be lasting for the entire application life, so that would be a Singleton:
services.AddSingleton<IAuthorizationHandler, RoleHandler>();
but the UserManager injected in the policy server is a scoped service and that is not allowed. Solution was very easy, changing the configuration of the policy service from a singleton to a scoped service
services.AddScoped<IAuthorizationHandler, RoleHandler>();
but I don't know whether that cause any issue or not.
WRITING AN ASYNCHRONOUS POLICY HANDLER
This is my implementation of the HandleRequirementAsync method:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
AppUser user = UserManager.FindByIdAsync(context.User.Identity.Name).Result;
if (user != null)
{
bool result = UserManager.IsInRoleAsync(user, requirement.Role.ToString()).Result;
if (result) context.Succeed(requirement);
}
return Task.CompletedTask;
}
I used Task.Result but it blocks the thread. I can't use await because that would make the method returning a Task<Task> instead of a Task and I can't change it. How can I solve this?
Don't return Task.CompletedTask.
When you declare a method as async, it implicitly returns a Task when the first await is hit:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
AppUser user = await UserManager.FindByIdAsync(context.User.Identity.Name);
if (user != null)
{
bool result = await UserManager.IsInRoleAsync(user, requirement.Role.ToString());
if (result) context.Succeed(requirement);
}
}
Task.CompletedTask is generally used when you need to implement a Task returning method synchronously, which you are not.
My HandleRequirementAsync also calls httpClient.GetAsync (Blazor server, .NET 5), adding async to the HandleRequirementAsync and execute the await hpptClient.GetAsync() breaks the authorization. With async method with delays, Try typing the route address in the browser and it will redirect to not authorized page, even though the context.Succeed(requirement) is executed.
The working solution for me is to keep the HandleRequirementAsync as it is, returning Task.CompletedTask. For the async method we need to call, just use pattern for calling async method from non async method.
The one I use is from https://stackoverflow.com/a/43148321/423356
my sample async method:
public async Task<IList<Permission>> GetGroupPermissions(int userId)
{
HttpResponseMessage response = await _httpClient.GetAsync(string.Format("Auth/GroupPermissions/{0}", userId));
try
{
var payload = await response.Content.ReadFromJsonAsync<List<Permission>>();
response.EnsureSuccessStatusCode();
return payload;
}
catch
{
return new List<Permission>();
}
}
HandleRequirementAsync:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var t2 = (Task.Run(() => GetGroupPermissions(userId)));
t2.Wait();
var userGroupPermissions = t2.Result;
if (!userGroupPermissions.Contains(requirement.Permission))
{
//context.Fail(); //no need to fail, other requirement might success
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}

Using Custom IHttpActionInvoker in WebAPI for TransactionScope in Async actions

I want to apply TransactionScope for every async controller actions. Rather than doing it in every action I want to do it in a central place so that it would be applicable for all actions. I tried creating a custom IHttpActionInvoker which inherits from ApiControllerActionInvoker
public class ControllerActionTransactionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Task<HttpResponseMessage> result;
result = base.InvokeActionAsync(actionContext, cancellationToken);
scope.Complete();
return result;
}
}
}
then in the Startup class of Web Api replaced default IHttpActionInvoker with the newly created one
config.Services.Replace(typeof(IHttpActionInvoker), new ControllerActionTransactionInvoker());
Now, I can call a controller action and get result but I manually raised exceptions for a series of Db operations and the desired Transaction process does not work. So there is partial work done in DB.
And it does not work at all after hosting the api in Azure api app. It says the controller action was not found.
How to resolve this?
Your scope is completing and being disposed before the controller action is finished executing, as you do not await the response. Try it like this instead:
public class ControllerActionTransactionInvoker : ApiControllerActionInvoker
{
public override async Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
HttpResponseMessage result = await base.InvokeActionAsync(actionContext, cancellationToken);
scope.Complete();
return result;
}
}
}
While this might well work, you might also want to consider doing this higher up the Web API stack - perhaps in a handler - as you might miss other transactional activity in e.g. handlers, filters, etc. Depends on what you want in and out of scope.

DelegatingHandler setting CurrentPrincipal

I am trying to unit test an implementation of DelegateHandler. My simplified implementation:
public class FooHandler
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("Vegard"), new[] { "A", "B" });
return await base.SendAsync(request, cancellationToken);
}
}
When I try to unit test this, I do it like this:
public class TestHandler : DelegatingHandler
{
private readonly Func<HttpRequestMessage,
CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public TestHandler()
{
_handlerFunc = (r, c) => Return(HttpStatusCode.OK);
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
public static Task<HttpResponseMessage> Return(HttpStatusCode status)
{
return Task.Factory.StartNew(
() => new HttpResponseMessage(status));
}
}
[TestMethod]
public async Task SendAsync_CorrectTokens_IsAuthorized()
{
var message = new HttpRequestMessage(HttpMethod.Get, "http://www.test.com");
var handler = new AuthorizationHeaderHandler
{
InnerHandler = new TestHandler()
};
var invoker = new HttpMessageInvoker(handler);
var result = await invoker.SendAsync(message, new CancellationToken());
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
Assert.IsTrue(Thread.CurrentPrincipal.Identity.IsAuthenticated); // fails
Assert.AreEqual("Vegard", Thread.CurrentPrincipal.Identity.Name); // fails
}
My guess is that this happens because HttpMessageInvoker runs the DelegateHandler on a separate thread. Can I force these to be on the same thread?
Can I force these to be on the same thread?
You can't.
A better question is "how do I flow Thread.CurrentPrincipal to whatever thread is executing the request"? There is an answer to this question.
Thread.CurrentPrincipal is odd in ASP.NET. In fact, I recommend you don't use it at all; use HttpContext.User instead. But if you want, you can get it to work by understanding these points:
HttpContext.User is flowed by the ASP.NET SynchronizationContext.
Thread.CurrentPrincipal is overwritten by HttpContext.User whenever a thread enters an ASP.NET request SynchronizationContext.
Unfortunately, your current test is flawed in a couple of key points:
After a request is completed, the value of Thread.CurrentPrincipal is undefined.
The current way you're running your tests, there is no HttpContext (or ASP.NET SynchronizationContext), and this interferes with the flowing of the principal user.
To fully test authorization, you'd need an integration test.
Also see my answer to this question.
What you're actually running into is the behavior of await. Await will reset the principal to whatever it was when you entered the await when you exit the await. So since there is no current principal when you call await invoker.SendAsync, there will be no current principal after you await that call.
However, your test handler should see the right principal. What you could do is have your test handler store the current principal in its SendAsync implementation, expose it as a public property, and then have your test assert that the test handler saw the principal it was supposed to. That should work fine, and that should be the behavior you care about.

Categories

Resources