Home ASP.NET Core 2.0 Cookie Authentication - Local logins
Post
Cancel

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.

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.

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

And the registration in ConfigureServices method

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 theMicrosoft.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.

ConfigureServices method

Configure method.

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

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.

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.

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.

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.

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:

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 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).

Login screen

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

Home page

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.

Profile page

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.

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.

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".

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 nameLogout.cshtml.

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.

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.

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 theMicrosoft.AspNetCore.Authentication namespace.
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 HttpContext.SignInAsync method.

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 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 2.0 Authentication, IdentityServer4 and Angular
This post is licensed under CC BY 4.0 by the author.

Testing AngularJS UI-Router routes

ASP.NET Core 2.0 Authentication with local logins - Implementing claims transformation

Comments powered by Disqus.