I'm using cookie auth with the following settings:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = false,
ExpireTimeSpan = new TimeSpan(2, 0, 0)
});
I've assumed that using the [Authorize] attribute with a policy declared requires a user to be authenticated before evaluating custom requirements, however, it's not the case.
This has also been attempted (note the call to RequireAuthenticatedUser()):
options.AddPolicy("MyPolicy",
policy =>
{
policy.RequireAuthenticatedUser();
policy.Requirements.Add(
new SomeRequirement(serviceProvider.GetService<IMyService>()));
});
However, even then, in my AuthorizationHandler for SomeRequirement, HandleRequirementAsync() still gets executed. Why?
I really, really, really don't want to check if user is authenticated in every AuthenticationHandler like this:
if (!context.User.Identity.IsAuthenticated)
{
context.Fail();
return Task.CompletedTask;
}
That's by design. When you specify a custom authorization policy, you replace the default one. The workaround is simple:
[Authorize(Policy="foo")]
[Authorize]
This combines your policy AND default policy.
Explanation: this is how policies are combined: AuthorizationPolicy. You could see policyProvider.GetDefaultPolicyAsync() is called only if authorizeDatum.Policy is empty (i.e. if custom policy is not specified).
FYI: default policy has only one requirement: DenyAnonymousAuthorizationRequirement.
Its essential part is:
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
return TaskCache.CompletedTask;
So it doesn't fail on an anonymous user, it passes control-flow to the next requirement. Chain of requirements will continue until explicit context.Fail() call or chain's end. To fail-fast on an anonymous user, write the custom requirement:
if (userIsAnonymous) context.Fail()
And place it first in the list (order of handlers here determines their execution order in request pipeline):
services.AddScoped<IAuthorizationHandler, CustomFailAuthorizationRequirementHandler>();
//Register other authorization handlers
Default DenyAnonymousAuthorizationRequirement can be overriden with this custom requirement (requirement handler will be called on every authorization (custom or default)):
services.AddAuthorization(options => {
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddRequirements(new CustomFailAuthorizationRequirement)
.Build();
//Add other policies
});
Don't want to deal with unauthenticated requests in your custom policies? Do what's listed in blowdart's ASP.NET Authorization Workshop on GitHub: authorize all endpoints with a default policy.
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Then use [AllowAnonymous] where needed.
Related
This is my first time trying to create a Web API in Visual Studio using C#.I'm using a database which has a table User. When I want to make request such as logging in and seeing more information I want to authenticate the user. The login method seems to be working just fine, it checks the user's credentials, validates them in the database, lets the user login if the credentials are right and generates the jwt bearer token. I'm generating the token based on claims. However, I don't know how to limit any of the GET, POST, PUT methods for authorized users based on the token. I'm confused as to how will the program know whether you are logged in and can make other requests or not? Should I create another field in the User model which will store let's say true or false for authorization(the token)? And how do I test it using swagger or postman?
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt: Issuer"],
ValidAudience = Configuration["Hwt: Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
Hopefully this can get you pointed in the right direction, you need to wire up a few things for this. You will need to tweak this answer to whatever token scheme you are using, but hopefully this gets you started and you can google from here.
In the startup.cs file add something like this
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.Name);
});
});
In startup.cs where you are setting your IApplicationBuilder app:
app.UseAuthentication();
app.UseAuthorization();
Then in each controller you can decorate the top of the controller with these attributes.
[ApiController]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = "BasicAuthentication")]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[Route("[controller]")]
public class CustomerController : ControllerBase
You don't need all 3 of these, tailor this to whatever token scheme you are using.
You can also decorate the individual endpoints with these attributes, when doing it at the top of the controller class it applies them to all the endpoints.
I have a hard time configuring the API authentication and authorization. There is definitely something I do not understand. Ideas appreciated.
I have NET Core 3.1 API. It contains Razor Pages (documentation and Swagger UI).
There is no sign-in endpoint. The token is validated using the secret signing key.
Requirements:
Use JWT
Make all endpoints protected by default.
Razor pages are public.
Enable anonymous access to health checks.
Enable anonymous access to several endpoints.
Support roles.
I am not sure how to combine all the settings to make it work properly.
I've implemented a custom authentication handler.
I've tried many combinations, add/removing parts of the config.
The main problem is the handler is called every time (AllowAnonymous attribute is ignored) or I'm getting 401 instead of 403.
Playing with:
// in controllers
[Authorize(Roles="Role")]
[AllowAnonymous]
// in configure services
services.AddRazorPages(options => options.Conventions.AllowAnonymousToFolder("/"));
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddScheme<JwtBearerOptions, JwtBearerAuthenticationHandler>(JwtBearerDefaults.AuthenticationScheme, options => { });
services.AddAuthorization(options =>
{
// expected to add the default schema for the [Authorize] attribute (so I do not need to write it explicitly)
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
// tried to resolve the health checks call the authentication handler
options.AddPolicy("PublicPolicy", new AuthorizationPolicyBuilder()
.RequireAssertion(_ => true)
.Build());
});
// in configure
app.UseAuthentication(); // will cause all actions like decoreted with [Authorize]
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages().RequireAuthorization(SecurityRegistration.PublicPolicyName);
// map health checks
endpoints.MapApiHealthChecks("/health")
.RequireAuthorization("PublicPolicy");
}
I have implemented a custom authentication handler. Later on, I refactored it and inherited the JwtBearerHandler.
public class JwtBearerAuthenticationHandler: JwtBearerHandler
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
}
}
Why not using the AddJwtBearer()? I am generating/validating the tokes and I need to implement the OptionsMonitor pattern (reading the security settings from JSON, namely signing key, issuer, audience and expiration).
I expected that when I decorate a controller or an action with [AllowAnonymous] attribute, the handler will not be invoked. But it is called every time.
When I remove the UseAuthentication() I am getting issues with the default authentication schema and I'm getting 401 instead of 403.
I've also tried to add/remove the authorize attribute to controllers using a filter with/without a policy parameter.
configure.Filters.Add(new AuthorizeFilter());
I am not sure I completely understood the changes for Microsoft.Identity.Web but I was following an article (given by Microsoft here) Where it described how to change in startup
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
to
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
while this looks good and easy I have a little more work because I have the following snippet in my existing code
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => this.configuration.Bind("AzureAd", options))
.AddJwtBearer(options =>
{
//this code used to validate signing keys
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).GetAwaiter().GetResult();
var tenantId = this.configuration["TenantId"];
var validIssuer = $"https://sts.windows.net/{tenantId}/";
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = validIssuer,
ValidAudience = this.configuration["ClientId"],
IssuerSigningKeys = openIdConfig.SigningKeys,
};
});
To give you a little bit of context we have two variations with this application
User Login and do some staff (here user will get Microsoft login dialog to login using his/her credential)
Microsoft Azure calls our endpoint with some token and we need to validate that token.
The JWTvaliation section you see above is for the 2nd item where once we received a token we validate that token without login and UI workflow.
Question:
The above code is working correctly. The only issue here is if we like to use Microsoft.Identity how should we use the second item (JWT) because services.AddAuthentication().AddAzureAD returns IAuthenticationBuilder which we use further to add AddJwtBearer, While services.AddMicrosoftIdentityWebAppAuthentication does not return IAuthenticationBuilder.
AddMicrosoftIdentityWebAppAuthentication is actually just a fancy way to do the following:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(…)
So it configures the default scheme to be the OIDC scheme and runs AddMicrosoftIdentityWebApp to configure whatever this ends up doing.
Now, AddAuthentication can actually be called multiple times on the service collection. You just need to be careful not to reconfigure things incorrectly. The parameterless function does not do that, so it is a good way to access the IAuthenticationBuilder to further configure authentication.
That means that you can change your code like this:
// configure Microsoft Identity Web first
// this also sets the default authentication to OIDC
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
// retrieve an authentication builder without changing the default
services.AddAuthentication()
// add JWT bearer now
.AddJwtBearer(options =>
{
// …
});
I'm trying to create an easy policy, which will Allow anonymous Requests for All HttpGet Gets by Default and Authorize for anything else (put, patch, post, delete etc.)
Currently I'm requiring authorization everywhere like so:
var authorizationPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
services.AddMvc(config => config.Filters.Add(new AuthorizeFilter(authorizationPolicy)))
But in reality I want to do something like this:
var authorizationPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser(opt => opt.DesinationCalls.Where(m =>
false == m.Attributes.Any(x => x == "HttpGet"))).Build();
I'm do not want to have to remember add attributes to my controllers,
Does anyone know how I can filter my default authorization policy by Http Verb?
I have added a JWT middleware to my application:
app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true;} )
Ideally what I want to achieve is that all controller actions are protected by default (there were filters for that in previous ASP.NET), and I will put Anonymous on those that I want public or perhaps Authorize("SomePolicy") if I want additional policies, but I want that without a token the API cannot be accessed at all. How do I do this in the ASP.NET Core?
Starting with .Net 6 we can do this (if using minimal hosting model recommended by Microsoft):
app
.MapControllers()
.RequireAuthorization(); // This will set a default policy that says a user has to be authenticated
Starting with .Net Core 3 we can do this:
app.UseEndpoints(endpoints =>
{
endpoints
.MapControllers()
.RequireAuthorization(); // This will set a default policy that says a user has to be authenticated
});
It is possible to change default policy or add a new policy and use it as well.
P.S. Please note that even though the method name says "Authorization", by default it will only require that the user is Authenticated. It is possible to add more policies to extend the validation though.
You can still use filters as in this example:
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
The policy in this example is very simple but there a lots of ways to configure a policy for various requirements, roles etc.
The below example worked for me when using .NET 5, the accepted answer doesn't seem to work for .NET 5
services.AddMvc(config => {
config.Filters.Add(new AuthorizeFilter());
});
There exist a lot of solutions will tell you two of them:-
//First one
builder.Services.AddControllers(opts =>
{
opts.Filters.Add(new AuthorizeFilter());
});
//Second one
builder.Services.AddAuthorization(opts =>
{
opts.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});