static void

ASP.Net Core Security

Nuget packages

Startup Configuration

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 { getset; }
}

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:

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<IActionResultEdit(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.

        public static void Main(string[] args)
        {
            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();
        }