Cookies

ASP.NET Core 2.0 Cookie Authentication – Local logins

ASP.NET Core 2.0 makes it very easy and straightforward to setup a cookie authentication mechanism in your application. Framework provides numerous ways to achieve that, with or without ASP.NET Core Identity membership system.

This post is part of a series on ASP.NET Core 2.0 Authentication and I am about to talk Cookie Authentication without ASP.NET Core Identity. I will show how to setup, login and logout using local logins, a custom implementation of a membership system. In the coming examples I will show how to simply secure your ASP.NET Core 2.0 MVC application using cookies and in-memory stored users, though they could be stored anywhere, database, flat files, etc.
In coming blog posts I am going to show a more appropriate way to do the local login authentication, using a database, signing-up users, transforming their claims, etc., but first let’s see something quick and simple.

Application & Cookie authentication

A very common security authentication technique would be the cookies authentication without doubt. It is used to secure web applications long enough to trust and use it in enterprise.

ASP.NET Core 2.0 provides various ways of implementing cookie-based authentication in our applications, with or without ASP.NET Core Identity. Here, we are going to discuss on using cookie authentication as standalone authentication provider in an ASP.NET Core MVC application.

What are we going to build here exactly and which are the resources that need to be secured?
Well, in this example, we have an MVC application, which has two pages. One is Home and the other is Profile. The latter is the protected resource, whereas Home can be accessed by anonymous users.
Profile page will enumerate throughout authenticated user’s claims.
If a user is anonymous and tries to access this page, he/she will be challenged to enter their credentials in a login screen which they are going to be redirected to.
Of course, users can logout from the application and enter in anonymous state again.

We’ll need some sort of a user store. In this example, I am going to create an in-memory, pre-populated user store and a service to validate user’s credentials.

Code repository can be found on Github.

Let’s start with that, but before we start, make sure to create a new empty Web Application, targeting ASP.NET Core 2.0.
Then install the Microsoft.AspNetCore.All nuget package.

Local logins

Now that the environment is set, let’s go ahead and create a User model and a service to validate user’s credentials.

Under the root of the application, I’ve added a folder called Models and there, I’ve created a new class named User.

public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
view raw User.cs hosted with ❤ by GitHub

Pretty much standard here, our User has an integer as a unique identifier, a user name which is registered with and a password and also some personal details like his first, last name and date of birth. Let’s continue with the service now,

First, I create a new IUserService interface with only one method, ValidateUserCredentialsAsync, to validate user credentials.

public interface IUserService
{
Task<(bool, User)> ValidateUserCredentialsAsync(string username, string password);
}
view raw IUserService.cs hosted with ❤ by GitHub

And here is the implementation

public class UserService : IUserService
{
private readonly IDictionary<string, User> _users;
public UserService(IDictionary<string, User> users) => _users = users;
public Task<(bool, User)> ValidateUserCredentialsAsync(string username, string password)
{
var isValid = _users.ContainsKey(username) &&
string.Equals(_users[username].Password, password, StringComparison.Ordinal);
var result = (isValid, isValid ? _users[username] : null);
return Task.FromResult(result);
}
}
view raw UserService.cs hosted with ❤ by GitHub

So, this service here is expecting a dictionary of string and user. Key is the user name of the user, whereas value is the User object itself. Pretty simple implementation for a simple example.

Now, ValidateUserCredentialsAsync job is to validate if that user is validated based on the username and password input.
Note that this is not a recommended way to validate users credentials, with plain string passwords moving around, this is just an example and it shouldn’t be implemented in production no matter what.

So, first we check if that user exists in our store, that is _users.ContainsKey(username) and if that user exists, then we compare the password input with User’s password. If they match, isValid is true else it’s false. Then I create a new Tuple and I assign it to the result variable, containing the isValid value and the user, if isValid is true, else a null value.
Lastly, I return a Task, which is required from the method’s signature.

Now that we have the models and services ready, it’s time to configure the application, but first, let me go and create the in-memory store and register it on the DI along with the IUserService.

Next step, is to open the Startup class and add the in-memory store, so I create a new dictionary of string and User, populating it with a new User and then registering it on the DI

