Перейти к основному содержимому

Интеграция для .NET Core

В статье рассматривается пример подключения двухфакторной аутентификации для сайтов, построенных на платформе .NET Core. За основу взят шаблонный проект ASP.NET Core Web Application.

Принцип работы

  1. Сайт проверяет логин и пароль пользователя
  2. Если данные указаны корректно, запрашивает API мультифактора для получения адреса страницы доступа и отправляет на нее пользователя
  3. Получает от Мультифактора токен доступа, сохраняет в куки пользователя
  4. При каждом запросе берет токен доступа из куки и добавляет в заголовок Authorization: Bearer
  5. Проверяет токен и разрешает доступ

Конфигурационный файл

Добавьте в файл appsettings.json раздел с параметрами для подключения к API Мультифактора

"Multifactor": {
"ApiKey": "",
"ApiSecret": "",
"CallbackUrl": "https://localhost:44300/account/mfa"
},
  • Параметры ApiKey и ApiSecret доступны в личном кабинете
  • CallbackUrl это адрес возврата пользователя на ваш сайт

Клиент для API

Добавьте в проект службу для взаимодействия с API Мультифактора

/// <summary>
/// Multifactor Client
/// </summary>
public class MultifactorService
{
//параметры для подключения к API
private string _apiKey;
private string _apiSecret;
private string _callbackUrl;

private string _apiHost = "https://api.multifactor.ru";

public MultifactorService(string apiKey, string apiSecret, string callbackUrl)
{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
_apiSecret = apiSecret ?? throw new ArgumentNullException(nameof(apiSecret));
_callbackUrl = callbackUrl ?? throw new ArgumentNullException(nameof(callbackUrl));
}

public async Task<string> GetAccessPage(string identityName, IDictionary<string, string> claims = null)
{
if (string.IsNullOrEmpty(identityName)) throw new ArgumentNullException(nameof(identityName));

var request = JsonConvert.SerializeObject(new
{
Identity = identityName, //login пользователя
Callback = new
{
Action = _callbackUrl, //адрес возврата
Target = "_self"
},
Claims = claims //набор заявок
});

var payLoad = Encoding.UTF8.GetBytes(request);

//basic authorization
var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(_apiKey + ":" + _apiSecret));

using var client = new WebClient();
client.Headers.Add("Authorization", "Basic " + authHeader);
client.Headers.Add("Content-Type", "application/json");

var responseData = await client.UploadDataTaskAsync(_apiHost + "/access/requests", "POST", payLoad);
var responseJson = Encoding.ASCII.GetString(responseData);

var response = JsonConvert.DeserializeObject<MultifactorResponse<MultifactorAccessPage>>(responseJson);
return response.Model.Url; //адрес страницы доступа
}

internal class MultifactorResponse<T>
{
public bool Success { get; set; }
public T Model { get; set; }
}

internal class MultifactorAccessPage
{
public string Url { get; set; }
}
}

Сервис клиент для API Мультифактора. В конструктор класса передаются параметры для подключения, которые берутся из конфигурационного файла appsettings.json. В методе GetAccessPage формируется запрос для получения адреса страницы проверки доступа, далее запрашивается API и возвращается результат.

Внедрение зависимостей и настройка сервисов

Отредактируйте файл Startup.cs, добавьте в метод ConfigureServices код для загрузки настроек из файла конфигурации и регистрации клиента для API

//load Multifactor settings
var multifactorSection = Configuration.GetSection("Multifactor");
var apiKey = multifactorSection["ApiKey"];
var apiSecret = multifactorSection["ApiSecret"];
var callbackUrl = multifactorSection["CallbackUrl"];

//register Multifactor service
var multifactorService = new MultifactorService(apiKey, apiSecret, callbackUrl);
services.AddSingleton(multifactorService);

В .net core предусмотрено несколько схем проверки подлинности. В едином приложении можно использовать одну или несколько одновременно, в зависимости от сценариев. Наиболее удобная схема для использования аутентификации на базе JWT токена это Bearer аутентификация. В соответствии с этой схемой, JWT токен передается в HTTP заголовке "Authorization: Bearer", а .NET Core автоматически проверяет подпись, срок действия, прочие атрибуты токена и авторизует пользователя.

Следующий код переключает схему авторизации .net на JWT Bearer

services
.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(apiSecret)), //signature validation key
ValidateIssuer = true,
ValidIssuer = "https://access.multifactor.ru",
ValidateAudience = false,
NameClaimType = ClaimTypes.NameIdentifier
};
});

Здесь необходимо обратить внимание на следующие моменты:

  • ValidateIssuerSigningKey необходимо проверять подпись токена
  • IssuerSigningKey совпадает с ApiSecret из настроек доступа к API
  • ValidateIssuer необходимо проверять эмитента токена
  • NameClaimType указывает из какой заявки брать идентификатор пользователя

Middleware

Добавьте в метод Configure код, который берет токен доступа из куки и перекладывает в заголовок JWT Bearer

app.Use(async (context, next) =>
{
var token = context.Request.Cookies["jwt"];
if (!string.IsNullOrEmpty(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}

await next();
});

И укажите адрес формы входа

//redirect to /account/login when unauthorized
app.UseStatusCodePages(async context => {
var response = context.HttpContext.Response;

if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
response.Redirect("/account/login");
}
});

AccountController

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

Добавьте в него сервис для работы с API Мультифактора

private MultifactorService _multifactorService;

И сделайте запрос на мультифакторную аутентификацию после проверки логина и пароля

[HttpPost("/account/login")]
public async Task<IActionResult> Login([Required]string login, [Required]string password)
{
if (ModelState.IsValid)
{
//ваш identity provider для проверки логина и пароля пользователя
var isValidUser = _identityService.ValidateUser(login, password, out string role);

if (isValidUser)
{
var claims = new Dictionary<string, string> //можно добавить роль и любые другие аттрибуты пользователя в токен, чтоб не запрашивать их из базы данных
{
{ "Role", role }
};

var url = await _multifactorService.GetAccessPage(login, claims);
return RedirectPermanent(url);
}
}

return View();
}

Последний момент это адрес возврата пользователя после успешной аутентификации с токеном доступа

[HttpPost("/account/mfa")]
public IActionResult MultifactorCallback(string accessToken)
{
//сохраняем токен в куки и отправляем пользователя в авторизованную зону
Response.Cookies.Append("jwt", accessToken);
return LocalRedirect("/");
}

Для того, чтоб разлогинить пользователя, сделайте метод Logout, который удаляет куки с токеном

[HttpGet("/account/logout")]
public IActionResult Logout()
{
Response.Cookies.Delete("jwt");
return Redirect("/");
}

Смотрите также: