ASP.Net Core Security
Nuget packages
- Microsoft.AspNetCore.Authorization
- Microsoft.AspNetCore.Authentication.Cookies
- For bearer: Microsoft.AspNetCore.Authentication.JwtBearer
Startup Configuration
- In ConfigureServices, services.AddAuthorization();
- In Configure, app.UseCookieAuthentication(options => { options.AuthenticationScheme = "Cookie"; ...
- For login page, set the options.LoginPath = new PathString("/Account/Login/"); //unauthenticated/ 401
- For "not authorized" page, set the options.AccessDeniedPath = new PathString("/Account/Forbidden/"); //unauthorized/ 403
- See docs for Bearer (js)/Cookie (html) schemes - you have to set options.AutomaticAuthenticate = false when you have two schemes
Policy
Policy is a richer than simple Role-based authorization.
Configure in Startup.ConfigureServices
services.AddAuthorization(opts => opts.AddPolicy("PolicyName", policy => policy.RequireRole("Administrator") );
You can have several named policies, which your Authorize attribute can add together.
Policies can be more complex:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
//a role
options.AddPolicy("AdministratorOnly", policy => policy.RequireRole("Administrator"));
//a specific claim with paramarray of acceptible values
options.AddPolicy("EmployeeId", policy => policy.RequireClaim("EmployeeId", "123", "456"));
//a custom requirement
options.AddPolicy("Over21Only", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
services.AddMvc(config =>
{
//For MVC config, build a policy (here, must be authenticated)
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
//Add a global filter
config.Filters.Add(new AuthorizeFilter(policy));
});
//register the custom handlers that know what to do with custom requirements
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}
Custom Requirements
Custom requirements have the IAuthorizationRequirement marker interface
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(int age)
{
MinimumAge = age;
}
public int MinimumAge { get; set; }
}
And an handler (which must be registered in ConfigureServices). If there are 2 handlers for the same requirement, the request is authorized if either succeeds ("or").
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override void Handle(AuthorizationContext context, MinimumAgeRequirement requirement)
{
//check the claim (and issuer).
if (!context.User.HasClaim(
c => c.Type == ClaimTypes.Age &&
c.Issuer == "http://contoso.com"))
{
return;
}
//find the claim
var age = Convert.ToInt32(context.User.FindFirst(
c => c.Type == ClaimTypes.Age &&
c.Issuer == "http://contoso.com").Value);
//test the claim
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
//if fails, don't do anything
}
}
Controllers
Old-style [Authorize] attribute and [AllowAnonymous] are still used (there's no Authorize(Users=...) though)
[Authorize(Roles = "Admin,Reader")] //classic style
[Authorize(Policy = "AdminPolicy")]
[Authorize(AuthenticationSchemes = "Cookie,Bearer")]
For login, create a ClaimsPrincipal and await HttpContext.Authentication.SignInAsync("Cookie", userPrincipal,new AuthenticationProperties...
Resource authorisation
docs.asp.net. Conditional authorisation (for instance, user is only able to see their own files)
Controller/Service:
- take an IAuthorizationService argument in DI
- call await _authorizationService.AuthorizeAsync(User, document, "policyName")
public class FileController : Controller
{
private readonly IAuthorizationService _authorizationService;
private readonly IFileService _fileService;
public FileController(IAuthorizationService authorizationService, IFileService fileService)
{
_authorizationService = authorizationService;
_fileService = fileService;
}
public async Task<IActionResult> Edit(int id)
{
var document = _fileService.Find(id);
if (await _authorizationService.AuthorizeAsync(User, document, "EditPolicy"))
{
return View(document);
}
return new ChallengeResult();
}
}
JWT Bearer Token
Use in webapi scenarios (here with CORS). Install Microsoft.AspNetCore.Authentication.JwtBearer. In your appsettings.config add the Auth and Cors values.
{
var builder = WebApplication.CreateBuilder(args);
// Get config params
string clientUrl = builder.Configuration.GetSection("Cors").GetValue<string>("ClientUrl");
// Cors
const string corsPolicyName = "AllowSpecificOrigins";
builder.Services.AddCors(options =>
{
options.AddPolicy(corsPolicyName, builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithOrigins(clientUrl);
});
});
// Authentication with jwt token
var authority = builder.Configuration.GetSection("Auth").GetValue<string>("Authority");
var validIssuer = builder.Configuration.GetSection("Auth").GetValue<string>("ValidIssuer");
var audience = builder.Configuration.GetSection("Auth").GetValue<string>("Audience");
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Authority = authority;
options.Audience = audience;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = validIssuer
};
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseCors(corsPolicyName);
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}