I have a .Net Core 3.0 Web API that is configured with as such:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
...
});
services.AddAuthorizationCore(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
And I enable it in the controller like:
[Authorize(Roles = "Admin,Technician")]
public IActionResult CreateFoo([FromBody] Foo foo)
Some api endpoints are also disabled using the [AllowAnonymous].
This product is supporting multiple environments, and one endpoint needs to be either anonymous or authorized dependent on the runtime variable; currently using custom "ASPNETCORE_ENVIRONMENT" options.
I have seen this comment from .net security person, but if I implement a custom policy, it disallows anonymous access.
What is the easiest way to allow anonymous access if the application is running in a certain environment?
If I understand your question then you could create a custom attribute and always grant the user access when the application is running in a certain env?
public class CustomEnvRequirement : AuthorizationHandler<CustomEnvRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomEnvRequirement requirement)
{
string currentEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
// Allow Anonymous when the current env is development.
if (currentEnv.ToLowerInvariant().Equals("development"))
{
context.Succeed(requirement);
}
else if (currentEnv.ToLowerInvariant().Equals("production"))
{
// TODO: add more authorization logic.
}
return Task.CompletedTask;
}
}
And here's the Custom attribute to be added
[Authorize(Policy = "CustomEnv")]
public IActionResult Index()
{
return this.View();
}
Also, make sure to configure it in the startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("CustomEnv",
policy => policy.Requirements.Add(new CustomEnvRequirement()));
});
AuthorizeAttribute is just an implementation of AuthorizationFilterAttribute . You can create your own implementation that will bypass authentication for certain environments:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class EnvironmentSpecificAutorizeAttribute : AuthorizeAttribute
{
public string AllowAnonymousEnvironment { get; set; }
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
// if currentEnv == AllowAnonymousEnvironment
// return
// else
// base.HandleUnauthorizedRequest(actionContext);
}
public override void OnAuthorization(HttpActionContext actionContext)
{
// same logic as above
base.OnAuthorization(actionContext);
}
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
// same logic as above
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
}
You may find other suggestions in this thread
Related
In the web application I'm currently working on there is a requirement for all users to be authenticated. This is currently handled by using an AuthorizeFilter.
I now need to be able to apply different authorization policies to different parts of the application, and therefore I wanted to switch from using a global authorization filter to setting the fallback policy (as described in, and recommended by, the official documentation).
This works as intended, with the exception that requests for resources which don't exist now returns HttpStatusCode 401 if not authenticated, or 403 if authenticated but some other requirement is not fulfilled (and we have a few in the default/fallback policy). Previously, with the authorization filter solution, a 404 would be returned. I would guess the reason is that the fallback policy is evaluated earlier in the pipeline, than the authorization filter is, but it is still a side-effect I would like to avoid.
(How) Can I get the application to return 404s like before while utilizing the FallbackPolicy? I guess I could use a custom IAuthorizationMiddlewareResultHandler if the application was using net5.0 (or later), but an upgrade isn't in the short term plan which means the solution must work for netcoreapp3.1.
I've managed to get the result I was after while utilizing netcoreapp3.1 by adding a custom authorization handler which extends DenyAnonymousAuthorizationRequirement:
public class CustomDenyAnonymousAuthorizationRequirement : DenyAnonymousAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
{
if (context.Resource != null)
{
return base.HandleRequirementAsync(context, requirement);
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
This code just marks the requirement as fulfilled if there is no resource.
Add it the service collection like so:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IAuthorizationHandler, CustomDenyAnonymousAuthorizationRequirement>();
}
If using net5.0+ a custom IAuthorizationMiddlewareResultHandler can be used to accomplish the same:
public class CustomAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new();
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
if ((authorizeResult.Challenged || authorizeResult.Forbidden) && context.GetEndpoint() == null)
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
}
}
And added to the service collection:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationMiddlewareResultHandler>();
}
To returns 404 with fallback policy:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapFallbackToController("api/{**slug}", nameof(ErrorController.Error404), "Error");
endpoints.MapFallbackToPage("{**slug}", "/Public/Errors/404");
});
And obviously the action decorated with AllowAnonymous
[Route("api/[controller]")]
[ApiController]
public class ErrorController : ControllerBase
{
[HttpGet]
[AllowAnonymous]
public IActionResult Error404()
{
return NotFound();
}
}
For razor pages:
builder.AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Public");
Policies
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole(LoginEntities.Sede.ToString())
.Build();
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
To solve this problem where by applying FallBackPolicy we are no longer receiving 404,, we can make use of Middlewares.
public class TestMiddleware
{
private readonly RequestDelegate next;
public TestMiddleware(RequestDelegate requestDelegate)
{
next = requestDelegate;
}
public async Task Invoke(HttpContext httpContext)
{
await next(httpContext);
var ep = httpContext.GetEndpoint();
if(ep == null)
{
httpContext.Response.StatusCode = 404;
await httpContext.Response.WriteAsync("Not Found!!!");
}
}
}
Above is just a sample implementation of such middleware. We can add this middleware after UseEndpoints middleware in the pipeline.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseMiddleware<TestMiddleware>();
In the middleware code we are using GetEndPoints method on HttContext to understand whether the call actually mapped to any ActionMethod if not we can return 404. So we can have fallback policy and yet we can return 404 for Not found resources
I have implemented a custom IAuthorizationPolicyProvider following the documentation provided here, but when I debug and reach the handler and look into the context.User object, I see that the properties like IsAuthenticated or context.User.IsInRole are false/empty. My application is configured with jwt token authorization, and I have confirmed that the token does in fact contain values in the roles payload data, but it doesn't seem to authenticating before it reaches the handler for me to use those values. Can someone help me understand the order of operations, or how I might be able to step through the authentication actually happening?
I have both authentication and authorization in my Startup.cs:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
services.AddAuthorization(options =>
{
var defaultAuthorizationBuilder = new AuthorizationPolicyBuilder("Bearer");
defaultAuthorizationBuilder = defaultAuthorizationBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationBuilder.Build();
}
services.AddSingleton<IAuthorizationPolicyProvider, MyCustomPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, MyCustomHandler>();
After much tinkering, I want to share what I learned that answers this and the broader question of how implementing a custom policy provider works. When using AddAuthentication and AddAuthorization in the Startup.cs, this serves as the policy setup that will be used when you set up the default authorization policy provider, for example like so:
public YourAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
{
this.BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
For me, I use this fallback policy provider as the default:
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return this.BackupPolicyProvider.GetDefaultPolicyAsync(); //this is the default policy established in Startup.cs
}
(Note: something I found confusing and not a lot of clear documentation on: DefaultPolicy vs FallbackPolicy, especially because it appears that, at the time of writing this, GetFallbackPolicyAsync recently became a method that requires implementation when implementing IAuthorizationPolicyProvider. DefaultPolicy: when the [Authorize] attribute is applied, this is the policy to be used. When no policy is provided, then GetFallbackPolicyAsync is called.)
What I was missing when I was building my custom policies was specifying in the AuthorizationPolicyBuilder which authentication scheme I wanted the policy to use. I realize now it's in the docs, but it's not called out specifically so I missed it:
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (//some check on your policy name)
{
var policy = new AuthorizationPolicyBuilder(//what scheme to use for authentication);
// your requirements
return Task.FromResult(policy.Build());
}
}
The question & current answer gave me excellent direction on how to configure this myself. Sharing a full example of how a custom policy provider can be configured. In my case, I wanted to decorate actions with [AuthorizePermission("Read")] and have the permission checked by a custom AuthorizationHandler.
CustomPolicyProvider.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace MyApp.Authentication.Policies
{
public class CustomPolicyProvider : IAuthorizationPolicyProvider
{
public DefaultAuthorizationPolicyProvider OriginalPolicyProvider { get; set; }
public CustomPolicyProvider(
IOptions<AuthorizationOptions> options)
{
OriginalPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => OriginalPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => OriginalPolicyProvider.GetFallbackPolicyAsync();
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(AuthSettings.Policies.Permission, StringComparison.OrdinalIgnoreCase))
{
var permission = policyName[AuthSettings.Policies.Permission.Length..];
if (!string.IsNullOrWhiteSpace(permission))
{
var policy = new AuthorizationPolicyBuilder(AuthSettings.Schemes.All);
policy.AddRequirements(new PermissionRequirement(permission));
return Task.FromResult<AuthorizationPolicy?>(policy.Build());
}
}
return OriginalPolicyProvider.GetPolicyAsync(policyName);
}
}
}
Program.cs
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.AddAuthenticationSchemes(AuthSettings.Schemes.All)
.Build();
options.AddPolicy(AuthSettings.Policies.Scopes.General, policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes(AuthSettings.Schemes.All);
policy.RequireScope("api");
});
options.AddPolicy(AuthSettings.Policies.Scopes.Identity, policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes(AuthSettings.Schemes.All);
policy.RequireScope("api.identity");
});
});
builder.Services.AddSingleton<IAuthorizationPolicyProvider, CustomPolicyProvider>();
I am in the process of adding integration tests at work for an MVC app. Many of our endpoints have policies applied to them, e.g.
namespace WorkProject
{
[Route("A/Route")]
public class WorkController : Controller
{
[HttpPost("DoStuff")]
[Authorize(Policy = "CanDoStuff")]
public IActionResult DoStuff(){/* */}
}
}
For our integration tests, I have overridden the WebApplicationFactory like it is suggested in the ASP .NET Core documentation. My goal was to overload the authentication step and to bypass the policy by making a class which allows all parties through the authorization policy.
namespace WorkApp.Tests
{
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
options.DefaultChallengeScheme = "Test Scheme";
}).AddTestAuth(o => { });
services.AddAuthorization(options =>
{
options.AddPolicy("CanDoStuff", policy =>
policy.Requirements.Add(new CanDoStuffRequirement()));
});
// I've also tried the line below, but neither worked
// I figured that maybe the services in Startup were added before these
// and that a replacement was necessary
// services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
services.Replace(ServiceDescriptor.Transient<IAuthorizationHandler, CanDoStuffActionHandler>());
});
}
}
internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
{
public CanDoStuffActionHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class CanDoStuffRequirement : IAuthorizationRequirement
{
}
}
The first thing that I do to the services is override the authentication as suggested here (without the bit about overriding Startup since that didn't seem to work for me). I am inclined to believe that this authentication override works. When I run my tests, I receive an HTTP 403 from within the xUnit testing framework. If I hit the route that I am testing from PostMan I receive an HTTP 401. I have also made a class that lives in the custom web application factory that allows all requests for the CanDoStuff authorization handler. I thought this would allow the integration tests through the authorization policy, but, as stated above, I receive an HTTP 403. I know that a 403 will be returned if the app doesn't know where certain files are. However, this is a post route strictly for receiving and processing data and this route does not attempt to return any views so this 403 is most likely related to the authorization policy which, for some reason, is not being overridden.
I'm clearly doing something wrong. When I run the test under debug mode and set a breakpoint in the HandleRequirementsAsync function, the application never breaks. Is there a different way that I should be attempting to override the authorization policies?
Here is what I did.
Override the WebApplicationFactory with my own. Note, I still added my application's startup as the template parameter
Create my on startup function which overrides the ConfigureAuthServices function that I added.
Tell the builder in the ConfigureWebHost function to use my custom startup class.
Override the authentication step in the ConfigureWebHost function via builder.ConfigureServices.
Add an assembly reference to the controller whose endpoint I am trying to hit at the end of builder.ConfigureServices in the ConfigureWebHost function.
Write my own IAuthorizationHandler for the policy that allows all requests to succeed.
I hope I have done a decent job at explaining what I did. If not, hopefully the sample code below is easy enough to follow.
YourController.cs
namespace YourApplication
{
[Route("A/Route")]
public class WorkController : Controller
{
[HttpPost("DoStuff")]
[Authorize(Policy = "CanDoStuff")]
public IActionResult DoStuff(){/* */}
}
}
Test.cs
namespace YourApplication.Tests
{
public class Tests
: IClassFixture<CustomWebApplicationFactory<YourApplication.Startup>>
{
private readonly CustomWebApplicationFactory<YourApplication.Startup> _factory;
public Tests(CustomWebApplicationFactory<YourApplication.Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task SomeTest()
{
var client = _factory.CreateClient();
var response = await client.PostAsync("/YourEndpoint");
response.EnsureSuccessStatusCode();
Assert.Equal(/* whatever your condition is */);
}
}
}
CustomWebApplicationFactory.cs
namespace YourApplication.Tests
{
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Test Scheme"; // has to match scheme in TestAuthenticationExtensions
options.DefaultChallengeScheme = "Test Scheme";
}).AddTestAuth(o => { });
services.AddAuthorization(options =>
{
options.AddPolicy("CanDoStuff", policy =>
policy.Requirements.Add(new CanDoStuffRequirement()));
});
services.AddMvc().AddApplicationPart(typeof(YourApplication.Controllers.YourController).Assembly);
services.AddTransient<IAuthorizationHandler, CanDoStuffActionHandler>();
});
builder.UseStartup<TestStartup>();
}
}
internal class CanDoStuffActionHandler : AuthorizationHandler<CanDoStuffActionRequirement>
{
public CanDoStuffActionHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanDoStuffActionRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class CanDoStuffRequirement : IAuthorizationRequirement
{
}
}
TestStartup.cs
namespace YourApplication.Tests
{
public class TestStartup : YourApplication.Startup
{
public TestStartup(IConfiguration configuration) : base(configuration)
{
}
protected override void ConfigureAuthServices(IServiceCollection services)
{
}
}
}
I can't figure out why my authorization won't succeed.
I found this while looking into potential reasons:
https://github.com/aspnet/Security/issues/1103
Seems like OP had a similar issue, though my issue isn't even related to resource based authorization.
Here's my code:
AuthorizationHandler:
public class DebugOrDeveloperRequirementHandler : AuthorizationHandler<DebugOrDeveloperRequirement>
{
private readonly IHostingEnvironment _environment;
public DebugOrDeveloperRequirementHandler(IHostingEnvironment environment)
{
// breakpoint here - does get hit
_environment = environment;
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DebugOrDeveloperRequirement requirement)
{
// breakpoint here but never hit
if (_environment.IsDevelopment() || _environment.IsIntegrationTest() || context.User.IsInRole(Constants.RoleNames.Developer))
context.Succeed(requirement);
return Task.CompletedTask;
}
}
requirement:
public class DebugOrDeveloperRequirement : IAuthorizationRequirement
{
}
Startup.cs code:
services.AddAuthorization(config =>
{
config.AddPolicy(ApplicationPolicyNames.Contractor, builder =>
{
builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole(DataLayer.Setup.Constants.RoleNames.Contractor, DataLayer.Setup.Constants.RoleNames.Developer, DataLayer.Setup.Constants.RoleNames.Admin);
});
config.AddPolicy(ApplicationPolicyNames.Customer, builder =>
{
builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole(DataLayer.Setup.Constants.RoleNames.Customer, DataLayer.Setup.Constants.RoleNames.Developer, DataLayer.Setup.Constants.RoleNames.Admin);
});
config.AddPolicy(ApplicationPolicyNames.Administrator, builder =>
{
builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole(DataLayer.Setup.Constants.RoleNames.Developer, DataLayer.Setup.Constants.RoleNames.Admin);
});
config.AddPolicy(ApplicationPolicyNames.Developer, builder =>
{
builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireRole(DataLayer.Setup.Constants.RoleNames.Developer);
});
config.AddPolicy(ApplicationPolicyNames.DeveloperOrDebug, builder =>
{
builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.Requirements.Add(new DebugOrDeveloperRequirement());
});
});
services.AddSingleton<IAuthorizationHandler, DebugOrDeveloperRequirementHandler>();
My code does not look all that different from documentation. Hence i can't really see why this AuthorizationHandler is not called.
Well now i feel silly - i thought action authorize attributes override controller attributes - they don't.
My controller had a Developer Policy - that made the action fail before that handler even got to its execution turn.
I want to properly use DI in ASP.NET Core 2.0 in order to have my custom method handle the OnTokenValidated event that fires after a JWT token is validated during authentication. The solution below works, except that in the handler I use an injected service that hits MemoryCache to check for cached items added elsewhere in a controller (I've verified that they're added and persisted), and when it's accessed, the cache is always empty. I suspect this is because my custom handler object is being created by a different container (due to the early BuildServiceProvider() call?) and is utilizing a separate instance of MemoryCache (or similar).
If that's the case, I guess I'm not clear on how to properly add and reference my class and method in ConfigureServices() in startup.cs so that it works as intended. Here's what I have:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
...
services.AddScoped<IJwtTokenValidatedHandler, JwtTokenValidatedHandler>();
// add other services
...
var sp = services.BuildServiceProvider();
services.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, bOptions =>
{
// Configure JwtBearerOptions
bOptions.Events = new JwtBearerEvents
{
OnTokenValidated = sp.GetService<JwtTokenValidatedHandler>().JwtTokenValidated
};
}
My custom handler class is below. The ValidateSessionAsync() call uses an injected AppSessionService to access the MemoryCache object and ensure a cache entry exists:
public class JwtTokenValidatedHandler : IJwtTokenValidatedHandler
{
AppSessionService _session;
public JwtTokenValidatedHandler(AppSessionService session)
{
_session = session;
}
public async Task JwtTokenValidated(TokenValidatedContext context)
{
// Add the access_token as a claim, as we may actually need it
var accessToken = context.SecurityToken as JwtSecurityToken;
if (Guid.TryParse(accessToken.Id, out Guid sessionId))
{
if (await _session.ValidateSessionAsync(sessionId))
{
return;
}
}
throw new SecurityTokenValidationException("Session not valid for provided token.");
}
}
If the custom OnTokenValidated method contained simple logic and didn't need an injected service I would inline it with an anonymous function or declare it privately in startup.cs. I'd prefer to fix this approach if I can, but I'd be open to other ones.
Instead of using static/singleton events, consider subclassing JwtBearerEvents and using the JwtBearerOptions.EventsType option:
public class CustomJwtBearerEvents : JwtBearerEvents
{
AppSessionService _session;
public CustomJwtBearerEvents(AppSessionService session)
{
_session = session;
}
public override async Task TokenValidated(TokenValidatedContext context)
{
// Add the access_token as a claim, as we may actually need it
var accessToken = context.SecurityToken as JwtSecurityToken;
if (Guid.TryParse(accessToken.Id, out Guid sessionId))
{
if (await _session.ValidateSessionAsync(sessionId))
{
return;
}
}
throw new SecurityTokenValidationException("Session not valid for provided token.");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<CustomJwtBearerEvents>();
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.EventsType = typeof(CustomJwtBearerEvents);
});
}
}