Concepts
Before going any further, let’s take a step back and look at a few terms and ideas.
Authentication
Is the user who they say they are? i.e – is the request authentic? Authentication might be done by checking the presence of an authentication cookie, or by validating the signature of a JWT, for example.
Authorisation
Is the user allowed to access the requested resource or perform the requested action? i.e – are they authorised to do so?
Claims
A claim is a key-value pair that tells you something about the user making the request. Or if you prefer, making the claim. We’ll look at claims in Step 2 today.
Policies
A policy is a set of requirements that must be met in order for a user to be authorised to access a set of resources. Those requirements will have handlers that determine whether or not the requirements are met. We’ll look more at policies in Part 2.
Authentication and Authorisation go hand-in-hand in ASP.NET Core, but let’s start with a super-simple authentication use-case of simply checking if the user is logged in or not. Then we’ll explore some more complex authorisation examples in Part 2.
Example: You shall not pass!
In this example, we’ll create a super-simple login form. In fact, even calling it a login form may be a bit of a stretch, since there are no passwords and no backing data store – so everyone can log in! But this will demonstrate how to handle the authenticated vs not-authenticated states.
Step 0: Add Cookie Authentication to Startup.cs
This will be a common requirement, regardless of your application or authentication type. In other words, this still applies for Web APIs using JWT Authentication, and other app and auth types. The first thing we need to do is to configure what type of authentication the application is going to use, and to configure some options for it.
In Startup.cs, add the following to the existing ConfigureServices method.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.None; options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.Name = "BlogPost.AuthCookieAspNetCore"; options.LoginPath = "/Home/Login"; options.LogoutPath = "/Home/Logout"; });
services.Configure(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Strict;
options.HttpOnly = HttpOnlyPolicy.None;
options.Secure = CookieSecurePolicy.None;
});
The first block adds cookie authentication to the application, with some options. We also set the name of the cookie, and some urls. The second block configures our policy options (we’ll look at policies in more detail in Part 2). Also, it’s worth mentioning that the configuration options here are for development only, and you probably shouldn’t use these for hosting an app in production.
At the end of the ConfigureServices method, if your project targets ASP.NET Core 3.1 LTS, you’ll have the following line:
services.AddControllersWithViews();
This call is a common replacement for AddMVC() from ASP.NET Core 2.x. Going into the differences between these two calls (and the other available options) is outside of the scope of this article, but here is a link to another simple blog post where you can read up about them: https://www.strathweb.com/2020/02/asp-net-core-mvc-3-x-addmvc-addmvccore-addcontrollers- and-other-bootstrapping-approaches/
In any case though, in order to get security working correctly, we’ll make one modification to the AddControllersWithViews method call, to provide some options. Change it to:
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AuthorizeFilter());
});
This adds a global AuthorizeFilter to the application, so the user will need to be authorised in order to access any of the routes. We’ll need to add an AllowAnonymous attribute to the login routes, so that the user can login without having to be logged in (and avoid a catch-22). This essentially is a whitelist approach to securing routes, in that all routes are protected except those that we whitelist (make public) using the AllowAnonymous attribute.
The other approach we could take is (instead of adding this global filter via the options lambda) to use a blacklist instead, where each route in the application is public, except those that we protect using an Authorize attribute. Generally, I prefer the former.
Step 1: Add authentication to the app pipeline
This is an easy step to forget, but it’s required to get authentication working correctly. This step is also going to be required no matter your app or authentication type, though the exact code used may differ slightly.
In Startup.cs, in the Configure method (below the ConfigureServices method we modified earlier), add the following just before the app.UseEndpoints(...) line.
app.UseCookiePolicy(); // Don't forget this line
app.UseAuthentication(); // And this line
If we forget to add these two, then the required components won’t be registered in the HTTP Request Pipeline, so things will definitely not work as we expect. Thankfully, it’s a short and sweet step and we shouldn’t need to fiddle with this as the application grows.
Step 2: Add login and logout logic to HomeController
So far we’ve set up our application to use authentication and authorisation, but we haven’t actually implemented any means for a user to become authenticated and authorised. In other words, we’ll need to implement somewhat of a login / logout feature. For simplicity, I’ve created a public passwordless form where anybody can sign in, so the logic you’ll see is missing any calls to verify that the user exists, is active, etc. And for brevity, only the login and logout logic is shown in this article, but you can look at the whole solution on github via the link at the end of the article.
On the existing HomeController, add the following:
[AllowAnonymous]
public IActionResult Login()
{
if (User.Claims.Any())
{
return View("Index"); }
return View();
}
This public route will serve the Index view if the user is already logged in (i.e – if they have any claims). Otherwise, it’ll serve the login view.
public async Task Logout()
{
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Login");
}
This route signs the user out (i.e – it destroys any previously established authentication cookie) and redirects them back to the Login action.
[AllowAnonymous]
[HttpPost]
public async Task Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return View(model); }
var claims = new List
{
new Claim(nameof(LoginViewModel.Name), model.Name), new Claim(ClaimTypes.Email, model.Email), new Claim(nameof(LoginViewModel.Age), model.Age.ToString()) };
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties();
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
return RedirectToAction("Index");
}
This public route does a few things. If the model that represents the login form is invalid, it’ll simply return the view, so it can display the appropriate error messages to the user. Otherwise, it’ll create and assign three claims: name, email and age. It’ll then sign the user in, which will establish the authentication cookie in the browser.
And that’s all for this example! If you run the project, you’ll be able to log in with whatever name and email you like, access the auto-generated (now protected) Home and Privacy pages, and log out. If all you need is very basic authentication, then this should have you covered. For a slightly more complicated example, stay tuned for Part 2 of this article!
For more articles on .net core, bash and more, you can check out Jason’s blog at [https://jason.sultana.net.au].
Sample project on GitHub
https://github.com/jasonsultana/aspnet-core-auth-example
Further reading
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple?view=aspnetcore-3. 1
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-3.1 https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-3. 1
https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimsidentity?view=netcore- 3.1