Problem
How to implement cookie authentication in ASP.NET Core 2.0
Solution
Create an empty project and update Startup to configure services and middleware for MVC and Authentication,
- public void ConfigureServices(
- IServiceCollection services)
- {
- services.AddAuthentication("FiverSecurityScheme")
- .AddCookie("FiverSecurityScheme", options =>
- {
- options.AccessDeniedPath = new PathString("/Security/Access");
- options.LoginPath = new PathString("/Security/Login");
- });
-
- services.AddMvc();
- }
-
- public void Configure(
- IApplicationBuilder app,
- IHostingEnvironment env)
- {
- app.UseAuthentication();
-
- app.UseMvcWithDefaultRoute();
- }
Create a model to receive login details,
- public class LoginInputModel
- {
- public string Username { get; set; }
- public string Password { get; set; }
- }
Create a login page,
- <form asp-controller="Security" asp-action="Login" method="post">
- <input type="text" name="username" id="username" />
- <input type="password" name="password" id="password" />
- <input type="submit" value="Login" />
- </form>
Create a controller for Login and Logout actions,
- public IActionResult Login()
- {
- return View();
- }
-
- [HttpPost]
- public async Task<IActionResult> Login(LoginInputModel inputModel)
- {
- if (!IsAuthentic(inputModel.Username, inputModel.Password))
- return View();
-
-
- List<Claim> claims = new List<Claim>
- {
- new Claim(ClaimTypes.Name, "Sean Connery"),
- new Claim(ClaimTypes.Email, inputModel.Username)
- };
-
-
- ClaimsIdentity identity = new ClaimsIdentity(claims, "cookie");
-
-
- ClaimsPrincipal principal = new ClaimsPrincipal(identity);
-
-
- await HttpContext.SignInAsync(
- scheme: "FiverSecurityCookie",
- principal: principal);
-
- return RedirectToAction("Index", "Home");
- }
-
- public async Task<IActionResult> Logout()
- {
- await HttpContext.SignOutAsync(
- scheme: "FiverSecurityCookie");
-
- return RedirectToAction("Login");
- }
Finally add a controller to secure using [Authorize] attribute,
- [Authorize]
- public class HomeController : Controller
- {
- public IActionResult Index()
- {
- return View();
- }
- }
Discussion
Authentication middleware intercepts incoming requests and checks for the existence of a cookie holding encrypted user data.
- If a cookie is found, it will be serialised into a ClaimsPincipal type and can be accessed via HttpContext.User property.
- If a cookie isn’t found, middleware redirects to login page using an action method. Through the login page you’ll receive user details and authenticate against your database records. Once authenticated, you’ll need to,
- Create List<Claim> related to the user identity.
- Create ClaimsIdentity, assign claims and specify authentication type.
- Create ClaimsPrincipal and assign identity.
- Call HttpContext.SignInAsync() with authentication scheme name (setup via services, see next section) and Principal.
Cookie Authentication Options
When setting up cookie services there are several options to tweak its behavior like,
- AccessDeniedPath: redirects to this path when authorization fails
- AuthenticationScheme: name that identifies the scheme, used with signing in and out. In the solution it’s FiverSecurityScheme.
- Cookie.Domain: domain used for cookie storage, defaults to request’s host. Browser will only send cookie to matching host
- Cookie.HttpOnly: sets whether cookie can be accessed from client side scripts. Default is truei.e. only accessed via HTTP and not via scripts.
- Cookie.Name: set to override default name of cookie, which is .AspNetCore.Cookies
- Cookie.Path: path where cookie is created, default is ‘/’ i.e. root.
- Cookie.SecurePolicy: determines if cookie can be accessed via HTTPS requests only.
- ExpireTimeSpan: determines how long the cookie is valid for.
- LoginPath: redirects to this page for unauthenticated users.
- ReturnUrlParameter: name of query string parameter appended to URL when redirected to login path.
- SlidingExpiration: set to keep the cookie alive once close to expiry time (half way).
Events
Cookie Authentication allows developers to hook into events at various lifecycle stages of authentication process. For instance you could log successful sign-ins using OnSignedIn or use OnValidatePrincipal (runs on every request) to invalidate the user (e.g. if you want to force sign-out).
Note
For some of the events (e.g. OnValidatePrincipal) the HttpContext.User is null, use the Principalproperty of event’s context parameter.
- Events = new CookieAuthenticationEvents
- {
- OnSignedIn = context =>
- {
- Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
- "OnSignedIn", context.Principal.Identity.Name);
- return Task.CompletedTask;
- },
- OnValidatePrincipal = context =>
- {
- Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
- "OnValidatePrincipal", context.Principal.Identity.Name);
- return Task.CompletedTask;
- },
- },
Sign Out
To delete the authentication cookie, and thus sign out the user, you call HttpContext.SignOutAsync() method with the authentication scheme name.
Cookie Expiration
In order to set an absolute expiry time for the identity/cookie (as opposed to sliding expiration), you could use AuthenticationProperties,
- await HttpContext.SignInAsync(
- scheme: "FiverSecurityScheme",
- principal: principal,
- properties: new AuthenticationProperties
- {
- ExpiresUtc = DateTime.UtcNow.AddMinutes(1)
- });
Migrating from ASP.NET Core 1.x
Prior to ASP.NET Core 2.0 the cookie authentication was setup little differently. It was setup in Configure() method and some of the property names were different too. Below is from the project I originally created using ASP.NET Core 1.x,
- app.UseCookieAuthentication(new CookieAuthenticationOptions
- {
- AccessDeniedPath = new PathString("/Security/Access"),
- AuthenticationScheme = "FiverSecurityCookie",
- AutomaticAuthenticate = true,
- AutomaticChallenge = true,
- CookieHttpOnly = true,
- CookieName = ".Fiver.Security.Cookie",
- CookiePath = "/",
- CookieSecure = CookieSecurePolicy.SameAsRequest,
- Events = new CookieAuthenticationEvents
- {
- OnSignedIn = context =>
- {
- Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
- "OnSignedIn", context.Principal.Identity.Name);
- return Task.CompletedTask;
- },
- OnSigningOut = context =>
- {
- Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
- "OnSigningOut", context.HttpContext.User.Identity.Name);
- return Task.CompletedTask;
- },
- OnValidatePrincipal = context =>
- {
- Console.WriteLine("{0} - {1}: {2}", DateTime.Now,
- "OnValidatePrincipal", context.Principal.Identity.Name);
- return Task.CompletedTask;
- },
- },
- ExpireTimeSpan = TimeSpan.FromMinutes(5),
- LoginPath = new PathString("/Security/Login"),
- ReturnUrlParameter = "RequestPath",
- SlidingExpiration = true,
- });
Also the sign-in and sign-out methods were accessed using Authentication property on HttpContext,
- await HttpContext.Authentication.SignInAsync(
- authenticationScheme: "FiverSecurityCookie",
- principal: principal);
-
- await HttpContext.Authentication.SignOutAsync(
- authenticationScheme: "FiverSecurityCookie");
Source Code
GitHub