Asp net core jwt

Asp net core jwt

Общие подходы к авторизации и аутентификации в ASP.NET Core Web API несколько отличаются от того, что мы имеем в MVC. В частности, в Web API механизм авторизации полагается преимущественно на JWT-токены. Что такое JWT-токен?

JWT (или JSON Web Token) представляет собой веб-стандарт, который определяет способ передачи данных о пользователе в формате JSON в зашифрованном виде.

JWT-токен состоит из трех частей:

Header — объект JSON, который содержит информацию о типе токена и алгоритме его шифрования

Payload — объект JSON, который содержит данные, нужные для авторизации пользователя

Signature — строка, которая создается с помощью секретного кода, Headera и Payload. Эта строка служит для верификации токена

Для использования JWT-токенов создадим новый проект ASP.NET Core по типу Empty .

Далее добавим в проект папку Models , в которой определим новый класс Person :

Этот класс будет описывать учетные записи пользователей в приложении.

Для работы с JWT-токенами установим через Nuget пакет Microsoft.AspNetCore.Authentication.JwtBearer .

Также добавим в проект специальный класс AuthOptions , который будет описывать ряд свойств, нужных для генерации токена:

Константа ISSUER представляет издателя токена. Здесь можно определить любое название. AUDIENCE представляет потребителя токена — опять же может быть любая строка, но в данном случае указан адрес текущего приложения.

Константа KEY хранит ключ, который будет применяться для создания токена.

Для встраивания функциональности JWT-токенов в конвейер обработки запроса используется компонент JwtBearerAuthenticationMiddleware . Так, изменим класс Startup следующим образом:

Для установки аутентификации с помощью токенов в методе ConfigureServices в вызов services.AddAuthentication передается значение JwtBearerDefaults.AuthenticationScheme . Далее с помощью метода AddJwtBearer() добавляется конфигурация токена.

Для конфигурации токена применяется объект JwtBearerOptions , который позволяет с помощью свойств настроить работу с токеном. В данном случае использованы следующие свойства:

RequireHttpsMetadata : если равно false, то SSL при отправке токена не используется. Однако данный вариант установлен только дя тестирования. В реальном приложении все же лучше использовать передачу данных по протоколу https.

TokenValidationParameters : параметры валидации токена — сложный объект, определяющий, как токен будет валидироваться. Этот объект в свою очередь имеет множество свойств, которые позволяют настроить различные аспекты валидации токена. Но наиболее важные свойства: IssuerSigningKey — ключ безопасности, которым подписывается токен, и ValidateIssuerSigningKey — надо ли валидировать ключ безопасности. Ну и кроме того, можно установить ряд других свойств, таких как нужно ли валидировать издателя и потребителя токена, срок жизни токена, можно установить название claims для ролей и логинов пользователя и т.д.

Теперь мы можем использовать авторизацию на основе токенов. Однако в прокте пока не предусмотрена генерация токенов. По умолчанию в ASP.NET Core отсутствуют встроенные возможности для создания токена. И в данном случае мы можем либо воспользоваться сторонними решениями (например, IdentityServer или OpenIdDict), либо же создать свой механизм. Выберем второй способ.

Создадим в проекте новую папку Controllers и добавим в нее новый контроллер AccountController :

Для упрощения ситуации данные пользователей определены в виде простого списка. Для поиска пользователя в этом списке по логину и паролю определен метод GetIdentity() , который возвращает объект ClaimsIdentity.

Принцип создания ClaimsIdentity здесь тот же, что и при аутентификации с помощью кук: создается набор объектов Claim, которые включают различные данные о пользователе, например, логин, роль и т.д.

Для обработки запроса в контроллере создан метод Token, который сопоставлен с маршрутом "/token". Этот метод обрабатывает запросы POST и через параметры принимает логин и пароль пользователя.

Сам токен представляет объект JwtSecurityToken , для инициализации которого применяются все те же константы и ключ безопасности, которые определены в классе AuthOptions и которые использовались в классе Startup для настройки JwtBearerAuthenticationMiddleware. Важно, чтобы эти значения совпадали.

С помощью параметра claims: identity.Claims в токен добавляются набор объектов Claim, которые содержат информацию о логине и роли пользователя.

Далее посредством метода JwtSecurityTokenHandler().WriteToken(jwt) создается Json-представление токена. И в конце он сериализуется и отправляет клиенту с помощью метода Json() .

Таким образом генерируется токен.

Для тестирования токена создадим простенький контроллер ValuesController:

И в конце добавим в проект для статических файлов папку wwwroot , а в нее — новый файл index.html :

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

После нажатия кнопки на форме логина запрос будет отправляться методом POST на адрес "/token". Поскольку за обработку запросов по этому маршруту отвечает метод Token контроллера AccountController, по результатам работы которого будет формироваться токен.

Ответом сервера в случае удачной аутентификации будет примерно следующий объект:

Параметр access_token как раз и будет представлять токен доступа. Также в объекте передается дополнительная информация о нике пользователя.

Для того, чтобы в коде js данный токен в дальнейшем был доступен, то он сохраняется в хранилище sessionStorage.

Последние два блока предназначены для отправки запросов к методам ValuesController. Чтобы отправить токен в запросе, нам нужно настроить в запросе заголовок Authorization:

В итоге весь проект будет выглядеть следующим образом:

По ранее сохраненному ключу получаем из хранилища sessionStorage токен и формируем заголовок.

Теперь после получения токена мы можем осуществить запрос к методам контроллера ValuesController:

В то же время если мы попробуем обратиться к тем же методам без токена или с токеном с истекшим сроком, то получим ошибку 401 (Unauthorized).

Читайте также:  Excel печать закрепленной области на каждой странице

This post was written and submitted by Michael Rousos

In several previous posts, I discussed a customer scenario I ran into recently that required issuing bearer tokens from an ASP.NET Core authentication server and then validating those tokens in a separate ASP.NET Core web service which may not have access to the authentication server. The previous posts covered how to setup an authentication server for issuing bearer tokens in ASP.NET Core using libraries like OpenIddict or IdentityServer4. In this post, I’m going to cover the other end of token use on ASP.NET Core – how to validate JWT tokens and use them to authenticate users.

JWT Authentication

The good news is that authenticating with JWT tokens in ASP.NET Core is straightforward. Middleware exists in the Microsoft.AspNetCore.Authentication.JwtBearer package that does most of the work for us!

To test this out, let’s create a new ASP.NET Core web API project. Unlike the web app in my previous post, you don’t need to add any authentication to this web app when creating the project. No identity or user information is managed by the app directly. Instead, it will get all the user information it needs directly from the JWT token that authenticates a caller.

Once the web API is created, decorate some of its actions (like the default Values controller) with [Authorize] attributes. This will cause ASP.NET Core to only allow calls to the attributed APIs if the user is authenticated and logged in.

To actually support JWT bearer authentication as a means of proving identity, all that’s needed is a call to the UseJwtBearerAuthentication extension method (from the Microsoft.AspNetCore.Authentication.JwtBearer package) in the app’s Startup.Configure method. Because ASP.NET Core middleware executes in the order it is added in Startup , it’s important that the UseJwtBearerAuthentication call comes before UseMvc .

UseJwtBearerAuthentication takes a JwtBearerOptions parameter which specifies how to handle incoming tokens. A typical, simple use of UseJwtBearerAuthentication might look like this:

The parameters in such a usage are:

The scenario I worked on with a customer recently, though, was a little different than this typical JWT scenario. The customer wanted to be able to validate tokens without access to the issuing server. Instead, they wanted to use a public key that was already present locally to validate incoming tokens. Fortunately, UseJWTBearerAuthentication supports this use-case. It just requires a few adjustments to the parameters passed in.

  1. First, the Authority property should not be set on the JwtBearerOptions . If it’s set, the middleware assumes that it can go to that URI to get token validation information. In this scenario, the authority URI may not be available.
  2. A new property ( TokenValidationParameters ) must be set on the JwtBearerOptions . This object allows the caller to specify more advanced options for how JWT tokens will be validated.

There are a number of interesting properties that can be set in a TokenValidationParameters object, but the ones that matter for this scenario are shown in this updated version of the previous code snippet:

In my previous posts on the topic of issuing authentication tokens with ASP.NET Core, it was necessary to generate a certificate to use for token signing. As part of that process, a .cer file was generated which contained the public (but not private) key of the certificate. That certificate is what needs to be made available to apps (like this sample) that will be consuming the generated tokens.

With UseJwtBearerAuthentication called in Startup.Configure , our web app should now respect identities sent as JWT bearer tokens in a request’s Authorization header.

Authorizing with Custom Values from JWT

To make the web app consuming tokens a little more interesting, we can also add some custom authorization that only allows access to APIs depending on specific claims in the JWT bearer token.

Role-based Authorization

Authorizing based on roles is available out-of-the-box with ASP.NET Identity. As long as the bearer token used for authentication contains a roles element, ASP.NET Core’s JWT bearer authentication middleware will use that data to populate roles for the user.

So, a roles-based authorization attribute (like [Authorize(Roles = "Manager,Administrator")] to limit access to managers and admins) can be added to APIs and work immediately.

Custom Authorization Policies

Custom authorization in ASP.NET Core is done through custom authorization requirements and handlers. ASP.NET Core documentation has an excellent write-up on how to use requirements and handlers to customize authorization. For a more in-depth look at ASP.NET Core authorization, check out this ASP.NET Authorization Workshop.

