Поделиться через


Многофакторная проверка подлинности в ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см. версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске смотрите версию .NET 9 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см. статью .NET 9.

Дэмиен Боуден

Просмотр или скачивание примера кода (репозиторий damienbod/AspNetCoreHybridFlowWithApi GitHub)

Многофакторная проверка подлинности (MFA) — это процесс, в котором пользователя запрашивают во время входа в систему, чтобы предоставить дополнительные формы идентификации. Это может быть ввод кода с мобильного телефона, использование ключа FIDO2 или проверка отпечатков пальцев. Если требуется вторая форма проверки подлинности, безопасность улучшается. Дополнительный фактор не может быть легко получен или продублирован кибератакером.

В этой статье рассматриваются следующие области:

  • Что такое MFA и какие потоки MFA рекомендуется
  • Настройка MFA для страниц администрирования с помощью ASP.NET Core Identity
  • Отправьте требование многофакторной аутентификации для входа на сервер OpenID Connect
  • Принуждение клиента OpenID Connect для ASP.NET Core к обязательной многофакторной аутентификации

МФА (многофакторная аутентификация), 2ФА (двухфакторная аутентификация)

Многофакторная проверка подлинности требует минимум двух типов подтверждения личности, например, что-то, что вы знаете, чем вы обладаете, или биометрическая аутентификация пользователя.

Двухфакторная проверка подлинности (2FA) похожа на подмножество MFA, но разница в том, что MFA может требовать два или более факторов для подтверждения удостоверения.

2FA поддерживается по умолчанию при использовании ASP.NET Core Identity. Чтобы включить или отключить 2FA для определенного пользователя, задайте IdentityUser<TKey>.TwoFactorEnabled свойство. Пользовательский интерфейс по умолчанию ASP.NET Core Identity включает страницы для настройки 2FA.

MFA TOTP (алгоритм одноразового пароля на основе времени)

MFA с помощью TOTP поддерживается по умолчанию при использовании ASP.NET Core Identity. Этот подход можно использовать вместе с любым приложением для проверки подлинности, в том числе:

  • Microsoft Authenticator
  • Google Authenticator

Сведения о реализации см. в разделе "Включение создания QR-кода" для приложений проверки подлинности TOTP в ASP.NET Core.

Чтобы отключить поддержку MFA TOTP, настройте проверку подлинности, используя AddIdentity вместо AddDefaultIdentity. AddDefaultIdentity вызывает AddDefaultTokenProviders внутри себя, регистрируя нескольких поставщиков токенов, включая одного для MFA TOTP. Чтобы зарегистрировать только необходимых поставщиков токенов, вызовите AddTokenProvider для каждого из них. Дополнительные сведения о доступных поставщиках маркеров см. в справочных источниках AddDefaultTokenProviders, (репозитории GitHubdotnet/aspnetcore).

Примечание.

Документационные ссылки на референсный источник .NET обычно ведут к основной ветви репозитория, которая соответствует текущей разработке для следующего выпуска .NET. Чтобы выбрать тег для конкретного релиза, используйте раскрывающийся список Переключение ветвей или тегов. Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Ключи доступа MFA/ FIDO2 или без пароля

passkeys/FIDO2 в настоящее время:

  • Самый безопасный способ достижения MFA.
  • Многофакторная проверка подлинности, которая защищает от фишинговых атак. (А также проверка подлинности на основе сертификатов и Windows для бизнеса)

В настоящее время ASP.NET Core не поддерживает ключи доступа или FIDO2 напрямую. Ключи доступа/FIDO2 можно использовать для потоков MFA или без пароля.

Идентификатор Microsoft Entra предоставляет поддержку потоков passkeys/FIDO2 и без пароля. Дополнительные сведения см. в разделе "Параметры проверки подлинности без пароля".

Другие формы MFA без паролей могут не защищать от фишинга.

MFA SMS

Многофакторная проверка подлинности с помощью SMS значительно увеличивает безопасность по сравнению с проверкой подлинности паролем (один фактор). Однако использование SMS в качестве второго фактора больше не рекомендуется. Для этого типа реализации существует слишком много известных векторов атак.

Рекомендации NIST

Настройка MFA для страниц администрирования с помощью ASP.NET Core Identity

MFA может применяться для пользователей, чтобы получить доступ к конфиденциальным страницам в приложении ASP.NET Core Identity. Это может быть полезно для приложений, где существуют различные уровни доступа для различных учетных записей. Например, пользователи могут просматривать данные профиля с помощью имени входа в систему паролей, но администратору потребуется использовать MFA для доступа к административным страницам.

Расширение входа с использованием запроса MFA

Демонстрационный код создается с использованием ASP.NET Core и страниц Identity и Razor. Метод AddIdentity используется вместо AddDefaultIdentity одного, поэтому реализацию IUserClaimsPrincipalFactory можно использовать для добавления утверждений в удостоверение после успешного входа.

Предупреждение

В этой статье показано использование строк подключения. С локальной базой данных пользователю не нужно проходить проверку подлинности, а в продакшене строка подключения иногда включает пароль для проверки подлинности. Учетные данные владельца ресурса (ROPC) — это риск безопасности, который следует избежать в рабочих базах данных. Рабочие приложения должны использовать самый безопасный поток проверки подлинности. Дополнительные сведения о проверке подлинности для приложений, развернутых в тестовых или рабочих средах, см. в разделе "Безопасные потоки проверки подлинности".

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(
        Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
		options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddSingleton<IEmailSender, EmailSender>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, 
    AdditionalUserClaimsPrincipalFactory>();

builder.Services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled", x => x.RequireClaim("amr", "mfa")));

builder.Services.AddRazorPages();

Класс AdditionalUserClaimsPrincipalFactory добавляет amr утверждение к утверждениям пользователя только после успешного входа. Значение утверждения считывается из базы данных. Утверждение добавляется здесь, так как пользователь должен получить доступ только к более защищенному представлению, если удостоверение вошедло в систему с помощью MFA. Если представление базы данных считывается из базы данных напрямую вместо использования утверждения, то можно получить доступ к представлению без MFA сразу после активации MFA.

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityStandaloneMfa
{
    public class AdditionalUserClaimsPrincipalFactory : 
        UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
    {
        public AdditionalUserClaimsPrincipalFactory( 
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager, 
            IOptions<IdentityOptions> optionsAccessor) 
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = (ClaimsIdentity)principal.Identity;

            var claims = new List<Claim>();

            if (user.TwoFactorEnabled)
            {
                claims.Add(new Claim("amr", "mfa"));
            }
            else
            {
                claims.Add(new Claim("amr", "pwd"));
            }

            identity.AddClaims(claims);
            return principal;
        }
    }
}

