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.
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.
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.
And here is the implementation
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.
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
Next step, is to open the
Startup class and add the in-memory store, so I create a new dictionary of
User, populating it with a new
User and then registering it on the DI
And the registration in
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.
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?
AddAuthentication, I use the following options in order to configure the authentication scheme.
DefaultAuthenticateSchemeis the default scheme to use when a user authenticates. So I set the default scheme to cookie authentication (
CookieAuthenticationDefaults.AuthenticationSchemevalue is "cookie")
DefaultChallengeSchemeis the default challenge to pose to an unauthenticated user, a
401response. In this case we use cookie authentication scheme, so when a
Challengeis 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
AddCookie, I have defined two options
LoginPathis 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
LogoutPathis 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
So, in order for this authentication to work, I need to call the
UseAuthentication method in
Configure, before the
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
I've added the
ProfileViewModel class under the Home folder, next to
And now add the
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.
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
_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.
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.
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.
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.
The login action has a route path
"login" and it receives a
returnUrl as an optional parameter and for the View, it passes a
Create a new
LoginViewModel in the same folder with
Now it's time to create the Login action
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.
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.
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.
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
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
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.
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.
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
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
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
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
HTTP POST Logout action is very straightforward, we just sign the user out and then we redirect back to home page.
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.
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.
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
Let's take a peak into its internals
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
Let's see an example of setting an absolute cookie expiration
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
Now, if you login, wait for 11 seconds and refresh your page, you will see that you are automatically logged out.
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
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.
- ASP.NET Core 2.0 Cookie Authentication - Local logins
- ASP.NET Core 2.0 Authentication with local logins - Implementing claims transformation
- ASP.NET Core 2.0 Authentication with local logins - Responding to backend changes
- ASP.NET Core 2.0 Authentication with local logins - Implementing custom authorization policies
- ASP.NET Core 2.1 Authentication with social logins
- ASP.NET Core 2.0 Authentication with social logins - Implementing a profile store
- ASP.NET Core 2.0 Authentication with Azure Active Directory
- ASP.NET Core 2.0 Authentication with Azure Active Directory B2C
- ASP.NET Core 2.0 Authentication, IdentityServer4 and Angular
Comments powered by Disqus.