The important thing to know when working with JWT tokens is that in your AuthorizationHandler ‘s HandleRequirementAsync method, all the elements from the incoming token are available as claims on the AuthorizationHandlerContext.User . So, to validate that a custom claim is present from the JWT, you might confirm that the element exists in the JWT with a call to context.User.HasClaim and then confirm that the claim is valid by checking its value.

Читайте также:  Hdaudio func 01 ven 1106 dev 0397

Again, details on custom authorization policies can be found in ASP.NET Core documentation, but here’s a code snippet demonstrating claim validation in an AuthorizationHandler that authorizes users based on the (admittedly strange) requirement that their office number claim be lower than some specified value. Notice that it’s necessary to parse the office number claim’s value from a string since (as mentioned in my previous post), ASP.NET Identity stores all claim values as strings.

This authorization requirement can be registered in Startup.ConfigureServices with a call to AddAuthorization to add a requirement that an office number not exceed a particular value (200, in this example), and by adding the handler with a call to AddSingleton :

Finally, this custom authorization policy can protect APIs by decorating actions (or controllers) with appropriate Authorize attributes with their policy argument set to the name used when defining the custom authorization requirement in startup.cs:

Testing it All Together

Now that we have a simple web API that can authenticate and authorize based on tokens, we can try out JWT bearer token authentication in ASP.NET Core end-to-end.

The first step is to login with the authentication server we created in my previous post. Once that’s done, copy the token out of the server’s response.

Now, shut down the authentication server just to be sure that our web API can authenticate without it being online.

Then, launch our test web API and using a tool like Postman or Fiddler, create a request to the web API. Initially, the request should fail with a 401 error because the APIs are protected with an [Authorize] attribute. To make the calls work, add an Authorization header with the value “bearer X” where “X” is the JWT bearer token returned from the authentication server. As long as the token hasn’t expired, its audience and authority match the expected values for this web API, and the user indicated by the token satisfies any custom authorization policies on the action called, a valid response should be served from our web API.

Here are a sample request and response from testing out the sample created in this post:

Вступление

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

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

  • HTML5 Web Storage (localStorage or sessionStorage)
  • Cookies

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

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

Токены также могут передаваться через файлы cookie браузера. Файлы cookie, используемые с флагом httpOnly, не подвержены XSS. httpOnly — это флаг для доступа к чтению, записи и удалению cookies только на сервере. Они не будут доступны через JavaScript на клиенте, поэтому клиент не будет знать о токене, а авторизация будет полностью обрабатываться на стороне сервера.

Мы также можем установить secure флаг, чтобы гарантировать, что cookie передается только через HTTPS. Учитывая эти преимущества, мой выбор пал на cookies.

Эта статья описывает подход к реализации авторизации и аутентификации с помощью httpOnly secure cookies + JSON Web Token в ASP.NET Core Web Api в связке со SPA. Рассматривается вариант, при котором сервер и клиент находятся в разных origin.

Настройка локальной среды разработки

Для корректной настройки и отладки взаимоотношений клиента и сервера через HTTPS я настоятельно рекомендую сразу настроить локальную среду разработки так, чтобы и клиент, и сервер имели HTTPS-соединение.

Если этого не сделать сразу, а пытаться выстраивать взаимоотношения без HTTPS-соединения, то в дальнейшем всплывет великое множество мелочей, без которых не будут корректно работать secure cookies и дополнительные secure-policy в продакшене с HTTPS.

Я покажу пример настройки HTTPS на OS Windows 10, сервер — ASP.NET Core, SPA — React.

Настроить HTTPS в ASP.NET Core можно с помощью флага Configure for HTTPS при создании проекта или, если мы не сделали этого при создании, включить соответствующую опцию в Properties.

Чтобы настроить SPA, нужно модифицировать скрипт на «start», проставив ему значение «set HTTPS=true». Моя настройка выглядит следующим образом:

Читайте также:  Canon eos 30d инструкция на русском

Настройку HTTPS для среды разработки на других окружениях советую смотреть на create-react-app.dev/docs/using-https-in-development

Настройка ASP.NET Core сервера

Настройка JWT

В данном случае подойдет самая обычная реализация JWT из документации или любой статьи, с дополнительной настройкой options.RequireHttpsMetadata = true; так как в нашей среде разработки используется HTTPS:

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

Важно: CORS-policy должна содержать AllowCredentials() . Это нужно, чтобы получить запрос с XMLHttpRequest.withCredentials и отправить cookies обратно клиенту. Подробнее об этом будет написано далее. Остальные опции настраиваются в зависимости от нужд проекта.

Если сервер и клиент находятся на одном origin, то вся конфигурация ниже не нужна.

Настройка cookie-policy

