Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Claims based authorization in ASP.NET Core

Is there an authoritative sample project utilizing claims based authorization for ASP.NET Core?

Something like [https://silk.codeplex.com/] for MVC.

like image 389
GilliVilla Avatar asked Dec 06 '22 16:12

GilliVilla


1 Answers

From my personal experience there is very little by way of a proper explanation of claims and how to really use them. So I'm going to share my explanation by way of an example. It uses the default ASP.NET MVC core project created in Visual Studio 2015.

The essential thing to remember here is a user can have one or many claims. He can claim to be an “administrator” and have the “name” equal to “bhail” etc. Hence the reason why they use the word claim. The interesting thing is that Claims can be allocated by the ASP.Net application or even from other sources such as Facebook, Twitter etc. This is very important as we start using SPAs and Mobile Apps that use tokens which in turn embed Claims. But that’s for another day. In the example below I’ve amended the default AccountController to allocate a number of claims to a user when they register:

    // POST: /Account/Register
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
            var result = await _userManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {

                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim(ClaimTypes.Role, "Admin"));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim(ClaimTypes.Name, "Bhail"));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim(ClaimTypes.DateOfBirth, "18/01/1970"));
                await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim(ClaimTypes.Country, "UK"));



                // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713
                // Send an email with this link
                //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
                //await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
                //    $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
                await _signInManager.SignInAsync(user, isPersistent: false);
                _logger.LogInformation(3, "User created a new account with password.");

                return RedirectToLocal(returnUrl);
            }
            AddErrors(result);
        }
        // If we got this far, something failed, redisplay form
        return View(model);
    }

Great ! We now have claims stored against a user. These in turn are stored in the persistent store (database). The Table being XXX _Security_User_Claim. I’ve used XXX as I prefix all my table names including identity tables so I can have several clients, with security, on the same database to avoid hosting costs. The Table should contain the entries: 1 http://schemas.microsoft.com/ws/2008/06/identity/claims/role Admin 2 2 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name Bhail 2 3 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth 01/01/2000 2 4 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country UK 2

Now we’ve finished with what the User can do we need to focus our attention to how the Server will handle these. So we need to create Policies. A policy is a collection of claims. So in Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        // Adjustment to params from the default settings
        services.AddIdentity<ApplicationUser, ApplicationRole>()
      .AddEntityFrameworkStores<ApplicationDbContext, int>()
      .AddDefaultTokenProviders();


        services.AddMvc();

        #region Configure all Claims Policies

        services.AddAuthorization(options =>
        {
            //options.AddPolicy("Administrators", policy => policy.RequireRole("Admin"));
            options.AddPolicy("Administrators", policy => policy.RequireClaim(ClaimTypes.Role, "Admin")); // This works the same as the above code
            options.AddPolicy("Name", policy => policy.RequireClaim(ClaimTypes.Name, "Bhail"));

        });
        #endregion

        // Add application services.
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();
    }

The important point to remember is the policies are hardcoded. And you should include the PolicyTypes which are the same as you allocated to the User else it won’t work properly. The last step is to include the Policies against the contorllers and/or actions being checked. So in the HomeController I’ve added the following Policy Checks:

public class HomeController : Controller { public IActionResult Index() { return View(); }

    [Authorize(Policy = "Administrators")]
    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    }

    [Authorize(Policy = "Name")]
    public IActionResult Contact()
    {
        ViewData["Message"] = "Your contact page.";

        return View();
    }

    public IActionResult Error()
    {
        return View();
    }
}

There you go ! Basically Policies are collections of claims. Checks are made against Policies. And Claims themselves are allocated to users. Unlike Role Assignments you now have the power and flexibility to do many to many authorisation from a variety of sources.

like image 62
Bhail Avatar answered Dec 18 '22 07:12

Bhail