private readonly IDictionary<string, User> _users = new Dictionary<string, User>
{
{
"George", new User {
Id = 1,
UserName = "George",
FirstName = "George",
LastName = "Dyrrachitis",
Password = "1234",
DateOfBirth = new DateTime(1989, 10, 26)
}
}
};
view raw Startup.cs hosted with ❤ by GitHub

And the registration in ConfigureServices method

services.AddSingleton(_users);
services.AddTransient<IUserService, UserService>();
view raw Startup.cs hosted with ❤ by GitHub

Done, let’s move now on configuring MVC and cookies authentication.

Configuring ASP.NET Core 2.0 application to use cookie authentication

In order to add cookie authentication in the pipeline, first you need to register the authentication middleware in ConfigureServices method, using the AddAuthentication extension on services.

services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/auth/login";
options.LogoutPath = "/auth/logout";
});
view raw Startup.cs hosted with ❤ by GitHub

As you see, first I add the authentication middleware in services and then I use the AddCookie method to declare that I would like cookie authentication to be enabled.
This extension comes with the Microsoft.AspNetCore.Authentication nuget package.

What do the options passed mean?
For AddAuthentication, I use the following options in order to configure the authentication scheme.

  • DefaultAuthenticateScheme is the default scheme to use when a user authenticates. So I set the default scheme to cookie authentication (CookieAuthenticationDefaults.AuthenticationScheme value is “cookie”)
  • DefaultSignInScheme is the default scheme to use when a user signs in. In this case we use cookies to sign in a user.
  • The DefaultChallengeScheme is the default challenge to pose to an unauthenticated user, a 401 response. In this case we use cookie authentication scheme, so when a Challenge is send back to a user, he/she will be redirected to the login path where the cookie configuration has been set up. Check out the options.LoginPath in AddCookie

For AddCookie, I have defined two options

  • LoginPath is the path where the user is redirected to on the cookie authentication challenge. Default value is /Account/Login. In this case I’ve set it up to /auth/login
  • LogoutPath is set the same way, providing the path where the logout action lives in this application

Of course, by setting all these up does not mean that I’m finished yet.
I also have to register the Mvc service into the services and then use those in the pipeline, something that is done in the Configure method.
So, in order for this authentication to work, I need to call the UseAuthentication method in Configure, before the UseMvc or UseMvcWithDefaultRoute.

The ConfigureServices method

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Configuration);
services.AddSingleton(_users);
services.AddTransient<IUserService, UserService>();
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/auth/login";
options.LogoutPath = "/auth/logout";
});
}
view raw Startup.cs hosted with ❤ by GitHub

The Configure method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
view raw Startup.cs hosted with ❤ by GitHub

Note that I call the UseAuthentication after the UseStaticFiles, because I am not interested into authenticating static files, but before the UseMvc method which registers Mvc into the pipeline.

Setting up the protected Profile page

Next, let’s set up the secured resource. I am going to create a view model for this page, which is going to show the user’s name and his associated claims.

Create a new folder named Controllers and add a new subfolder called Home. Add a new controller called HomeController.

I’ve added the ProfileViewModel class under the Home folder, next to HomeController

public class ProfileViewModel
{
public string Name { get; set; }
public IEnumerable<Claim> Claims { get; set; }
}
view raw ProfileViewModel.cs hosted with ❤ by GitHub

And now add the Index and Profile actions in the controller.
Index is going to be allowed anonymous access, whereas Profile is going to be decorated with an Authorize attribute to prevent unauthorized access.

public IActionResult Index()
{
return View();
}
[Authorize]
[Route("profile")]
public IActionResult Profile()
{
var model = new ProfileViewModel
{
Name = User.Identity.Name,
Claims = User.Claims
};
return View(model);
}
view raw HomeController.cs hosted with ❤ by GitHub

In Profile, I pass the ProfileViewModel to the View, populating it with the username, which can be found in HttpContext.User.Identity.Name and his claims, which can be found at HttpContext.User.Claims. This code is executed only when a user is authenticated. Also note the [Authorize] attribute decorating the method. This is used to secure this action from anonymous users trying to access it.

Note: Skip the following step if you have scaffolded the application with the Mvc template.

Go ahead and add the views now, create a new folder named Views and add two subfolders, Home and Shared.

Note: Copy the _ViewImports.cshtml, _ViewStart.cshtml and paste them under Views folder and for Shared folder, copy and paste the _Layout.cshtml found in the Github repository, here.