Принудительно настраиваем cookie-policy на httpOnly и secure.

По возможности устанавливаем MinimumSameSitePolicy = SameSiteMode.Strict; — это повышает уровень безопасности файлов cookie для типов приложений, которые не полагаются на обработку cross-origin запросов.

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

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

  1. Прокинуть токен в HTTP-запрос с использованием httpOnly и secure флагов.
  2. Получать и валидировать токены клиентских приложений из HTTP-запроса.

Для этого нам надо:

  • Записывать токен в httpOnly cookie при логине и удалять его оттуда при разлогине.
  • При наличии токена в cookies подставить токен в HTTP-заголовок каждого последующего запроса.
  • Если токена нет в cookies, то заголовок будет пустым, а запрос не будет авторизованным.

Middleware

Основная идея — это реализовать Custom Middleware для вставки токена во входящий HTTP-запрос. После авторизации пользователя мы сохраняем cookie под определенным ключом, например: ".AspNetCore.Application.Id". Я рекомендую задавать название, никак не связанное с авторизацией или токенами, — в этом случае cookie с токеном будут выглядеть как какая-то непримечательная системная константа AspNetCore приложения. Так выше шанс, что злоумышленник увидит много системных переменных и, не разобравшись, какой механизм авторизации используется, пойдет дальше. Конечно, если он не прочитает эту статью и не будет специально высматривать такую константу.

Далее нам нужно вставить этот токен во все последующие входящие HTTP-запросы. Для этого мы напишем несколько строк кода Middleware. Это не что иное, как HTTP-pipeline.

Мы можем вынести эту логику в отдельный Middleware-сервис, чтобы не засорять Startup.cs, идея от этого не изменится.

Для того чтобы записать значение в cookies, нам достаточно добавить следующую строку в логику авторизации:

С помощью наших cookie-policy эти cookie автоматически отправятся как httpOnly и secure. Не нужно переопределять их политику в cookie options.

В CookieOptions можно задать MaxAge, чтобы указать время жизни. Это полезно указывать вместе с JWT Lifetime при выпуске токена, чтобы cookie исчезала по истечении времени. Остальные свойства CookieOptions настраиваются в зависимости от требований проекта.

Для обеспечения большей безопасности советую добавить в Middleware следующие заголовки:

  • Заголовок X-Content-Type-Options используется для защиты от уязвимостей типа MIME sniffing. Эта уязвимость может возникнуть, когда сайт позволяет пользователям загружать контент, однако пользователь маскирует определенный тип файла как что-то другое. Это может дать злоумышленникам возможность выполнять cross-site scripting сценарии или компрометировать веб-сайт.
  • Все современные браузеры имеют встроенные возможности фильтрации XSS, которые пытаются поймать уязвимости XSS до того, как страница будет полностью отображена нам. По умолчанию они включены в браузере, но пользователь может оказаться хитрее и отключить их. Используя заголовок X-XSS-Protection, мы можем фактически сказать браузеру игнорировать то, что сделал пользователь, и применять встроенный фильтр.
  • X-Frame-Options сообщает браузеру, что если ваш сайт помещен внутри HTML-фрейма, то ничего не отображать. Это очень важно при попытке защитить себя от попыток clickjacking-взлома.

Я описал далеко не все заголовки. Есть еще куча способов по достижению большей безопасности веб-приложения. Советую ориентироваться на чеклист по безопасности из ресурса securityheaders.com.

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

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

Я обернул свои методы следующим образом:

Мы можем обернуть свой request config любым способом, главное, чтобы там был withCredentials = true.

Свойство XMLHttpRequest.withCredentials определяет, должны ли создаваться кросс-доменные запросы с использованием таких идентификационных данных, как cookie, авторизационные заголовки или TLS сертификаты.

Этот флаг также используется для определения, будут ли проигнорированы куки, переданные в ответе. XMLHttpRequest с другого домена не может установить cookie на свой собственный домен в случае, если перед созданием этого запроса флаг withCredentials не установлен в true.

Другими словами, если не указать этот атрибут, то наш cookie не сохранится браузером, т.е. мы не сможем обратно отправить cookie на сервер, а сервер не найдет желаемую cookie с JWT и не подпишет Bearer Token в нашем HTTP-pipeline.

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

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

Если зайти в Developer Tools, мы наблюдаем заветные флаги httpOnly и secure:

Проведем crush-test, попробуем вытащить куки из клиента:

Мы наблюдаем ‘ ‘, т.е. куки не доступны из пространства document, что делает невозможным их чтение с помощью скриптов.

Мы можем попробовать достать эти cookie с помощью дополнительных инструментов или расширений, но все перепробованные мной инструменты вызывали именно нативную реализацию из пространства document.

Ссылка на основную публикацию
Adblock detector