Cookie Authentication and [Authorize]
Turn on cookie authentication in Program.cs, sign users in with claims and SignInAsync, protect actions with [Authorize] and roles, and show who's signed in with User.
Journeyman · ASP.NET Core · C# · Updated
After this lesson you can
- Turn on cookie authentication, and sign a user in and out with SignInAsync and SignOutAsync
- Protect actions with [Authorize] and [Authorize(Roles = "...")], and read the signed-in user with User
Key takeaways
AddAuthentication(...).AddCookie(...)turns on cookie authentication, andapp.UseAuthentication()comes beforeapp.UseAuthorization().- To sign a user in, put their name and role in claims and call
HttpContext.SignInAsync;SignOutAsyncdeletes the cookie. - Mark an action
[Authorize]for signed-in users, or[Authorize(Roles = "Admin")]for one role, on the POST action too: hiding a link isn't protection. User.Identity.Nameis the signed-in user's name, andUser.IsInRole("Admin")checks a role, in a controller or a view.- Redirect to
returnUrlonly ifUrl.IsLocalUrlsays it's on this site, and never say whether the user name or the password was wrong.
This lesson adds sign-in to NorthwindWeb: anyone can browse products, but only an administrator can add one.
1. Turn it on
In Program.cs, register cookie authentication with the services:
using Microsoft.AspNetCore.Authentication.Cookies;
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/Denied";
});
and add it to the pipeline, before authorization:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
UseAuthenticationreads the sign-in cookie on every request, and works out who the user is.UseAuthorizationchecks[Authorize]rules, using what authentication found.LoginPathis where someone who isn't signed in is sent;AccessDeniedPathis where a signed-in user without permission is sent.
2. Check the password
The users and their password hashes would live in a database table. To keep this lesson about sign-in, NorthwindWeb keeps two users in memory, in a UserService registered as a singleton. It hashes each password with ASP.NET Core's PasswordHasher, and checks a password like this:
public AppUser? SignIn(string userName, string password)
{
AppUser? user = users.Find(u => u.UserName == userName);
if (user is null)
{
return null;
}
PasswordVerificationResult result = hasher.VerifyHashedPassword(user, user.PasswordHash, password);
return result == PasswordVerificationResult.Failed ? null : user;
}
3. Sign in
The AccountController's POST action checks the password, then signs the user in:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(string userName, string password, string? returnUrl)
{
AppUser? user = userService.SignIn(userName, password);
if (user is null)
{
ViewData["ReturnUrl"] = returnUrl;
ViewData["Error"] = "That user name and password don't match.";
return View();
}
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Role, user.Role)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
- A claim is one fact about the user: their name, their role. The claims go into the cookie.
SignInAsyncwrites the cookie. From the next request on, the user is signed in.asyncandawait: signing in is asynchronous, so the action returns aTask<IActionResult>and waits for it withawait. (Tasks, async and await, in the HTTP and Web APIs chapter, explainsasyncproperly.)returnUrl: when[Authorize]sends someone to sign in, it adds where they were going, such as/Account/Login?ReturnUrl=%2Fproducts%2Fnew. After signing in, they're sent back there.Url.IsLocalUrlmakes sure it's an address on this site, so a crafted link can't send them somewhere else.- The error message doesn't say whether the user name or the password was wrong. That would help someone guessing.
Signing out deletes the cookie:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}
4. Protect actions
[Authorize] on an action (or a whole controller) lets only signed-in users in. Add Roles to narrow it further:
// GET /products/new
[Authorize(Roles = "Admin")]
[HttpGet("new")]
public IActionResult Create() { ... }
Who asks for /products/new |
What happens |
|---|---|
| Nobody signed in | Redirected to /Account/Login?ReturnUrl=%2Fproducts%2Fnew |
grace, role Staff |
Redirected to /Account/Denied?ReturnUrl=%2Fproducts%2Fnew |
ada, role Admin |
The form |
Put the same [Authorize(Roles = "Admin")] on the POST action too. Hiding a link isn't protection: anyone can send a POST without your form. [AllowAnonymous] opens one action inside a controller marked [Authorize].
5. Who's signed in: User
In a controller or a view, User is the signed-in user, built from the cookie's claims:
@if (User.Identity?.IsAuthenticated == true)
{
<form method="post" asp-controller="Account" asp-action="Logout" class="d-flex align-items-center gap-2">
<span class="navbar-text">Hi, @User.Identity.Name</span>
<button type="submit" class="btn btn-link nav-link">Sign out</button>
</form>
}
else
{
<a class="nav-link" asp-controller="Account" asp-action="Login">Sign in</a>
}
User.Identity.Name is the name claim, and User.IsInRole("Admin") checks a role, for showing the Add a Product link only to administrators. Sign-out is a POST form, with its anti-forgery token, so no other site can sign your users out with a link.
Check yourself
2 questions to try on the lesson page.
So what
Turn it on, check the password, sign in with claims, and mark actions [Authorize]: ASP.NET Core sends strangers to sign in, turns away users without the role, and tells every page who's there.
Key terms
- Cookie Authentication
- [Authorize]
- Claims and ClaimsPrincipal
- Authentication Middleware