Now, let’s add the Index.cshtml under the Home folder.

@{
ViewData["Title"] = "Home Page";
}
<div class="jumbotron">
<p>Local logins sample</p>
@if (User.Identity.IsAuthenticated)
{
<span><i class="glyphicon glyphicon-ok text-success"></i>&nbsp; Hello @User.Identity.Name</span>
}
</div>
view raw Index.cshtml hosted with ❤ by GitHub

Very simple implementation of the home page, if the user is authenticated, then a greeting will show on screen.

Now create the Profile.cshtml as well.

@model Authentication.Local.Controllers.Home.ProfileViewModel
@{
ViewData["Title"] = "Profile";
}
<div class="row">
<div class="col-md-12">
<h2>User claims</h2>
<h3>@Model.Name</h3>
@if (Model.Claims.Any())
{
foreach (var claim in Model.Claims)
{
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">@claim.Type</h3>
</div>
<div class="panel-body">
@claim.Value
</div>
</div>
}
}
else
{
<div class="alert alert-danger">
<p>User has no claims.</p>
</div>
}
</div>
</div>
view raw Profile.cshtml hosted with ❤ by GitHub

In this page, I show the user’s name and enumerate through his claims creating a new bootstrap panel for each of them.

If you have been following along, running the application now, you will see the following screen

If I try to access the Profile page I will be redirected to the /auth/login path, but this does not exist yet, so that’s exactly what we are going to build now.

User login

Now it’s time to build the login page.
Go to Controllers folder and create a new sub-folder named Authorization. Create a new controller there, called AuthController. Let’s give it a route path "auth" and create a new login action.

[Route("auth")]
public class AuthController: Controller
view raw AuthController.cs hosted with ❤ by GitHub

The login action has a route path "login" and it receives a returnUrl as an optional parameter and for the View, it passes a LoginViewModel through.
Create a new LoginViewModel in the same folder with AuthController:

public class LoginViewModel
{
[Required]
[DisplayName("User name")]
public string UserName { get; set; }
[Required]
[DisplayName("Password")]
public string Password { get; set; }
public string ReturnUrl { get; set; }
}
view raw LoginViewModel.cs hosted with ❤ by GitHub

Now it’s time to create the Login action

[Route("login")]
public IActionResult Login(string returnUrl)
{
return View(new LoginViewModel
{
ReturnUrl = returnUrl
});
}
view raw AuthController.cs hosted with ❤ by GitHub

In the code above, we are just rendering a view with a view model, populating the ReturnUrl property of that model, which is not going to be shown in the actual page, rather be a hidden field.
That’s all good, but we also need a view in order to show the login form to the user. Go to Views and create a new folder named Auth and there, create a new View, named Login.cshtml, having the following source code.

@model Authentication.Local.Controllers.Authorization.LoginViewModel
@{
ViewData["Title"] = "Login";
}
<div class="row">
<div class="col-md-5">
<h2>Login with your credentials</h2>
@if (!ViewContext.ModelState.IsValid)
{
<div class="alert alert-danger">
<div asp-validation-summary="All"></div>
</div>
}
<form asp-action="Login" method="post">
<div class="form-group">
<label for="username">@Html.DisplayNameFor(m => m.UserName)</label>
<input type="text" class="form-control" placeholder="@Html.DisplayNameFor(m => m.UserName)"
id="username" name="username" asp-for="UserName" />
</div>
<div class="form-group">
<label for="password">@Html.DisplayNameFor(m => m.Password)</label>
<input type="password" class="form-control" placeholder="@Html.DisplayNameFor(m => m.Password)"
id="password" name="password" asp-for="Password" />
</div>
<input type="hidden" name="returnUrl" asp-for="ReturnUrl" />
<button class="btn btn-primary btn-lg" type="submit">Submit</button>
</form>
</div>
</div>
view raw Login.cshtml hosted with ❤ by GitHub

So, if the ModelState is invalid, we show all validation errors in a bootstrap alert.
Below is the form, which makes a POST request to the HTTP POST Login action. All we have is three inputs, one for the username, the other for the password and the last for the ReturnUrl, which is a hidden field. Note that the asp-for tag helper is binding the model’s specific property to the view’s input.

Last piece in the jigsaw is to add the HTTP POST login action. It is going to receive a LoginViewModel, check its state and if it is valid, then it will go ahead and try to sign the user in.

[Route("login")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var (isValid, user) = await _userService.ValidateUserCredentialsAsync(model.UserName, model.Password);
if (isValid)
{
await LoginAsync(user);
if (IsUrlValid(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("InvalidCredentials", "Invalid credentials.");
}
return View(model);
}
view raw AuthController.cs hosted with ❤ by GitHub

First we check if the model is valid. If it is not, then we return the model back to the View to display the error messages.
Otherwise, we continue, by first validating the user, using the injected IUserService. If you haven’t already, inject the IUserService in the AuthController‘s constructor (see below). If the validation was not successful, then we add an error in the current ModelState, indicating that the submitted credentials are invalid.
Otherwise, we go ahead and login the user in. This is done in the LoginAsync method (see below). After it finishes signing, the user in, we finally check if the return URL is a valid (see below), relative URL and redirect there if that’s the case, else we redirect the user back to the home page.

private static bool IsUrlValid(string returnUrl)
{
return !string.IsNullOrWhiteSpace(returnUrl)
&& Uri.IsWellFormedUriString(returnUrl, UriKind.Relative);
}
view raw AuthController.cs hosted with ❤ by GitHub
private readonly IUserService _userService;
public AuthController(IUserService userService)
{
_userService = userService;
}
view raw AuthController.cs hosted with ❤ by GitHub
private async Task LoginAsync(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.DateOfBirth, user.DateOfBirth.ToString("o"), ClaimValueTypes.DateTime)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal);
}
view raw AuthController.cs hosted with ❤ by GitHub

This method is responsible of signing a user in. First, we create the user’s claims (in a real world scenario we would fetch the user’s claims from a database). We create a NameIdentifier claim, which is used to declare a unique identifier for the user. Then we create claims like Name, which is the user’s name, GivenName, which I provide the value of FirstName, Surname whereas I provide the LastName value and finally, DateOfBirth claim which is a DateTime claim with the date of birth value from the user.

Next, I construct the user’s identity, which contains all the user’s claims and I declare the authentication scheme this identity is for, which is the cookie authentication scheme. This is there  to match the authentication scheme we provided when registered the Cookie Authentication through services. Please note that we didn’t need to do that, as if no value is provided, the default will be picked.

After that, I create a new ClaimsPrincipal, passing the newly constructed ClaimsIdentity.
The magic happens in HttpContext.SignInAsync method, which is signing-in a user. This method creates an encrypted cookie and adds it to the current response. This method takes an IPrincipal, which is the ClaimsPrincipal I created earlier. That way, the encrypted cookie will contain all the user’s claims. Under the covers, this method populates the HttpContext.User with the ClaimsPrincipal we signed in, marking its identity as authenticated.

Also, some words on HttpContext.SignInAsync from the official documentation

Under the covers, the encryption used is ASP.NET Core’s Data Protection system. If you are hosting the app on multiple machines, load balancing across apps or using a web farm, then you must configure data protection to use the same key ring and app identifier.

Okay, now let’s try to login. I will open the home page and click on the "Login" link. This will bring me to the login page, whereas I will provide the user’s credentials, which are "George" for username and "1234" for password (not a very secure password but anyways).

Clicking on submit redirects me back the home page, whereas I see a greeting message. That means I am logged in! Cool!

If I navigate to the Profile page, I will be able to access it now. I can see all of the user’s claims, just like I set them up in code.

If you open Chrome’s developer tools (F12) and go to Application tab and Cookies, you will see an .AspNetCore.Cookies entry with an encrypted value. This is our cookie.

That’s all good, now I am able to sign-in as a user. But how do I logout? I need to build a logout mechanism to do so.

User logout

As you have noticed already, in the navigation bar, there is a link to Login page and when a user is authenticated it changes to Logout.

<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-left">
<li><a asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-controller="Home" asp-action="Profile">Profile</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
@if (User.Identity.IsAuthenticated)
{
<li>
<a asp-controller="Auth" asp-route-returnUrl="@Context.Request.GetEncodedPathAndQuery()" asp-action="Logout">Logout</a>
</li>
}
else
{
<li>
<a asp-controller="Auth" asp-action="Login">Login</a>
</li>
}
</ul>
</div>
view raw _Layout.cshtml hosted with ❤ by GitHub

This logout link is pointing to the Auth controller’s Logout action, which is exactly what we are going to build.
So, go to AuthController and create a new action called Logout, giving it a route path of "logout".

[Route("logout")]
public async Task<IActionResult> Logout(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
if (!_configuration.GetValue<bool>("Account:ShowLogoutPrompt"))
{
return await Logout();
}
return View();
}
view raw AuthController.cs hosted with ❤ by GitHub

I’ve also added an optional string for a returnUrl which is going to be used only in the logout consent screen, if the user cancels logout.
Line 6 is checking for a configuration value. I’ve gone ahead and injected the ASP.NET Core’s IConfiguration interface into the controller in order to have access to the appsettings.json. If the ShowLogoutPrompt value is true, then we go ahead and show the logout consent screen. Otherwise, I just go ahead and logout the user without asking for his consent, redirecting him back to the home page.
Let’s take a look on the configuration and the injected value into the controller’s constructor

private readonly IUserService _userService;
private readonly IConfiguration _configuration;
public AuthController(IUserService userService,
IConfiguration configuration)
{
_userService = userService;
_configuration = configuration;
}
view raw AuthController.cs hosted with ❤ by GitHub
{
"Account": {
"ShowLogoutPrompt": false
}
}
view raw appsettings.json hosted with ❤ by GitHub

Now that we have the logout page ready, let’s go ahead and create its view. Go to Auth subfolder of Views and create a new view with name Logout.cshtml.

@{
ViewData["Title"] = "Logout";
var url = ViewBag.ReturnUrl;
}
<div class="row">
<div class="col-md-12">
<h3>Are you sure you want to logout?</h3>
<form a method="post" asp-action="Logout">
<button type="submit" class="btn btn-lg btn-danger">Yes</button>
<a asp-action="Cancel" asp-route-returnUrl="@url" class="btn btn-lg btn-warning">Cancel</a>
</form>
</div>
</div>
view raw Logout.cshtml hosted with ❤ by GitHub

So here we are asking for the user’s consent, if he/she is really sure that wants to logout. If yes, then a POST request will be issued to the logout endpoint (more on this below) which destroys the current session cookie.
Otherwise, the cancel button redirects the user back to where he came from, by using the returnUrl path.

HTTP POST Logout action is very straightforward, we just sign the user out and then we redirect back to home page.

[Route("logout")]
public async Task<IActionResult> Logout(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
if (!_configuration.GetValue<bool>("Account:ShowLogoutPrompt"))
{
return await Logout();
}
return View();
}
view raw AuthController.cs hosted with ❤ by GitHub

So, if the user is authenticated, then we go ahead and we sign him/she out by calling the HttpContext.SignOutAsync method. Please note that we need to pass the target authentication scheme, the one that we used to sign the user in and we configured the authentication provider. Then we redirect back to home page. Let’s see that in action.

[HttpPost]
[Route("logout")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
return RedirectToAction("Index", "Home");
}
view raw AuthController.cs hosted with ❤ by GitHub

We are still logged in as "George". Now it’s time to logout. Click on the Logout link and see getting redirected back to home. The Logout link has changed to Login and if you try to access the Profile page, you will be redirected to the login page to provide credentials.
If you open the Chrome’s developer tools (F12) and go to Application tab and Cookies, you will not see the .AspNetCore.Cookies cookie any more.

Bonus

Cookie can be configured in many ways and it has many configuration properties for you to fiddle around. You can set configuration like the cookie domain, the expiration timespan, observe events and many more. These configuration options can be set on the AddCookie method in Startup, whereas a CookieAuthenticationOptions class is instantiated and passed in the method’s delegate. Check that link for more information on how to configure the authentication cookie.

But also there are some configuration properties that can exist on the ClaimsPrincipal level, when you create the user’s principal. You can instantiate a new AuthenticationProperties class and pass some configuration there. This class is part of the Microsoft.AspNetCore.Authentication namespace.
Let’s take a peak into its internals

namespace Microsoft.AspNetCore.Authentication
{
//
// Summary:
// Dictionary used to store state values about the authentication session.
public class AuthenticationProperties
{
//
// Summary:
// Initializes a new instance of the Microsoft.AspNetCore.Authentication.AuthenticationProperties
// class
public AuthenticationProperties();
//
// Summary:
// Initializes a new instance of the Microsoft.AspNetCore.Authentication.AuthenticationProperties
// class
//
// Parameters:
// items:
public AuthenticationProperties(IDictionary<string, string> items);
//
// Summary:
// State values about the authentication session.
public IDictionary<string, string> Items { get; }
//
// Summary:
// Gets or sets whether the authentication session is persisted across multiple
// requests.
public bool IsPersistent { get; set; }
//
// Summary:
// Gets or sets the full path or absolute URI to be used as an http redirect response
// value.
public string RedirectUri { get; set; }
//
// Summary:
// Gets or sets the time at which the authentication ticket was issued.
public DateTimeOffset? IssuedUtc { get; set; }
//
// Summary:
// Gets or sets the time at which the authentication ticket expires.
public DateTimeOffset? ExpiresUtc { get; set; }
//
// Summary:
// Gets or sets if refreshing the authentication session should be allowed.
public bool? AllowRefresh { get; set; }
}
}

You can setup properties like a RedirectUri, which is an absolute URI where the application will redirect the user when authenticated. Or you can setup properties like IsPersistent, which makes a cookie persistent across browser sessions, meaning that you can close the browser and when reopen it, the cookie will still be there (unless it has expired). Other options are IssuedUtc, which is a DateTimeOffset to indicate the time the cookie was created, ExpiresUtc sets the absolute time when the cookie expires. You must set the IsPersistent to true in order for this to be honored. The AllowRefresh configures the authentication session refreshing.

For example, if you wanted your cookie to be present when you reopen the browser you should set the IsPersistent to true.
If you want the cookie to expire on a specific time, set the IsPersistent to true and the ExpiresUtc to a DateTimeOffset. Then pass the AuthenticationProperties instance to the HttpContext.SignInAsync method.

Let’s see an example of setting an absolute cookie expiration

private async Task LoginAsync(User user)
{
var properties = new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(10)
};
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.DateOfBirth, user.DateOfBirth.ToString("o"), ClaimValueTypes.DateTime)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(principal, properties);
}
view raw AuthController.cs hosted with ❤ by GitHub

In this example, I reuse the LoginAsync code that was used before to sign a user in and I provide some additional authentication properties. What I want here is an absolute expiration of the cookie, the cookie expires after 10 seconds of its creation. Then I casually create the claims, the Identity and the Principal. Please note that in order for these authentication properties to be taken into account, I pass them into the HttpContext.SignInAsync.
Now, if you login, wait for 11 seconds and refresh your page, you will see that you are automatically logged out.

Summary

Code repository can be found on Github.

In this post we saw how to secure an MVC application using cookies and local logins, implementing a custom membership backend. We saw that what you need is the HttpContext.User property to be populated with the appropriate Identity of the user signed-in and the role that HttpContext.SignInAsync method plays into that.

We also saw how to sign-out users from a specific authentication scheme, using the HttpContext.SignOutAsync method.

Additionally, we saw how to configure a cookie authentication mechanism and how to create the authentication ticket for the user.

In the coming posts, I am going to show to manipulate user’s claims, how to perform social logins, with various social providers like Facebook, Twitter, Github, etc. I am also going to show how to create an SPA and secure it, using OpenId Connect and OAuth 2.0.


This post is part of the ASP.NET Core 2.0 Authentication series.

  1. ASP.NET Core 2.0 Cookie Authentication – Local logins
  2. ASP.NET Core 2.0 Authentication with local logins – Implementing claims transformation
  3. ASP.NET Core 2.0 Authentication with local logins – Responding to backend changes
  4. ASP.NET Core 2.0 Authentication with local logins – Implementing custom authorization policies
  5. ASP.NET Core 2.1 Authentication with social logins
  6. ASP.NET Core 2.0 Authentication with social logins – Implementing a profile store
  7. ASP.NET Core 2.0 Authentication with Azure Active Directory
  8. ASP.NET Core 2.0 Authentication with Azure Active Directory B2C
  9. ASP.NET Core API authentication with JWT
  10. Build a secure Angular client using ASP.NET Core and OAuth