How to Implement Cookie Authentication in ASP.NET Core

Вступление

Идентификация по JWT (JSON Web Token) — это довольно единообразный, согласованный механизм авторизации и аутентификации между сервером и клиентами. Преимущества JWT в том, что он позволяет нам меньше управлять состоянием и хорошо масштабируется. Неудивительно, что авторизация и аутентификация с его помощью все чаще используется в современных веб-приложениях.

При разработке приложений с JWT часто возникает вопрос: где и как рекомендуется хранить токен? Если мы разрабатываем веб-приложение, у нас есть два наиболее распространенных варианта:

Сравнивая эти способы, можно сказать, что они оба сохраняют значения в браузер клиента, оба довольно просты в использовании и представляют собой обычное хранилище пар ключ-значение. Разница заключается в среде хранения.

Web Storage (localStorage/sessionStorage) доступен через JavaScript в том же домене. Это означает, что любой JavaScript код в вашем приложении имеет доступ к Web Storage, и это порождает уязвимость к cross-site scripting (XSS) атакам. Как механизм хранения Web Storage не предоставляет никаких способов обезопасить свои данные во время хранения и обмена.

Asp.net core webapi cookie jwt authentication

I have not been able to find much information on a good way to do this – having to duplicate the API is a pain just to support 2 authorization schemes.

I have been looking into the idea of using a reverse proxy and it looks to me like a good solution for this.

  1. User signs into Website (use cookie httpOnly for session)
  2. Website uses Anti-Forgery token
  3. SPA sends request to website server and includes anti-forgery token in header: https://app.mydomain.com/api/secureResource
  4. Website server verifies anti-forgery token (CSRF)
  5. Website server determines request is for API and should send it to the reverse proxy
  6. Website server gets users access token for API
  7. Reverse proxy forwards request to API: https://api.mydomain.com/api/secureResource

Note that the anti-forgery token (#2,#4) is critical or else you could expose your API to CSRF attacks.


Example (.NET Core 2.1 MVC with IdentityServer4):

To get a working example of this I started with the IdentityServer4 quick start Switching to Hybrid Flow and adding API Access back. This sets up the scenario I was after where a MVC application uses cookies and can request an access_token from the identity server to make calls the API.

I used Microsoft.AspNetCore.Proxy for the reverse proxy and modified the quick start.

MVC Startup.ConfigureServices:

services.AddAntiforgery();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

MVC Startup.Configure:

app.MapWhen(IsApiRequest, builder =>
{
    builder.UseAntiforgeryTokens();

    var messageHandler = new BearerTokenRequestHandler(builder.ApplicationServices);
    var proxyOptions = new ProxyOptions
    {
        Scheme = "https",
        Host = "api.mydomain.com",
        Port = "443",
        BackChannelMessageHandler = messageHandler
    };
    builder.RunProxy(proxyOptions);
});

private static bool IsApiRequest(HttpContext httpContext)
{
    return httpContext.Request.Path.Value.StartsWith(@"/api/", StringComparison.OrdinalIgnoreCase);
}

ValidateAntiForgeryToken (Marius Schulz):

public class ValidateAntiForgeryTokenMiddleware
{
    private readonly RequestDelegate next;
    private readonly IAntiforgery antiforgery;

    public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
    {
        this.next = next;
        this.antiforgery = antiforgery;
    }

    public async Task Invoke(HttpContext context)
    {
        await antiforgery.ValidateRequestAsync(context);
        await next(context);
    }
}

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
    {
        return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
    }
}

BearerTokenRequestHandler:

public class BearerTokenRequestHandler : DelegatingHandler
{
    private readonly IServiceProvider serviceProvider;

    public BearerTokenRequestHandler(IServiceProvider serviceProvider, HttpMessageHandler innerHandler = null)
    {
        this.serviceProvider = serviceProvider;
        InnerHandler = innerHandler ?? new HttpClientHandler();
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
        var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
        request.Headers.Authorization =new AuthenticationHeaderValue("Bearer", accessToken);
        var result = await base.SendAsync(request, cancellationToken);
        return result;
    }
}

_Layout.cshtml:

@Html.AntiForgeryToken()

Then using your SPA framework you can make a request. To verify I just did a simple AJAX request:

<a onclick="sendSecureAjaxRequest()">Do Secure AJAX Request</a>
<div id="ajax-content"></div>