Identity Так как настройка службы изменилась в Startup классе, макеты Identity необходимо обновить. Создание шаблонов Identity страниц в приложение. Определите макет в Identity/Account/Manage/_Layout.cshtml файле.

@{
    Layout = "/Pages/Shared/_Layout.cshtml";
}

Кроме того, назначьте макет для всех страниц управления из страниц Identity.

@{
    Layout = "_Layout.cshtml";
}

Проверка требования MFA на странице администрирования

Страница администрирования Razor проверяет, выполнил ли пользователь вход с помощью MFA. В методе OnGet идентичность используется для доступа к утверждениям пользователя. Утверждение amr проверяется для значения mfa. Если у удостоверения отсутствует это требование или оно равно false, страница перенаправляется на страницу "Включить MFA". Это возможно, так как пользователь уже вошел в систему, но без MFA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityStandaloneMfa
{
    public class AdminModel : PageModel
    {
        public IActionResult OnGet()
        {
            var claimTwoFactorEnabled = 
                User.Claims.FirstOrDefault(t => t.Type == "amr");

            if (claimTwoFactorEnabled != null && 
                "mfa".Equals(claimTwoFactorEnabled.Value))
            {
                // You logged in with MFA, do the administrative stuff
            }
            else
            {
                return Redirect(
                    "/Identity/Account/Manage/TwoFactorAuthentication");
            }

            return Page();
        }
    }
}

Логика пользовательского интерфейса для смены сведений о входе пользователя

Политика авторизации была добавлена при запуске. Политика требует amr запрос с значением mfa.

services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled",
        x => x.RequireClaim("amr", "mfa")));

Затем эту политику можно использовать в _Layout представлении для отображения или скрытия меню администрирования с предупреждением:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IAuthorizationService AuthorizationService

Если удостоверение вошло в систему с помощью MFA, меню администратора отображается без предупреждающего сообщения. Когда пользователь выполнил вход без MFA, отображается меню администратора (не включено) вместе с подсказкой, которая сообщает пользователю (объясняя предупреждение).

@if (SignInManager.IsSignedIn(User))
{
    @if ((AuthorizationService.AuthorizeAsync(User, "TwoFactorEnabled")).Result.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin" 
               id="tooltip-demo"  
               data-toggle="tooltip" 
               data-placement="bottom" 
               title="MFA is NOT enabled. This is required for the Admin Page. If you have activated MFA, then logout, login again.">
                Admin (Not Enabled)
            </a>
        </li>
    }
}

Если пользователь входит в систему без MFA, отображается предупреждение:

Многофакторная аутентификация администратора

Пользователь перенаправляется в режим включения MFA при нажатии ссылки администратора :

Администратор активирует проверку подлинности MFA

Отправка требования к входу MFA на сервер OpenID Connect

Этот acr_values параметр можно использовать для передачи mfa требуемого значения от клиента серверу в запросе проверки подлинности.

Примечание.

Этот acr_values параметр необходимо обрабатывать на сервере OpenID Connect для работы.

Клиент OpenID Connect ASP.NET Core

Клиентское приложение OpenID Connect для ASP.NET Core Razor Pages использует AddOpenIdConnect метод для входа на сервер OpenID Connect. Параметр acr_values задается со значением mfa и отправляется с помощью запроса проверки подлинности. Этот OpenIdConnectEvents параметр используется для добавления.

Рекомендуемые значения параметров см. в разделе "Справочные acr_values значения метода проверки подлинности".

build.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "<OpenID Connect server URL>";
	options.RequireHttpsMetadata = true;
	options.ClientId = "<OpenID Connect client ID>";
	options.ClientSecret = "<>";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
	options.AdditionalAuthorizationParameters.Add("acr_values", "mfa");
});

Пример сервера Duende IdentityServer OpenID Connect с ASP.NET Core Identity

На сервере OpenID Connect, который реализуется с помощью ASP.NET Core Identity с Razor Pages, создается новая страница с именем ErrorEnable2FA.cshtml . Представление :

  • Отображается, если Identity исходит от приложения, требующего многофакторной аутентификации, но пользователь не активировал это в Identity.
  • Сообщает пользователю и добавляет ссылку для активации.
@{
    ViewData["Title"] = "ErrorEnable2FA";
}

<h1>The client application requires you to have MFA enabled. Enable this, try login again.</h1>

<br />

You can enable MFA to login here:

<br />

<a href="~/Identity/Account/Manage/TwoFactorAuthentication">Enable MFA</a>

В методе LoginIIdentityServerInteractionService реализация _interaction интерфейса используется для доступа к параметрам запроса OpenID Connect. Параметр acr_values доступен через свойство AcrValues. Поскольку клиент отправил это с использованием mfa, это можно затем проверить.

Если требуется MFA, и у пользователя в ASP.NET Core Identity включено MFA, то процесс входа продолжается. Если у пользователя нет многофакторной проверки подлинности, пользователь перенаправляется в пользовательское представление ErrorEnable2FA.cshtml. Затем ASP.NET Core Identity входит в систему пользователя.

Fido2Store используется для проверки того, активировал ли пользователь MFA с помощью пользовательского поставщика токенов FIDO2.

public async Task<IActionResult> OnPost()
{
	// check if we are in the context of an authorization request
	var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);

	var requires2Fa = context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

	var user = await _userManager.FindByNameAsync(Input.Username);
	if (user != null && !user.TwoFactorEnabled && requires2Fa)
	{
		return RedirectToPage("/Home/ErrorEnable2FA/Index");
	}

	// code omitted for brevity

	if (ModelState.IsValid)
	{
		var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberLogin, lockoutOnFailure: true);
		if (result.Succeeded)
		{
			// code omitted for brevity
		}
		if (result.RequiresTwoFactor)
		{
			var fido2ItemExistsForUser = await _fido2Store.GetCredentialsByUserNameAsync(user.UserName);
			if (fido2ItemExistsForUser.Count > 0)
			{
				return RedirectToPage("/Account/LoginFido2Mfa", new { area = "Identity", Input.ReturnUrl, Input.RememberLogin });
			}

			return RedirectToPage("/Account/LoginWith2fa", new { area = "Identity", Input.ReturnUrl, RememberMe = Input.RememberLogin });
		}

		await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId: context?.Client.ClientId));
		ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
	}

	// something went wrong, show form with error
	await BuildModelAsync(Input.ReturnUrl);
	return Page();
}

Если пользователь уже вошел в систему, клиентское приложение:

  • По-прежнему подтверждает amr утверждение.
  • Может настроить многофакторную аутентификацию с помощью ссылки на представление ASP.NET Core Identity.

изображение acr_values-1

Обязать клиента OpenID Connect для ASP.NET Core использовать многофакторную аутентификацию (MFA)

В этом примере показано, как приложение ASP.NET Core Razor Page, использующее OpenID Connect для входа, может требовать, чтобы пользователи прошли проверку подлинности с помощью MFA.

Для проверки требования MFA создается IAuthorizationRequirement требование. Это будет добавлено на страницы с помощью политики, требующей многофакторной аутентификации.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Реализуется AuthorizationHandler, который будет использовать запрос amr и проверять значение mfa. Возвращаемое значение в контексте успешной проверки подлинности может принимать множество различных значений, как это определено в спецификации значений справочника методов проверки подлинности .

Возвращаемое значение зависит от способа проверки подлинности удостоверения и реализации сервера OpenID Connect.

AuthorizationHandler использует RequireMfa требование и проверяет amr утверждение. Сервер OpenID Connect можно реализовать с помощью сервера Duende Identity с ASP.NET Core Identity. Когда пользователь входит в систему с помощью TOTP, amr заявление возвращается со значением двухфакторной аутентификации (MFA). Если используется другая реализация сервера OpenID Connect или другой тип MFA, amr утверждение будет или может иметь другое значение. Код должен быть расширен, чтобы принять это.

public class RequireMfaHandler : AuthorizationHandler<RequireMfa>
{
	protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		RequireMfa requirement)
	{
		if (context == null)
			throw new ArgumentNullException(nameof(context));
		if (requirement == null)
			throw new ArgumentNullException(nameof(requirement));

		var amrClaim =
			context.User.Claims.FirstOrDefault(t => t.Type == "amr");

		if (amrClaim != null && amrClaim.Value == Amr.Mfa)
		{
			context.Succeed(requirement);
		}

		return Task.CompletedTask;
	}
}

В файле программы метод AddOpenIdConnect используется в качестве схемы аутентификации по умолчанию. Обработчик авторизации, используемый для проверки amr утверждения, добавляется в контейнер Inversion of Control. Затем создается политика, которая добавляет RequireMfa требование.

builder.Services.ConfigureApplicationCookie(options =>
        options.Cookie.SecurePolicy =
            CookieSecurePolicy.Always);

builder.Services.AddSingleton<IAuthorizationHandler, RequireMfaHandler>();

builder.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "https://localhost:44352";
	options.RequireHttpsMetadata = true;
	options.ClientId = "AspNetCoreRequireMfaOidc";
	options.ClientSecret = "AspNetCoreRequireMfaOidcSecret";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
});

builder.Services.AddAuthorization(options =>
{
	options.AddPolicy("RequireMfa", policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements.Add(new RequireMfa());
	});
});

builder.Services.AddRazorPages();

Затем эта политика используется на Razor странице по мере необходимости. Политику можно добавить глобально для всего приложения.

[Authorize(Policy= "RequireMfa")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Если пользователь проходит проверку подлинности без MFA, утверждение, вероятно, amr будет иметь pwd значение. Запрос не будет авторизован для доступа к странице. Используя значения по умолчанию, пользователь будет перенаправлен на страницу Account/AccessDenied . Это поведение можно изменить или реализовать собственную пользовательскую логику здесь. В этом примере добавляется ссылка, чтобы допустимый пользователь смог настроить MFA для своей учетной записи.

@page
@model AspNetCoreRequireMfaOidc.AccessDeniedModel
@{
    ViewData["Title"] = "AccessDenied";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

<h1>AccessDenied</h1>

You require MFA to login here

<a href="https://localhost:44352/Manage/TwoFactorAuthentication">Enable MFA</a>

Теперь только пользователи, прошедшие проверку подлинности с помощью MFA, могут получить доступ к странице или веб-сайту. Если используются разные типы MFA или если 2FA допустимо, amr утверждение будет иметь разные значения и должно быть обработано правильно. Разные серверы OpenID Connect также возвращают разные значения для этого утверждения и могут не соответствовать спецификации ссылочных значений метода проверки подлинности.

При входе без MFA (например, с помощью пароля):

  • amr Имеет pwd значение:

    amr имеет значение pwd

  • Доступ запрещен:

    Доступ запрещен

В качестве альтернативы, авторизуйтесь с помощью OTP: Identity

Вход с помощью OTP Identity

Настройка OIDC и параметра OAuth

Параметр обработчиков AdditionalAuthorizationParameters проверки подлинности OAuth и OIDC позволяет настраивать параметры сообщения авторизации, которые обычно включаются в строку запроса перенаправления:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.AdditionalAuthorizationParameters.Add("prompt", "login");
    options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});

Дополнительные ресурсы

Дэмиен Боуден

Просмотр или скачивание примера кода (репозиторий damienbod/AspNetCoreHybridFlowWithApi GitHub)

Многофакторная проверка подлинности (MFA) — это процесс, в котором пользователь запрашивается во время события входа для дополнительных форм идентификации. Это может быть ввод кода с мобильного телефона, использование ключа FIDO2 или проверка отпечатков пальцев. Если требуется вторая форма проверки подлинности, безопасность улучшается. Дополнительный фактор не легко доступен или копируется кибератакующим.

В этой статье рассматриваются следующие области:

  • Что такое MFA и какие потоки MFA рекомендуется
  • Настройка MFA для страниц администрирования с помощью ASP.NET Core Identity
  • Отправка требования на вход с помощью MFA на сервер OpenID Connect
  • Принудительное требование многофакторной аутентификации клиентом OpenID Connect для ASP.NET Core

многофакторная аутентификация (MFA), двухфакторная аутентификация (2FA)

Многофакторная проверка подлинности требует минимум двух типов подтверждения личности, например, что-то, что вы знаете, чем вы обладаете, или биометрическая аутентификация пользователя.

Двухфакторная проверка подлинности (2FA) похожа на подмножество MFA, но разница в том, что MFA может требовать два или более факторов для подтверждения удостоверения.

2FA поддерживается по умолчанию при использовании ASP.NET Core Identity. Чтобы включить или отключить 2FA для определенного пользователя, задайте IdentityUser<TKey>.TwoFactorEnabled свойство. Пользовательский интерфейс по умолчанию ASP.NET Core Identity включает страницы для настройки 2FA.

MFA TOTP (алгоритм одноразового пароля на основе времени)

MFA с помощью TOTP поддерживается по умолчанию при использовании ASP.NET Core Identity. Этот подход можно использовать вместе с любым приложением для проверки подлинности, в том числе:

  • Microsoft Authenticator
  • Google Authenticator

Сведения о реализации см. в разделе "Включение создания QR-кода" для приложений проверки подлинности TOTP в ASP.NET Core.

Чтобы отключить поддержку MFA TOTP, настройте проверку подлинности, используя AddIdentity вместо AddDefaultIdentity. AddDefaultIdentity вызывает AddDefaultTokenProviders внутренне, что регистрирует несколько поставщиков токенов, включая один для MFA TOTP. Чтобы зарегистрировать только конкретных провайдеров токенов, вызовите AddTokenProvider для каждого необходимого провайдера. Дополнительные сведения о доступных поставщиках токенов см. в разделе AddDefaultTokenProviders на GitHub.

Ключи доступа MFA/ FIDO2 или без пароля

passkeys/FIDO2 в настоящее время:

  • Самый безопасный способ достижения MFA.
  • Многофакторная проверка подлинности, которая защищает от фишинговых атак. (А также проверка подлинности на основе сертификатов и Windows для бизнеса)

В настоящее время ASP.NET Core не поддерживает ключи доступа или FIDO2 напрямую. Ключи доступа/FIDO2 можно использовать для потоков MFA или без пароля.

Идентификатор Microsoft Entra поддерживает безпарольные потоки и passkeys/FIDO2. Дополнительные сведения см. в разделе "Параметры проверки подлинности без пароля".

Другие формы многофакторной аутентификации без паролей не защищают или могут не защищать от фишинга.

SMS для многофакторной аутентификации

Многофакторная проверка подлинности с помощью SMS значительно увеличивает безопасность по сравнению с проверкой подлинности паролем (один фактор). Однако использование SMS в качестве второго фактора больше не рекомендуется. Для этого типа реализации существует слишком много известных векторов атак.

Рекомендации NIST

Настройка MFA для страниц администрирования с помощью ASP.NET Core Identity

MFA может потребоваться пользователям для доступа к конфиденциальным страницам в приложении ASP.NET Core Identity. Это может быть полезно для приложений, где существуют различные уровни доступа для разных идентичностей. Например, пользователи могут просматривать данные профиля с помощью имени входа в систему паролей, но администратору потребуется использовать MFA для доступа к административным страницам.

Расширьте вход с помощью подтверждения MFA

Демонстрационный код настраивается с помощью ASP.NET Core и страниц Identity и Razor. Метод AddIdentity используется вместо AddDefaultIdentity одного, поэтому реализацию IUserClaimsPrincipalFactory можно использовать для добавления утверждений в удостоверение после успешного входа.

Предупреждение

В этой статье показано использование строк подключения. С локальной базой данных пользователю не нужно проходить аутентификацию, но в рабочей среде строка подключения иногда включает пароль для аутентификации. Учетные данные владельца ресурса (ROPC) — это риск безопасности, который следует избежать в рабочих базах данных. Рабочие приложения должны использовать самый безопасный поток проверки подлинности. Дополнительные сведения о проверке подлинности для приложений, развернутых в тестовых или рабочих средах, см. в разделе "Безопасные потоки проверки подлинности".

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(
        Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
		options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddSingleton<IEmailSender, EmailSender>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, 
    AdditionalUserClaimsPrincipalFactory>();

builder.Services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled", x => x.RequireClaim("amr", "mfa")));

builder.Services.AddRazorPages();

Класс AdditionalUserClaimsPrincipalFactory добавляет amr утверждение к утверждениям пользователя только после успешного входа. Значение утверждения считывается из базы данных. Утверждение добавляется здесь, так как пользователь должен получить доступ только к более защищенному представлению, если учетная запись выполнила вход в систему с помощью многофакторной аутентификации. Если представление в базе данных считывается непосредственно из базы данных, а не через запрос, то непосредственно после активации MFA можно получить доступ к представлению без использования MFA.

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityStandaloneMfa
{
    public class AdditionalUserClaimsPrincipalFactory : 
        UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
    {
        public AdditionalUserClaimsPrincipalFactory( 
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager, 
            IOptions<IdentityOptions> optionsAccessor) 
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = (ClaimsIdentity)principal.Identity;

            var claims = new List<Claim>();

            if (user.TwoFactorEnabled)
            {
                claims.Add(new Claim("amr", "mfa"));
            }
            else
            {
                claims.Add(new Claim("amr", "pwd"));
            }

            identity.AddClaims(claims);
            return principal;
        }
    }
}

Identity Так как настройка службы изменилась в Startup классе, макеты Identity необходимо обновить. Создание шаблонов Identity страниц в приложение. Определите макет в Identity/Account/Manage/_Layout.cshtml файле.

@{
    Layout = "/Pages/Shared/_Layout.cshtml";
}

Кроме того, назначьте макет для всех страниц управления со Identity страниц:

@{
    Layout = "_Layout.cshtml";
}

Проверка требования MFA на странице администрирования

Страница администрирования Razor проверяет, выполнил ли пользователь вход с помощью MFA. В методе OnGet идентичность используется для доступа к утверждениям пользователя. Утверждение amr проверяется для значения mfa. Если у удостоверения отсутствует это требование или оно равно false, страница перенаправляется на страницу "Включить MFA". Это возможно, так как пользователь уже вошел в систему, но без MFA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityStandaloneMfa
{
    public class AdminModel : PageModel
    {
        public IActionResult OnGet()
        {
            var claimTwoFactorEnabled = 
                User.Claims.FirstOrDefault(t => t.Type == "amr");

            if (claimTwoFactorEnabled != null && 
                "mfa".Equals(claimTwoFactorEnabled.Value))
            {
                // You logged in with MFA, do the administrative stuff
            }
            else
            {
                return Redirect(
                    "/Identity/Account/Manage/TwoFactorAuthentication");
            }

            return Page();
        }
    }
}

Логика интерфейса для изменения данных для входа пользователя

Политика авторизации была добавлена при запуске. Для политики требуется amr утверждение со значением mfa.

services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled",
        x => x.RequireClaim("amr", "mfa")));

Затем эту политику можно использовать в _Layout представлении для отображения или скрытия меню Администратора с предупреждением:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IAuthorizationService AuthorizationService

Если удостоверение вошло в систему с помощью MFA, меню администратора отображается без предупреждающего сообщения. Когда пользователь выполнил вход без MFA, отображается меню администратора (не включено) вместе с подсказкой, которая сообщает пользователю (объясняя предупреждение).

@if (SignInManager.IsSignedIn(User))
{
    @if ((AuthorizationService.AuthorizeAsync(User, "TwoFactorEnabled")).Result.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin" 
               id="tooltip-demo"  
               data-toggle="tooltip" 
               data-placement="bottom" 
               title="MFA is NOT enabled. This is required for the Admin Page. If you have activated MFA, then logout, login again.">
                Admin (Not Enabled)
            </a>
        </li>
    }
}

Если пользователь входит в систему без MFA, отображается предупреждение:

Аутентификация администратора MFA

Пользователь перенаправляется в режим включения MFA при нажатии ссылки администратора :

Администратор активирует проверку подлинности MFA

Отправка требования к входу MFA на сервер OpenID Connect

Этот acr_values параметр можно использовать для передачи mfa требуемого значения от клиента серверу в запросе проверки подлинности.

Примечание.

Этот acr_values параметр необходимо обрабатывать на сервере OpenID Connect для работы.

Клиент OpenID Connect ASP.NET Core

Клиентское приложение OpenID Connect для ASP.NET Core Razor Pages использует AddOpenIdConnect метод для входа на сервер OpenID Connect. Параметр acr_values задается со значением mfa и отправляется с помощью запроса проверки подлинности. OpenIdConnectEvents используется для добавления этого.

Рекомендуемые значения параметров см. в разделе "Справочные acr_values значения метода проверки подлинности".

build.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "<OpenID Connect server URL>";
	options.RequireHttpsMetadata = true;
	options.ClientId = "<OpenID Connect client ID>";
	options.ClientSecret = "<>";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
	options.Events = new OpenIdConnectEvents
	{
		OnRedirectToIdentityProvider = context =>
		{
			context.ProtocolMessage.SetParameter("acr_values", "mfa");
			return Task.FromResult(0);
		}
	};
});

Пример сервера Duende IdentityServer OpenID Connect с ASP.NET Core Identity

На сервере OpenID Connect, который реализуется с помощью ASP.NET Core Identity с Razor Pages, создается новая страница с именем ErrorEnable2FA.cshtml . Вид:

  • Отображается, если Identity поступает из приложения, которое требует многофакторной аутентификации, но пользователь не активировал её в Identity.
  • Сообщает пользователю и добавляет ссылку для активации.
@{
    ViewData["Title"] = "ErrorEnable2FA";
}

<h1>The client application requires you to have MFA enabled. Enable this, try login again.</h1>

<br />

You can enable MFA to login here:

<br />

<a href="~/Identity/Account/Manage/TwoFactorAuthentication">Enable MFA</a>

В методе Login используется реализация интерфейса _interactionIIdentityServerInteractionService для доступа к параметрам запроса OpenID Connect. К параметру acr_values обращаются через свойство AcrValues. Поскольку клиент отправил это с установленным значением mfa, это можно проверить.

Если требуется MFA, и у пользователя в ASP.NET Core Identity включено MFA, то вход продолжается. Если у пользователя нет многофакторной проверки подлинности, пользователь перенаправляется в пользовательское представление ErrorEnable2FA.cshtml. Затем ASP.NET Core Identity входит в систему пользователя.

Fido2Store используется для проверки того, активировал ли пользователь MFA с помощью пользовательского поставщика токенов FIDO2.

public async Task<IActionResult> OnPost()
{
	// check if we are in the context of an authorization request
	var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);

	var requires2Fa = context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

	var user = await _userManager.FindByNameAsync(Input.Username);
	if (user != null && !user.TwoFactorEnabled && requires2Fa)
	{
		return RedirectToPage("/Home/ErrorEnable2FA/Index");
	}

	// code omitted for brevity

	if (ModelState.IsValid)
	{
		var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberLogin, lockoutOnFailure: true);
		if (result.Succeeded)
		{
			// code omitted for brevity
		}
		if (result.RequiresTwoFactor)
		{
			var fido2ItemExistsForUser = await _fido2Store.GetCredentialsByUserNameAsync(user.UserName);
			if (fido2ItemExistsForUser.Count > 0)
			{
				return RedirectToPage("/Account/LoginFido2Mfa", new { area = "Identity", Input.ReturnUrl, Input.RememberLogin });
			}

			return RedirectToPage("/Account/LoginWith2fa", new { area = "Identity", Input.ReturnUrl, RememberMe = Input.RememberLogin });
		}

		await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId: context?.Client.ClientId));
		ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
	}

	// something went wrong, show form with error
	await BuildModelAsync(Input.ReturnUrl);
	return Page();
}

Если пользователь уже вошел в систему, клиентское приложение:

  • По-прежнему подтверждает amr утверждение.
  • Может настроить MFA со ссылкой на представление ASP.NET Core Identity .

изображение acr_values-1

Принудительное требование многофакторной аутентификации клиентом OpenID Connect в ASP.NET Core

В этом примере показано, как приложение ASP.NET Core Razor Page, использующее OpenID Connect для входа, может требовать, чтобы пользователи прошли проверку подлинности с помощью MFA.

Для проверки требования MFA создается новое требование IAuthorizationRequirement. Это будет добавлено на страницы с помощью политики, требующей многофакторной аутентификации.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Реализуется AuthorizationHandler, который будет использовать amr заявление и проверять значение mfa. При успешной аутентификации amr возвращается в id_token и может иметь множество различных значений, как это определено в спецификации ссылочных значений методов аутентификации.

Возвращаемое значение зависит от способа проверки подлинности удостоверения и реализации сервера OpenID Connect.

AuthorizationHandler использует RequireMfa требование и проверяет amr утверждение. Сервер OpenID Connect можно реализовать с помощью сервера Duende Identity с ASP.NET Core Identity. Когда пользователь выполняет вход в систему с использованием TOTP, amr запрос возвращается со значением многофакторной аутентификации (MFA). Если используется другая реализация сервера OpenID Connect или другой тип MFA, amr утверждение будет или может иметь другое значение. Код должен быть расширен, чтобы также принять это.

public class RequireMfaHandler : AuthorizationHandler<RequireMfa>
{
	protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		RequireMfa requirement)
	{
		if (context == null)
			throw new ArgumentNullException(nameof(context));
		if (requirement == null)
			throw new ArgumentNullException(nameof(requirement));

		var amrClaim =
			context.User.Claims.FirstOrDefault(t => t.Type == "amr");

		if (amrClaim != null && amrClaim.Value == Amr.Mfa)
		{
			context.Succeed(requirement);
		}

		return Task.CompletedTask;
	}
}

В программном файле метод AddOpenIdConnect используется в качестве схемы проверки по умолчанию. Обработчик авторизации, используемый для проверки amr утверждения, добавляется в контейнер Inversion of Control. Затем создается политика, которая добавляет RequireMfa требование.

builder.Services.ConfigureApplicationCookie(options =>
        options.Cookie.SecurePolicy =
            CookieSecurePolicy.Always);

builder.Services.AddSingleton<IAuthorizationHandler, RequireMfaHandler>();

builder.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "https://localhost:44352";
	options.RequireHttpsMetadata = true;
	options.ClientId = "AspNetCoreRequireMfaOidc";
	options.ClientSecret = "AspNetCoreRequireMfaOidcSecret";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
});

builder.Services.AddAuthorization(options =>
{
	options.AddPolicy("RequireMfa", policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements.Add(new RequireMfa());
	});
});

builder.Services.AddRazorPages();

Затем эта политика используется на Razor странице по мере необходимости. Политику можно добавить глобально для всего приложения.

[Authorize(Policy= "RequireMfa")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Если пользователь проходит проверку подлинности без MFA, утверждение, скорее всего, будет иметь значение pwd. Запрос не будет авторизован для доступа к странице. Используя значения по умолчанию, пользователь будет перенаправлен на страницу Account/AccessDenied . Это поведение можно изменить или реализовать собственную пользовательскую логику здесь. В этом примере добавляется ссылка, чтобы допустимый пользователь смог настроить MFA для своей учетной записи.

@page
@model AspNetCoreRequireMfaOidc.AccessDeniedModel
@{
    ViewData["Title"] = "AccessDenied";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

<h1>AccessDenied</h1>

You require MFA to login here

<a href="https://localhost:44352/Manage/TwoFactorAuthentication">Enable MFA</a>

Теперь только пользователи, прошедшие проверку подлинности с помощью MFA, могут получить доступ к странице или веб-сайту. Если используются различные типы MFA или если 2FA подходит, amr утверждение будет иметь разные значения и должно быть обработано правильно. Разные серверы OpenID Connect также возвращают разные значения для этого утверждения и могут не соответствовать спецификации ссылочных значений метода проверки подлинности.

При входе без MFA (например, с помощью пароля):

  • amr имеет следующее pwd значение:

    amr имеет значение pwd

  • Доступ запрещен:

    Доступ запрещен

Например, также можно войти в систему с помощью OTP: Identity

Вход с помощью OTP Identity

Дополнительные ресурсы

Дэмиен Боуден

Просмотр или скачивание примера кода (репозиторий damienbod/AspNetCoreHybridFlowWithApi GitHub)

Многофакторная проверка подлинности (MFA) — это процесс, при котором пользователя просят предоставить дополнительные формы идентификации при входе в систему. Это может быть ввод кода с мобильного телефона, использование ключа FIDO2 или проверка отпечатков пальцев. Если требуется вторая форма проверки подлинности, безопасность улучшается. Дополнительный фактор сложно получить или воспроизвести кибератакером.

В этой статье рассматриваются следующие области:

  • Что такое MFA и какие потоки MFA рекомендуется
  • Настройка MFA для страниц администрирования с помощью ASP.NET Core Identity
  • Отправка требования многофакторной аутентификации (MFA) на сервер OpenID Connect
  • Обязать клиента OpenID Connect для ASP.NET Core использовать многофакторную аутентификацию.

Аутентификация с использованием нескольких факторов (MFA), Двухфакторная аутентификация (2FA)

Многофакторная проверка подлинности требует минимум двух типов подтверждения личности, например, что-то, что вы знаете, чем вы обладаете, или биометрическая аутентификация пользователя.

Двухфакторная проверка подлинности (2FA) похожа на подмножество MFA, но разница в том, что MFA может требовать два или более факторов для подтверждения удостоверения.

MFA TOTP (алгоритм одноразового пароля на основе времени)

MFA с помощью TOTP — это поддерживаемая реализация с помощью ASP.NET Core Identity. Это можно использовать вместе с любым соответствующим приложением проверки подлинности, включая:

  • Приложение Microsoft Authenticator
  • Приложение Google Authenticator

Дополнительные сведения о реализации см. по следующей ссылке:

Включение создания QR-кодов для приложений проверки подлинности TOTP в ASP.NET Core

Ключи доступа MFA/ FIDO2 или без пароля

passkeys/FIDO2 в настоящее время:

  • Самый безопасный способ достижения MFA.
  • Многофакторная проверка подлинности, которая защищает от фишинговых атак. (А также проверка подлинности на основе сертификатов и Windows для бизнеса)

В настоящее время ASP.NET Core не поддерживает ключи доступа или FIDO2 напрямую. Ключи доступа/FIDO2 можно использовать для потоков MFA или без пароля.

Идентификатор Microsoft Entra предоставляет поддержку ключей доступа/FIDO2 и безпарольных потоков. Дополнительные сведения см. в разделе "Параметры проверки подлинности без пароля".

Другие формы многофакторной аутентификации без пароля не защищают или могут не защищать от фишинга.

Многофакторная аутентификация (MFA) SMS

Многофакторная проверка подлинности с помощью SMS значительно увеличивает безопасность по сравнению с проверкой подлинности паролем (один фактор). Однако использование SMS в качестве второго фактора больше не рекомендуется. Для этого типа реализации существует слишком много известных векторов атак.

Рекомендации NIST

Настройка MFA для страниц администрирования с помощью ASP.NET Core Identity

MFA может требоваться, чтобы пользователи могли получить доступ к конфиденциальным страницам в приложении ASP.NET Core Identity. Это может быть полезно для приложений, где существуют различные уровни доступа для различных учетных записей. Например, пользователи могут просматривать данные профиля с помощью имени входа в систему паролей, но администратору потребуется использовать MFA для доступа к административным страницам.

Продлите процесс входа с использованием многофакторной аутентификации.

Демонстрационный код настроен с использованием ASP.NET Core и IdentityRazor Pages. Метод AddIdentity используется вместо AddDefaultIdentity одного, поэтому реализацию IUserClaimsPrincipalFactory можно использовать для добавления утверждений в удостоверение после успешного входа.

Предупреждение

В этой статье показано использование строк подключения. С локальной базой данных пользователю не требуется аутентификация, но в рабочей среде строки подключения иногда включают пароль для аутентификации. Учетные данные владельца ресурса (ROPC) — это риск безопасности, который следует избежать в рабочих базах данных. Рабочие приложения должны использовать самый безопасный поток проверки подлинности. Дополнительные сведения о проверке подлинности для приложений, развернутых в тестовых или рабочих средах, см. в разделе "Безопасные потоки проверки подлинности".

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<IdentityUser, IdentityRole>(
            options => options.SignIn.RequireConfirmedAccount = false)
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddSingleton<IEmailSender, EmailSender>();
    services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, 
        AdditionalUserClaimsPrincipalFactory>();

    services.AddAuthorization(options =>
        options.AddPolicy("TwoFactorEnabled",
            x => x.RequireClaim("amr", "mfa")));

    services.AddRazorPages();
}

Класс AdditionalUserClaimsPrincipalFactory добавляет amr утверждение к утверждениям пользователя только после успешного входа. Значение утверждения считывается из базы данных. Утверждение добавляется здесь, так как пользователь должен получить доступ только к более защищённому режиму просмотра, если цифровая идентификация вошла в систему с помощью MFA. Если представление базы данных считывается из базы данных непосредственно вместо использования утверждения, то можно получить доступ к представлению без MFA непосредственно после активации MFA.

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityStandaloneMfa
{
    public class AdditionalUserClaimsPrincipalFactory : 
        UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
    {
        public AdditionalUserClaimsPrincipalFactory( 
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager, 
            IOptions<IdentityOptions> optionsAccessor) 
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = (ClaimsIdentity)principal.Identity;

            var claims = new List<Claim>();

            if (user.TwoFactorEnabled)
            {
                claims.Add(new Claim("amr", "mfa"));
            }
            else
            {
                claims.Add(new Claim("amr", "pwd"));
            }

            identity.AddClaims(claims);
            return principal;
        }
    }
}

Identity Так как настройка службы изменилась в Startup классе, макеты Identity необходимо обновить. Создание шаблонов Identity страниц в приложение. Определите макет в Identity/Account/Manage/_Layout.cshtml файле.

@{
    Layout = "/Pages/Shared/_Layout.cshtml";
}

Кроме того, назначьте макет для всех страниц управления из страниц Identity.

@{
    Layout = "_Layout.cshtml";
}

Проверка соответствия требованиям многофакторной аутентификации на странице администрирования.

Страница администрирования Razor проверяет, выполнил ли пользователь вход с помощью MFA. В методе OnGet идентичность используется для доступа к утверждениям пользователя. Утверждение amr проверяется для значения mfa. Если у удостоверения отсутствует это требование или оно равно false, страница перенаправляется на страницу "Включить MFA". Это возможно, так как пользователь уже вошел в систему, но без MFA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityStandaloneMfa
{
    public class AdminModel : PageModel
    {
        public IActionResult OnGet()
        {
            var claimTwoFactorEnabled = 
                User.Claims.FirstOrDefault(t => t.Type == "amr");

            if (claimTwoFactorEnabled != null && 
                "mfa".Equals(claimTwoFactorEnabled.Value))
            {
                // You logged in with MFA, do the administrative stuff
            }
            else
            {
                return Redirect(
                    "/Identity/Account/Manage/TwoFactorAuthentication");
            }

            return Page();
        }
    }
}

Логика пользовательского интерфейса для переключения сведений о входе пользователя

Политика авторизации была добавлена в файл программы. Для политики требуется amr утверждение со значением mfa.

builder.Services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled",
        x => x.RequireClaim("amr", "mfa")));

Затем данную политику можно применять в _Layout представлении для отображения или сокрытия меню администрирования с предупреждением.

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IAuthorizationService AuthorizationService

Если удостоверение вошло в систему с помощью MFA, меню администратора отображается без предупреждающего сообщения. Когда пользователь выполнил вход без MFA, отображается меню администратора (не включено) вместе с подсказкой, которая сообщает пользователю (объясняя предупреждение).

@if (SignInManager.IsSignedIn(User))
{
    @if ((AuthorizationService.AuthorizeAsync(User, "TwoFactorEnabled")).Result.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin" 
               id="tooltip-demo"  
               data-toggle="tooltip" 
               data-placement="bottom" 
               title="MFA is NOT enabled. This is required for the Admin Page. If you have activated MFA, then logout, login again.">
                Admin (Not Enabled)
            </a>
        </li>
    }
}

Если пользователь входит в систему без MFA, отображается предупреждение:

Аутентификация администратора с помощью MFA

Пользователь перенаправляется в режим включения MFA при нажатии ссылки администратора :

Администратор активирует проверку подлинности MFA

Отправить требование многофакторной аутентификации на сервер OpenID Connect

Этот acr_values параметр можно использовать для передачи mfa требуемого значения от клиента серверу в запросе проверки подлинности.

Примечание.

Этот acr_values параметр необходимо обрабатывать на сервере OpenID Connect для работы.

Клиент OpenID Connect ASP.NET Core

Клиентское приложение OpenID Connect для ASP.NET Core Razor Pages использует AddOpenIdConnect метод для входа на сервер OpenID Connect. Параметр acr_values задается со значением mfa и отправляется с помощью запроса проверки подлинности. Этот OpenIdConnectEvents параметр используется для добавления.

Рекомендуемые значения параметров см. в разделе "Справочные acr_values значения метода проверки подлинности".

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme =
            OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.SignInScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.Authority = "<OpenID Connect server URL>";
        options.RequireHttpsMetadata = true;
        options.ClientId = "<OpenID Connect client ID>";
        options.ClientSecret = "<>";
        options.ResponseType = "code";
        options.UsePkce = true;	
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");
        options.SaveTokens = true;
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                context.ProtocolMessage.SetParameter("acr_values", "mfa");
                return Task.FromResult(0);
            }
        };
    });

Пример сервера OpenID Connect IdentityServer 4 с ASP.NET Core Identity

На сервере OpenID Connect, который реализуется с помощью ASP.NET Core Identity с представлениями MVC, создается новое представление ErrorEnable2FA.cshtml . Представление :

  • Отображается, если Identity исходит от приложения, требующего многофакторной аутентификации, но пользователь не активировал это в Identity.
  • Сообщает пользователю и добавляет ссылку для активации.
@{
    ViewData["Title"] = "ErrorEnable2FA";
}

<h1>The client application requires you to have MFA enabled. Enable this, try login again.</h1>

<br />

You can enable MFA to login here:

<br />

<a asp-controller="Manage" asp-action="TwoFactorAuthentication">Enable MFA</a>

В методе LoginIIdentityServerInteractionService реализация _interaction интерфейса используется для доступа к параметрам запроса OpenID Connect. К параметру acr_values осуществляется доступ с помощью свойства AcrValues. После того как клиент отправил это сообщение с установленным mfa, его можно проверить.

Если требуется MFA, и у пользователя в ASP.NET Core Identity включен MFA, то процесс входа продолжается. Если у пользователя нет многофакторной проверки подлинности, пользователь перенаправляется в пользовательское представление ErrorEnable2FA.cshtml. Затем ASP.NET Core Identity входит в систему пользователя.

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
    var returnUrl = model.ReturnUrl;
    var context = 
        await _interaction.GetAuthorizationContextAsync(returnUrl);
    var requires2Fa = 
        context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

    var user = await _userManager.FindByNameAsync(model.Email);
    if (user != null && !user.TwoFactorEnabled && requires2Fa)
    {
        return RedirectToAction(nameof(ErrorEnable2FA));
    }

    // code omitted for brevity

Метод ExternalLoginCallback работает так же, как локальное Identity имя входа. Свойство AcrValues проверяется для mfa значения. Если значение mfa присутствует, многофакторная аутентификация принудительно производится до завершения входа (например, перенаправление на ErrorEnable2FA страницу).

//
// GET: /Account/ExternalLoginCallback
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(
    string returnUrl = null,
    string remoteError = null)
{
    var context =
        await _interaction.GetAuthorizationContextAsync(returnUrl);
    var requires2Fa =
        context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

    if (remoteError != null)
    {
        ModelState.AddModelError(
            string.Empty,
            _sharedLocalizer["EXTERNAL_PROVIDER_ERROR", 
            remoteError]);
        return View(nameof(Login));
    }
    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        return RedirectToAction(nameof(Login));
    }

    var email = info.Principal.FindFirstValue(ClaimTypes.Email);

    if (!string.IsNullOrEmpty(email))
    {
        var user = await _userManager.FindByNameAsync(email);
        if (user != null && !user.TwoFactorEnabled && requires2Fa)
        {
            return RedirectToAction(nameof(ErrorEnable2FA));
        }
    }

    // Sign in the user with this external login provider if the user already has a login.
    var result = await _signInManager
        .ExternalLoginSignInAsync(
            info.LoginProvider, 
            info.ProviderKey, 
            isPersistent: 
            false);

    // code omitted for brevity

Если пользователь уже вошел в систему, клиентское приложение:

  • По-прежнему подтверждает amr утверждение.
  • Можно настроить MFA с помощью ссылки на представление ASP.NET Core Identity.

изображение acr_values-1

Принудительное требование многофакторной аутентификации для клиента OpenID Connect в ASP.NET Core

В этом примере показано, как приложение ASP.NET Core Razor Page, использующее OpenID Connect для входа, может требовать, чтобы пользователи прошли проверку подлинности с помощью MFA.

Чтобы проверить требование MFA, создаётся требование IAuthorizationRequirement. Это будет добавлено на страницы с помощью политики, требующей многофакторной проверки подлинности.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

Реализован AuthorizationHandler, который будет использовать требование amr и проверять значение mfa. Возвращается amr в id_token успешной проверке подлинности и может иметь множество различных значений, как определено в спецификации ссылочных значений метода проверки подлинности.

Возвращаемое значение зависит от способа проверки подлинности удостоверения и реализации сервера OpenID Connect.

AuthorizationHandler использует требование RequireMfa и проверяет утверждение amr. Сервер OpenID Connect можно реализовать с помощью IdentityServer4 с ASP.NET Core Identity. Когда пользователь входит в систему с помощью TOTP, amr претензия возвращается со значением MFA. Если используется другая реализация сервера OpenID Connect или другой тип MFA, amr утверждение будет или может иметь другое значение. Код должен быть расширен, чтобы также принимать это.

public class RequireMfaHandler : AuthorizationHandler<RequireMfa>
{
	protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		RequireMfa requirement)
	{
		if (context == null)
			throw new ArgumentNullException(nameof(context));
		if (requirement == null)
			throw new ArgumentNullException(nameof(requirement));

		var amrClaim =
			context.User.Claims.FirstOrDefault(t => t.Type == "amr");

		if (amrClaim != null && amrClaim.Value == Amr.Mfa)
		{
			context.Succeed(requirement);
		}

		return Task.CompletedTask;
	}
}

В методе Startup.ConfigureServices метод AddOpenIdConnect используется в качестве схемы аутентификации по умолчанию. Обработчик авторизации, используемый для проверки amr утверждения, добавляется в контейнер Inversion of Control. Затем создается политика, которая добавляет RequireMfa требование.

public void ConfigureServices(IServiceCollection services)
{
    services.ConfigureApplicationCookie(options =>
        options.Cookie.SecurePolicy =
            CookieSecurePolicy.Always);

    services.AddSingleton<IAuthorizationHandler, RequireMfaHandler>();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme =
            OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.SignInScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.Authority = "https://localhost:44352";
        options.RequireHttpsMetadata = true;
        options.ClientId = "AspNetCoreRequireMfaOidc";
        options.ClientSecret = "AspNetCoreRequireMfaOidcSecret";
        options.ResponseType = "code";
        options.UsePkce = true;	
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");
        options.SaveTokens = true;
    });

    services.AddAuthorization(options =>
    {
        options.AddPolicy("RequireMfa", policyIsAdminRequirement =>
        {
            policyIsAdminRequirement.Requirements.Add(new RequireMfa());
        });
    });

    services.AddRazorPages();
}

Затем эта политика используется на Razor странице по мере необходимости. Политику можно добавить глобально для всего приложения.

[Authorize(Policy= "RequireMfa")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Если пользователь проходит проверку подлинности без MFA, утверждение, вероятно, будет иметь значение amrpwd. Запрос не будет авторизован для доступа к странице. Используя значения по умолчанию, пользователь будет перенаправлен на страницу Account/AccessDenied . Это поведение можно изменить или реализовать собственную пользовательскую логику здесь. В этом примере добавляется ссылка, чтобы допустимый пользователь смог настроить MFA для своей учетной записи.

@page
@model AspNetCoreRequireMfaOidc.AccessDeniedModel
@{
    ViewData["Title"] = "AccessDenied";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

<h1>AccessDenied</h1>

You require MFA to login here

<a href="https://localhost:44352/Manage/TwoFactorAuthentication">Enable MFA</a>

Теперь только пользователи, прошедшие проверку подлинности с помощью MFA, могут получить доступ к странице или веб-сайту. Если используются разные типы MFA или если 2FA допустимы, amr запрос будет иметь разные значения и должен быть обработан правильно. Разные серверы OpenID Connect также возвращают разные значения для этого утверждения и могут не соответствовать спецификации ссылочных значений метода проверки подлинности.

При входе без MFA (например, с помощью пароля):

  • У amr есть значение pwd.

    amr содержит значение pwd

  • Доступ запрещен:

    Доступ запрещен

Или войдите в систему с помощью OTP:Identity

Вход с помощью OTP Identity

Дополнительные ресурсы