I don't know if this may sound stupid, but I'm a bit worried about how authorization is done in Controllers.
Right now I'm using [Authorize] attribute to secure my end points. But, I'm a bit worried I forget to add a policy and anyone could access the end point.
Is there any way to apply all policies by default and just add an attribute forthe ones you want to allow?
In Asp.Net Core 2.x you can use a filter to set a global Authorization Attribute:
services.AddMvc(options =>
{
// This requires an authenticated user for all controllers/actions,
// except when at controller/action the [AllowAnonymous] attribute is set.
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
// In the same way you can set a global AntiforgeryToken
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
In Asp.Net core 3.x endpoint routing is introduced. When enabled you can set this per endpoint, as described in the migration documentation:
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
with possible endpoint configuration:
app.UseEndpoints(endpoints =>
{
// equivalent of [Authorize] attribute on each controller:
endpoints.MapDefaultControllerRoute().RequireAuthorization();
});
Not asked, but recommended: Automatically validate antiforgery tokens for unsafe HTTP methods only.
Related
Currently, we have a working OAuth authentication for our ASP.NET Core 5 Web API. We would like to add a certificate authentication as well to be double sure of our caller. Is there a way to have both of them? I tried the below code but it overrides one over the other.
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options =>
{
options.Instance = aADInstance;
options.ClientId = clientIdWithScope;
options.Domain = aADDomain;
options.TenantId = aADTenantId;
}
)
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
Changing default policy
// Add authentication schemes before, we already did this, so I would skip this part
// Change default Authorization policy
services.AddAuthorization(cfg =>
cfg.DefaultPolicy =
new AuthorizationPolicyBuilder(CertificateAuthenticationDefaults.AuthenticationScheme,
AzureADDefaults.JwtBearerAuthenticationScheme).RequireAuthenticatedUser().Build());
[Authorize] attribute now would require all http request to satisfied both CertificateAuthenticationDefaults.AuthenticationScheme and AzureADDefaults.JwtBearerAuthenticationScheme, that might be not the behavior we want for all of our endpoint, so, be careful with this approach.
Add our own policy
// Add authentication schemes before, we already did this, so I would skip this part
// Adding our own policy
services.AddAuthorization(options =>
{
options.AddPolicy("ComposedPolicy", p =>
{
p.AuthenticationSchemes = new List<string>
{CertificateAuthenticationDefaults.AuthenticationScheme, AzureADDefaults.JwtBearerAuthenticationScheme};
p.RequireAuthenticatedUser();
p.Build();
});
});
[Authorize] attribute behavior now would be untouch, but whenever we want to use our custom policy, we must specify them by [Authorize(Policy = "ComposedPolicy")].
Just choose the approach that suit the best.
I have an Asp.Net Core project setup using openiddict. One of the packages I use (Asp.Net Odata) does not support endpoint routing, so I've disabled it in ConfigureServices
services.AddControllers(c=>c.EnableEndpointRouting = false)
The problem is when I do this the openiddict extension method GetOpenIddictServerRequest returns null. Everything works fine as long as endpoint routing is enabled
[HttpPost("~/oauth/token"), Produces("application/json")]
public async Task<IActionResult> Token()
{
var request = HttpContext.GetOpenIddictServerRequest();
...
I register openiddict as shown below
services.AddOpenIddict()
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and entities.
options.UseEntityFrameworkCore()
.UseDbContext<MiPayOnlineCoreContext>();
})
.AddServer(options =>
{
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(5));
options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(11));
options.SetTokenEndpointUris("/oauth/token");
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.SetIssuer(new Uri(authSettings[nameof(JWTSettings.Issuer)]));
options.AddSigningCertificate(certificate);
options.AddEncryptionCertificate(certificate);
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough();
});
Is it possible to make this work with endpoint routing disabled?
It's a bug in your code: you didn't put app.UseMvc() at the right place.
Make sure it appears after app.UseAuthentication() and app.UseAuthorization() and the error will go away.
I've had no luck getting any Role or Policy attributes working in .Net Core 3. I started my project with the .Net Core Angular starter project with authentication.
I figured this was something to do with the new .AddDefault methods so I have simplified it as much as I possibly can and it still doesn't work.
Here is my policy:
services.AddAuthorization(options =>
{
options.AddPolicy("IsAdmin", policy =>
policy.RequireClaim("role", "admin"));
});
Here is my controller:
[Authorize(Policy = "IsAdmin")]
[Route("api/[controller]")]
public class AdminController : Controller
{
...
I made a custom Profile service that adds the claim to the token,
var claims = new List<Claim>();
if (await _userManager.IsInRoleAsync(user, "Admin"))
{
claims.Add(new Claim(JwtClaimTypes.Role, "admin"));
}
context.IssuedClaims.AddRange(claims);
Inside my access token (from jwt.io):
Other parts of configure services:
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
...
services.AddAuthentication()
.AddIdentityServerJwt();
The plain [Authorize] tag is working fine with the access token on other controllers.
When I hit this controller with the access token I get a 403 response
What am I missing that is preventing this from working?
I try your code and find that the role claim key has been transformed to the standard Role ClaimsType : http://schemas.microsoft.com/ws/2008/06/identity/claims/role
So using ClaimTypes.Role will fix the problem:
services.AddAuthorization(options => {
options.AddPolicy("IsAdmin", policy =>
{
policy.RequireClaim(ClaimTypes.Role,"admin");
});
});
Demo
You should also be able to achieve this without needing a policy. ASP.NET automatically maps common claims to the Microsoft schema.
When you inspect your access token. You will see you are sending the role claim. But when you look at the claims in the controller, you will notice that it has been transformed to http://schemas.microsoft.com/ws/2008/06/identity/claims/role.
There are two things you can do. Either set the RoleClaimType to ClaimTypes.Role. Like so:
services.Configure<JwtBearerOptions>(IdentityServerJwtConstants.IdentityServerJwtBearerScheme, options => {
options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
});
Or tell the JwtSecurityTokenHandler not to map default inbound claims like this:
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
Since it's a static property this can be done at any time. But I set it somewhere during my service registrations.
I'm just starting to get my mind wrapped around some of the .NetCore Identity stuff. However, there are a few things that I'm still a bit confused on. In some examples I've seen things like this:
services.AddAuthentication("mycookie")
.AddCookie("mycookie", options =>
{
options.Cookie.HttpOnly = true,
...
});
services.Configure<CookiePolicyOptions>(options =>
{
options.HttpOnly = true;
...
});
Is CookiePolicyOptions configuring the defaults and the CookieAuthorizationOptions of AddCookie overriding the default for that particular cookie?
Also, does the CookiePolicy configuration in the previous example services.Configure<CookiePolicyOptions> have any effect if there is no Cookie Middleware added in Configure? (i.e. app.UseCookiePolicy();)
Lastly, if you set up a Cookie Authentication Scheme (as shown in the code snippet), do you need both app.UseCookiePolicy() and app.UseAuthentication() middleware?
The use of cookie authentication in .net core is not itself the Identity membership system, rather a component that could be used as a standalone Authentication means or set up as part of a broader Identity configuration.
Identity -> Namespace: Microsoft.AspNetCore.Identity
CookieAuthentication -> Microsoft.AspNetCore.Authentication.Cookies
Identity: CookieConfiguration in Identity is an extension config method of the service Collection
CookieAuth: CookiePolicyOptions provides programmatic configuration for the CookiePolicyMiddleware.
services.Configure<CookiePolicyOptions>(options =>
{
options.HttpOnly = true;
...
});
This part is a set up of the Cookie policy in terms of privacy, GDPR(for Europe) and other policies.
The other part provides means to set up cookies in terms of authorization policy of your application:
services.AddAuthentication("mycookie")
.AddCookie("mycookie", options =>
{
options.Cookie.HttpOnly = true,
...
});
Unfortunately this is all I have as information. I hope more people can add some deeper understanding of the topic.
EDIT - 2022:
A more comprehensive article : https://www.reddit.com/r/dotnet/comments/we9qx8/a_comprehensive_overview_of_authentication_in/
I am applying authorize attibutes on each classes.
So is it possible to avoid this, and secure my entire web application at once?
Something like at "Namespace" level?
I am using .net core mvc application.
You should add your Authorization filter in ConfigureServices method on startup.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(YourCustomAuthorizationAttribute));
});
}
None of the above worked. But I got the solution. So following worked for me.
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
I know this may be late. But ideally, the check must happen only if there is an authorization header found. It is never possible for all pages in a project to require authentication... There must be at least one login page that does not need authentication