<script language="javascript">
function sendSecureAjaxRequest(path) {
    var myRequest = new XMLHttpRequest();
    myRequest.open('GET', '/api/secureResource');
    myRequest.setRequestHeader("RequestVerificationToken",
        document.getElementsByName('__RequestVerificationToken')[0].value);
    myRequest.onreadystatechange = function () {
        if (myRequest.readyState === XMLHttpRequest.DONE) {
            if (myRequest.status === 200) {
                document.getElementById('ajax-content').innerHTML = myRequest.responseText;
            } else {
                alert('There was an error processing the AJAX request: '   myRequest.status);
            }
        }  
    };
    myRequest.send();
};
</script>

This was a proof of concept test so your mileage may very and I’m pretty new to .NET Core and middleware configuration so it could probably look prettier. I did limited testing with this and only did a GET request to the API and did not use SSL (https).

As expected, if the anti-forgery token is removed from the AJAX request it fails. If the user is has not logged in (authenticated) the request fails.

As always, each project is unique so always verify your security requirements are met. Please take a look at any comments left on this answer for any potential security concerns someone might raise.

On another note, I think once subresource integrity (SRI) and content security policy (CSP) is available on all commonly used browsers (i.e. older browsers are phased out) local storage should be re-evaluated to store API tokens which will lesson the complexity of token storage. SRI and CSP should be used now to help reduce the attack surface for supporting browsers.

Authentication & authorization

Now we will use the Cookie Authentication in the ASP.NET Core application. In this application first create 2 controllers which are described below:

1. HomeController.cs

Authentication and authorization in asp.net core mvc using cookie

Authentication and Authorization are two major aspects while thinking about securing your application. Security is the main concern of modern applications because anyone can steal your data if it is not secured. So, if you are going to create an application where the data security is a primary concern, then think about Authentication and Authorization. 

Похожее:  learnapidoc-ru/authentication-and-authorization.md at master · docops-hq/learnapidoc-ru · GitHub

Authentication is the process to validate an anonymous user based on some credentials and Authorization process happens just after that and grants resources to this validated user. So, we can say, it’s two-step validating process before providing the access of the resources or data.

We have many techniques to validate the users, like Windows Authentication, JWT Authentication, and Cookie Authentication etc. Today, we will learn how to implement and make ASP.NET Core MVC applications more secure using Cookie-based authentication and authorization. So, let’s start the demonstration and create a fresh ASP.NET Core MVC project. You can refer to the following for the step by step process of creating an ASP.NET Core MVC application. 

Be sure that while creating the project, your template should be Web Application (Model-View-Controller) and change the authentication as ‘No Authentication’.

Here, you can choose the inbuilt Authentication functionality instead of ‘No Authentication’ and it will provide the readymade code. But we are choosing ‘No Authentication’ here because we are going to add our own Cookie-based authentication functionality in this demo and you will learn how to implement the Authentication and Authorization system from scratch.

We are choosing MVC template because we would like to see some Login and Logout functionality on UI along with Authentication and Authorization using Cookies. Now, click OK and it will take a few seconds and the project will be ready. Run it for checking if everything is working fine or not. Once everything is OK, you are ready to go.

Let’s move to the starting point of the ASP.NET Core application file which is “Startup.cs” where we configure the setting for the application like configuring the required services and configuring the middleware services etc. So, implementing the Authentication features, first, we have to add the authentication and then use it. So, let’s move to Startup.cs’s ConfigureService method and add the authentication feature using the following line of code, it will be just above services.AddMvc().

Now move to Configure in the startup.cs method and use the authentication features using the following line of code, it will be just above  routing.

Following is the whole code for adding the Authentication and using it.

We can implement Authentication through Login feature. In most of the applications today, Authorization is decided internally based on your role. So, now we are going to create account login and logout feature, so just create one more controller as ‘AccountController.cs’ inside the controllers folder and add two action methods, one for rendering the Login View and  the other one for posting user credentials data for logging in to the system. Here is the code for AccountController where we have implemented Login functionality.

The first login action method is rendering the UI for login page and once you fill the data required for Login as username and password then the second action method as Login will work and send the Post request to the server.

In this method, first, we will check whether username and password should not be empty then we will validate the username and password. Here, in this demonstration, we are checking the username and password with some dummy data. You can implement database login instead of this.

After validating the user information, if everything is correct then we create Identity for that user and create the cookie information for it. Based on this principal data, we try to Sign In using a generic function called “SignInAsync” and if everything goes in the right direction then we redirect to the Home page.

Now, let’s create the Login view page from where we can give the functionality to the user to enter the username and password. So, right click on the Login action method and add view without a model. It will automatically create the Account folder inside the Views under that will create “login.cshtml” file. Just open it and create a container and add a form tag along with two textboxes for entering the username and password. Apart from this, create two separate buttons as “Submit” and “Reset”. Once you fill the data and click on the submit button, it will call to Login action method defined in Account Controller using POST call. So, modify the code of “login.cshtml” as follows.

So far we have implemented the Cookie-based Authentication functionality in Asp.Net Core MVC project. But what about Authorization. Authorization means, providing access to the authenticated user to access a resource based on role.

So, let’s first understand how we can implement the Authorization in Asp.Net Core MVC. For now, if you will try to access the HOME page without sign in, you can access it. So, let’s prevent the anonymous user from accessing the HOME page directly, if someone wants to access the HOME page then they should have to go through the Authentication process and then they will be able to access it.

Похожее:  НОВОСТИ | МУП «Элиставодоканал»

So, to accomplish this, let’s open the Home Controller and put the [Authorize] attribute just above to controller. You can place it at action level but here we would like to block the whole home controller functionality and if we want to access, just go and log in. So, just do something like below.

Note
Be sure you have cleared all cookies which have been  created based on your previous login. If you will not do this, you will be accessing the HOME page, it is because authenticated user cookie is available in browser memory.

So, let’s check how it works. Run the application and try to access the Home page. You will see here that your application automatically redirects to Login page. Now let’s try to provide the user information as username = “Admin” and password =” password”. Once you will pass the correct credentials and login then you will redirect to HOME page. So, let’sadd the feature that shows the logged in username along with a logout button. If you click to the log out button, your cookie value will be deleted and you will redirect to login page.

So, let’s open the Account Controller and add the following logout action method.

And now open the Index.cshtml file from the Home folder inside the Views and modify the code as follows. Here, first of all, we are trying to show the Logged In username using @User.Identity.Name and apart from this adding a link for logout.

So far, we are able to understand how to implement Authentication in Asp.Net Core MVC and how to implement Authorization and give access to validate the users. Now, let’s understand how to work with multiple roles. Here we are doing everything manually with some static value, but you can change the logic and connect to the database for validating the user. So, just modify the Login method as follows where we are providing two different kinds of roles; one is Admin role and another is User role. Based on these roles, we will provide access to some of the pages.

  1. [HttpPost]  
  2. public IActionResult Login(string userName, string password)  
  3. {  
  4.     if (!string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(password))  
  5.     {  
  6.         return RedirectToAction(“Login”);  
  7.     }  
  8.   
  9.       
  10.       
  11.     ClaimsIdentity identity = null;  
  12.     bool isAuthenticated = false;  
  13.   
  14.     if (userName == “Admin” && password == “password”)  
  15.     {  
  16.   
  17.           
  18.         identity = new ClaimsIdentity(new[] {  
  19.                     new Claim(ClaimTypes.Name, userName),  
  20.                     new Claim(ClaimTypes.Role, “Admin”)  
  21.                 }, CookieAuthenticationDefaults.AuthenticationScheme);  
  22.   
  23.         isAuthenticated = true;  
  24.     }  
  25.   
  26.     if (userName == “Mukesh” && password == “password”)  
  27.     {  
  28.           
  29.         identity = new ClaimsIdentity(new[] {  
  30.                     new Claim(ClaimTypes.Name, userName),  
  31.                     new Claim(ClaimTypes.Role, “User”)  
  32.                 }, CookieAuthenticationDefaults.AuthenticationScheme);  
  33.   
  34.         isAuthenticated = true;  
  35.     }  
  36.   
  37.     if (isAuthenticated)  
  38.     {  
  39.         var principal = new ClaimsPrincipal(identity);  
  40.   
  41.         var login = HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);  
  42.   
  43.         return RedirectToAction(“Index”“Home”);  
  44.     }  
  45.     return View();  
  46. }  

Now let’s move to HomeController and remove the [Authorize] attribute from the class level and put it in action level as follows. Here we have two different action methods which point to two different views. One is pointing to Index view and another one is pointing to the Setting page. Index page can be accessible to both type of roles, either it is Admin or User but the Setting page can be accessed only by Admin role.

Now modify the Index.cshtml file and add one thing as Role, just modify the code as follows.

Now, we have added everything and it’s time to run the application. So, just press F5 and it will run your application. First, go to “Login” page and login with “User” role.

Once you will log in as a User role, definitely you will be redirected to the home page because the home page is accessible to both types  the roles.

Now, let’s try to access the settings page, here you will get some Access Denied error. It is because “User” role member does not allow you to access the settings page. By default, you will get the following error as per the browser. But you can customize your error and page as well. It totally depends on you.

Now, let log out of the application for the “User” role and try to log in for Admin role. As follows, you can see, we are able to access the home page.

But let try to access the setting page for “Admin” role and yes, you will be accessed the setting page.

Lastly, let me show how you can see the cookie information. For this demo, I am using the Microsoft Edge browser, you can use any other as per your choice. But cookie information saves almost in the same place for every browser. So, just go to Network tab and then Cookie tab. Here you can see all the listed Cookies.

Похожее:  Безопасность REST API от А до ПИ / Хабр

Conclusion

So, today we have learned what authentication and authorization are and how to implement the Cookie Based Authentication and Authorization in Asp.Net Core MVC.

I hope this post will help you. Please leave your feedback using the comments which helps me to improve myself for the next post. If you have any doubts please ask your doubts or query in the comment section and If you like this post, please share it with your friends. Thanks.

Configuration

First we need to configure the Cookie Authentication method. On .NET 6.0 or later versions this is done on the Program.cs class of the app. We need to add the highlighted code lines (shown below) to the program class.

On .NET 5.0 or previous versions, we have to do this configuration inside the Startup.cs of the App. Open Startup.cs file and inside it’s ConfigureServices() method, create the Authentication Middleware service with the AddAuthentication and AddCookie methods:

Note: add the above code before the AddControllersWithViews() method.

In the above code we have passed the CookieAuthenticationDefaults.AuthenticationScheme to the AddAuthentication() method. It sets the default authentication scheme for the app.

Cookie authentication login feature

Add the Login Action method which has the following code. Here we are adding it to the Home Controller.

Cookie authentication logout feature

Next, add the Logout action method to the home controller that contains the following code:

Cookie authentication timeout

The expiry time of the Cookie can be set by using the ConfigureApplicationCookie method of IServiceCollection interface.

The below code added to the ConfigureServices() method of the Startup class sets the expiry time to 10 minutes in sliding expiry way.

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.Name = ".AspNetCore.Cookies";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
        options.SlidingExpiration = true;
    });

Fetch api

If you are using the more modern Fetch API you need to add the property credentials with value include with every request that might result in a response that creates cookies or requests for which cookie are to be sent with.

Here’s an example of a post request:

Jquery

For jQuery you can perform a request with withCredentials set to true this way:

Jwt identity claim

Often, an auth cookie isn’t enough to secure API endpoints or microservices. For the web app to call a service, it can use a JWT bearer token to authenticate. To make the access token accessible, place it inside the identity claims.

In the Login action method within HomeController, expand the list of claims with a JWT:

Logout

To log out of the web app and clear the auth cookie do:

Setup

To begin, I’ll assume you know enough about the ASP.NET MVC framework to gut the scaffolding into a skeleton web app. You need a HomeController with an Index, Login, Logout, and Revoke action methods.

I’ll use debug logs to show critical events inside the cookie authentication. Be sure to enable debug logs in appsettings.json and disable Microsoft and system logs.

My log setup looks like this:

Для чего все это нужно?

Выше я описал устойчивый к XSS способ обмена токенами. Пройдемся и посмотрим на результат реализованной функциональности.

Идея безопасного обмена токеном

Эта часть представляет собой концепцию. Мы собираемся сделать две вещи:

Использование

Чтобы защитить наши API-методы, необходимо добавить атрибут

[AutoValidateAntiforgeryToken]

— для контроллера или

[ValidateAntiForgeryToken]

— для метода.

Настройка asp.net core сервера

Middleware Services

ConfigureServices


services.AddAntiforgery(options => { options.HeaderName = "x-xsrf-token"; });
services.AddMvc();

Configure

app.UseAuthentication();
app.UseXsrfProtection(antiforgery);

Настройка cors-политики

Важно

: CORS-policy должна содержать

AllowCredentials()

. Это нужно, чтобы получить запрос с

Настройка jwt


В данном случае подойдет самая обычная реализация JWT из документации или любой статьи, с дополнительной настройкой

Настройка spa клиента

При расположении клиента и сервера на разных origin требуется дополнительная настройка и на клиенте. Необходимо оборачивать каждый запрос использованием

Conclusion

Implementing an auth cookie is seamless in ASP.NET Core 2.1. You configure cookie options, invoke middleware, and set identity claims. Sign in and sign out methods work based on an authentication scheme. Auth cookie options allow the app to react to back-end events and set a session store. The auth cookie is flexible enough to work well with any enterprise solution.

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 4,00 из 5)
Загрузка...

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector