Partilhar via


Autenticação multifator no ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Por Damien Bowden

Visualizar ou baixar o código de exemplo (repositório GitHub damienbod/AspNetCoreHybridFlowWithApi)

A autenticação multifator (MFA) é um processo em que formas adicionais de identificação. são solicitadas de um usuário durante um evento de entrada. Essa solicitação pode ser de inserir um código no celular, usar um chave FIDO2 ou fornecer uma digitalização de impressão digital. Quando você exige um segundo tipo de autenticação, a segurança é aprimorada. O fator adicional não é facilmente obtido ou duplicado por um invasor cibernético.

Esse artigo aborda os seguintes tópicos:

  • O que é a MFA e quais fluxos de MFA são recomendados
  • Configurar a MFA para páginas de administração por meio do ASP.NET Core Identity
  • Enviar requisito de entrada com MFA para o servidor OpenID Connect
  • Forçar l cliente do ASP.NET Core do OpenID Connect a exigir MFA

MFA, 2FA

A MFA requer pelo menos dois ou mais tipos de prova de identity, como algo que você sabe, algo que você possui ou validação biométrica para o usuário se autenticar.

A 2FA (autenticação de dois fatores) é como um subconjunto de MFA, mas a diferença é que a MFA pode exigir dois ou mais fatores para provar a identity.

A 2FA é compatível por padrão ao usar o ASP.NET Core Identity. Para habilitar ou desabilitar a 2FA para um usuário específico, defina a propriedade IdentityUser<TKey>.TwoFactorEnabled. A interface do usuário padrão do ASP.NET Core Identity inclui páginas para configurar a 2FA.

MFA TOTP (algoritmo de senha única baseado no tempo)

A MFA é compatível por padrão ao usar o ASP.NET Core Identity. Essa abordagem pode ser usada junto com qualquer aplicativo autenticador compatível, incluindo:

  • Microsoft Authenticator
  • Google Authenticator

Para obter detalhes sobre a implementação, consulte Como habilitar a geração de código QR para aplicativos autenticadores TOTP no ASP.NET Core.

Para desabilitar o suporte para MFA TOTP, configure a autenticação usando AddIdentity em vez de AddDefaultIdentity. AddDefaultIdentity chama AddDefaultTokenProviders internamente, que registra vários provedores de token, incluindo um para TOTP MFA. Para registrar apenas provedores de token específicos, chame AddTokenProvider para cada provedor necessário. Para obter mais informações sobre provedores de token disponíveis, consulte a fonte AddDefaultTokenProviders no GitHub.

MFA usando chaves de acesso /FIDO2 ou sem senha

Usar chaves de acesso/FIDO2 é atualmente:

  • A maneira mais segura de alcançar a MFA.
  • A MFA que protege contra ataques de phishing. (Assim como a autenticação de certificados e o Windows para empresas)

No momento, o ASP.NET Core não dá suporte diretamente a chaves de acesso/FIDO2. As chaves de acesso/FIDO2 podem ser usadas para fluxos sem senha ou MFA.

O Microsoft Entra ID fornece suporte a chaves de acesso/FIDO2 e fluxos sem senha. Para obter mais informações, confira Opções de autenticação sem senha.

Outras formas de MFA sem senha não protegem ou podem não proteger contra phishing.

MFA com SMS

A MFA com SMS aumenta substancialmente a segurança em comparação com a autenticação com senha (fator único). No entanto, o uso de SMS como um segundo fator não é mais recomendado. Existem muitos vetores de ataque conhecidos para esse tipo de implementação.

Diretrizes do NIST

Configurar a MFA para páginas de administração por meio do ASP.NET Core Identity

A MFA pode ser forçada aos usuários ao acessar páginas confidenciais em um aplicativo ASP.NET CoreIdentity. Isso pode ser útil para aplicativos em que existem diferentes níveis de acesso para as diferentes identidades. Por exemplo, os usuários podem exibir os dados do perfil usando um logon de senha, mas um administrador seria obrigado a usar a MFA para acessar as páginas administrativas.

Estender o logon com uma declaração de MFA

O código de demonstração é configurado usando ASP.NET Core com Páginas Identity e Razor. O método AddIdentity é usado em vez da AddDefaultIdentity, portanto, uma implementação IUserClaimsPrincipalFactory pode ser usada para adicionar declarações à identity após um logon.

Aviso

Este artigo mostra o uso de cadeias de conexão. Com um banco de dados local, o usuário não precisa ser autenticado, mas na produção, as cadeias de conexão às vezes incluem uma senha para autenticação. Uma credencial de senha do proprietário do recurso (ROPC) é um risco de segurança que deve ser evitado em bancos de dados de produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos implantados em ambientes de teste ou produção, consulte Fluxos de autenticação seguros.

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();

A classe AdditionalUserClaimsPrincipalFactory adiciona a declaração amr às declarações do usuário somente após um logon bem-sucedido. O valor da declaração é lido do banco de dados. A declaração é adicionada aqui porque o usuário só deve acessar a exibição protegida mais alta se a identity tiver feito logon com a MFA. Se a exibição do banco de dados for lida diretamente do banco de dados em vez de usar a declaração, será possível acessar a exibição sem MFA diretamente depois de ativar a 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;
        }
    }
}

Como a configuração do serviço Identity foi alterada na classe Startup, os layouts do Identity precisam ser atualizados. Faça o scaffold das páginas Identity no aplicativo. Defina o layout no arquivo Identity/Account/Manage/_Layout.cshtml.

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

Atribua também o layout para todas as páginas de gerenciamento das páginas Identity:

@{
    Layout = "_Layout.cshtml";
}

Validar o requisito de MFA na página de administração

A página de administração Razor valida se o usuário fez logon por meio da MFA. No método OnGet, a identity é usada para acessar as declarações do usuário. A declaração amr é verificada quanto ao valor mfa. Se a identity não tiver essa declaração ou for false, a página será redirecionada para a página Habilitar MFA. Isso é possível porque o usuário já fez logon, mas sem 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();
        }
    }
}

Lógica da interface do usuário para alternar informações de logon do usuário

Uma política de autorização foi adicionada na inicialização. A política exige a declaração amr com o valor mfa.

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

Essa política pode ser usada no modo de exibição _Layout para mostrar ou ocultar o menu Administração com o aviso:

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

Se a identity tiver feito logon usando a MFA, o menuAdminastração será exibido sem o aviso de dica de ferramenta. Quando o usuário faz logon sem MFA, o menu Administração (Não Habilitado) é exibido junto com a dica de ferramenta que informa o usuário (explicando o aviso).

@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>
    }
}

Se o usuário fizer logon sem MFA, o aviso será exibido:

Autenticação do Administrador por MFA

O usuário é redirecionado para o modo de exibição habilitar MFA ao clicar no link Administração:

Autenticação do Administrador por MFA

Enviar requisito de entrada com MFA para o servidor OpenID Connect

O parâmetro acr_values pode ser usado para passar o valor mfa necessário do cliente para o servidor em uma solicitação de autenticação.

Observação

O parâmetro acr_values precisa ser tratado no servidor do OpenID Connect para que isso funcione.

Cliente ASP.NET Core do OpenID Connect

O aplicativo cliente Páginas Razor do ASP.NET Core do OpenID Connect usa o método AddOpenIdConnect para fazer logon no servidor do OpenID Connect. O parâmetro acr_values é definido com o valor mfa e enviado com a solicitação de autenticação. O OpenIdConnectEvents é usado para adicioná-lo.

Para obter valores de parâmetro acr_values recomendados, consulteValores de referência do método de autenticação.

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");
});

Exemplo de servidor do Duende IdentityServer do OpenID Connect com o ASP.NET Core Identity

No servidor do OpenID Connect, que é implementado usando ASP.NET Core Identity com o Razor Pages, uma nova página chamada ErrorEnable2FA.cshtml é criada. A exibição:

  • Ele exibe se o Identity vem de um aplicativo que requer MFA, mas o usuário não ativou essa opção no Identity.
  • Ele informa o usuário e adiciona um link para ativar a MFA.
@{
    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>

No método Login, a IIdentityServerInteractionServiceimplementação da interface _interaction é usada para acessar os parâmetros de solicitação do OpenID Connect. O parâmetro acr_values é acessado por meio da propriedade AcrValues. Como o cliente enviou isso com a definição mfa, isso pode ser verificado.

Se a MFA for necessária e o usuário no ASP.NET Core Identity tiver a MFA habilitada, o logon continuará. Quando o usuário não tem nenhuma MFA habilitada, ele é redirecionado para o modo de exibição personalizada ErrorEnable2FA.cshtml. Em seguida, o ASP.NET Core Identity realiza a entrada do usuário.

O Fido2Store é usado para verificar se o usuário ativou a MFA usando um Provedor de Token FIDO2 personalizado.

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();
}

Se o usuário já estiver conectado, o aplicativo cliente:

  • Ainda valida a declaração amr.
  • Ele pode configurar a MFA com um link para o modo de exibição Identity do ASP.NET Core.

imagem acr_values-1

Forçar l cliente do ASP.NET Core do OpenID Connect a exigir MFA

Esse exemplo mostra como um aplicativo de Página Razor do ASP.NET Core, que usa o OpenID Connect para entrar, pode exigir que os usuários tenham se autenticado por meio de MFA.

Para validar a exigência de MFA, é criado um requisito IAuthorizationRequirement. Ele será adicionado às páginas usando uma política que requer MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Um AuthorizationHandler que usará a declaração amr é implementado e verificará o valor mfa. O amr é retornado no id_token de uma autenticação bem-sucedida e pode ter muitos valores diferentes, conforme definido na especificação Valores de Referência do Método de Autenticação .

O valor retornado depende de como a identity foi autenticada e da implementação do servidor do OpenID Connect.

O AuthorizationHandler usa o requisito RequireMfa e valida a declaração amr. O servidor OpenID Connect pode ser implementado por meio do Servidor Identity Duende com ASP.NET Core Identity. Quando um usuário faz logon usando TOTP, a declaração amr é retornada com um valor de MFA. Se estiver usando uma implementação de servido dor OpenID Connect diferente ou um tipo de MFA diferente, a declaração amr terá ou poderá ter um valor diferente. O código deve ser estendido para aceitar isso também.

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;
	}
}

No arquivo de programa, o método AddOpenIdConnect é usado como o esquema de desafio padrão. O manipulador de autorização, que é usado para verificar a declaração amr, é adicionado ao contêiner Inversão de Controle. Em seguida, é criada uma política que adiciona o requisito 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();

Essa política é usada na página Razor conforme necessário. A política também pode ser adicionada globalmente para todo o aplicativo.

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

Se o usuário se autenticar sem MFA, a declaração amr provavelmente terá um valor pwd. A solicitação não será autorizada a acessar a página. Usando os valores padrão, o usuário será redirecionado para a página Account/AccessDenied . Esse comportamento pode ser alterado ou você pode implementar sua própria lógica personalizada aqui. Nesse exemplo, um link é adicionado para que o usuário válido possa configurar a MFA para sua conta.

@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>

Agora, somente os usuários que se autenticam com MFA podem acessar a página ou o site. Se diferentes tipos de MFA forem usados ou se estiver tudo bem com a 2FA, a declaração amr terá valores diferentes e precisará ser processada corretamente. Diferentes servidores do OpenID Connect também retornam valores diferentes para essa declaração e podem não seguir a especificação Valores de Referência do Método de Autenticação .

Ao fazer logon sem MFA (por exemplo, usando apenas uma senha):

  • O amr tem o valor pwd:

    amr tme o valor pwd

  • O acesso é negado

    O acesso é negado

Como alternativa, faça logon usando OTP com Identity:

 Fazer logon usando OTP com Identity

Personalização do parâmetro OIDC e OAuth

A opção AdditionalAuthorizationParameters dos manipuladores de autenticação OAuth e OIDC permite a personalização dos parâmetros de mensagem de autorização que geralmente são incluídos como parte da cadeia de caracteres de consulta de redirecionamento:

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

Recursos adicionais

Por Damien Bowden

Visualizar ou baixar o código de exemplo (repositório GitHub damienbod/AspNetCoreHybridFlowWithApi)

A autenticação multifator (MFA) é um processo em que formas adicionais de identificação. são solicitadas de um usuário durante um evento de entrada. Essa solicitação pode ser de inserir um código no celular, usar um chave FIDO2 ou fornecer uma digitalização de impressão digital. Quando você exige um segundo tipo de autenticação, a segurança é aprimorada. O fator adicional não é facilmente obtido ou duplicado por um invasor cibernético.

Esse artigo aborda os seguintes tópicos:

  • O que é a MFA e quais fluxos de MFA são recomendados
  • Configurar a MFA para páginas de administração por meio do ASP.NET Core Identity
  • Enviar requisito de entrada com MFA para o servidor OpenID Connect
  • Forçar l cliente do ASP.NET Core do OpenID Connect a exigir MFA

MFA, 2FA

A MFA requer pelo menos dois ou mais tipos de prova de identity, como algo que você sabe, algo que você possui ou validação biométrica para o usuário se autenticar.

A 2FA (autenticação de dois fatores) é como um subconjunto de MFA, mas a diferença é que a MFA pode exigir dois ou mais fatores para provar a identity.

A 2FA é compatível por padrão ao usar o ASP.NET Core Identity. Para habilitar ou desabilitar a 2FA para um usuário específico, defina a propriedade IdentityUser<TKey>.TwoFactorEnabled. A interface do usuário padrão do ASP.NET Core Identity inclui páginas para configurar a 2FA.

MFA TOTP (algoritmo de senha única baseado no tempo)

A MFA é compatível por padrão ao usar o ASP.NET Core Identity. Essa abordagem pode ser usada junto com qualquer aplicativo autenticador compatível, incluindo:

  • Microsoft Authenticator
  • Google Authenticator

Para obter detalhes sobre a implementação, consulte Como habilitar a geração de código QR para aplicativos autenticadores TOTP no ASP.NET Core.

Para desabilitar o suporte para MFA TOTP, configure a autenticação usando AddIdentity em vez de AddDefaultIdentity. AddDefaultIdentity chama AddDefaultTokenProviders internamente, que registra vários provedores de token, incluindo um para TOTP MFA. Para registrar apenas provedores de token específicos, chame AddTokenProvider para cada provedor necessário. Para obter mais informações sobre provedores de token disponíveis, consulte a fonte AddDefaultTokenProviders no GitHub.

MFA usando chaves de acesso /FIDO2 ou sem senha

Usar chaves de acesso/FIDO2 é atualmente:

  • A maneira mais segura de alcançar a MFA.
  • A MFA que protege contra ataques de phishing. (Assim como a autenticação de certificados e o Windows para empresas)

No momento, o ASP.NET Core não dá suporte diretamente a chaves de acesso/FIDO2. As chaves de acesso/FIDO2 podem ser usadas para fluxos sem senha ou MFA.

O Microsoft Entra ID fornece suporte a chaves de acesso/FIDO2 e fluxos sem senha. Para obter mais informações, confira Opções de autenticação sem senha.

Outras formas de MFA sem senha não protegem ou podem não proteger contra phishing.

MFA com SMS

A MFA com SMS aumenta substancialmente a segurança em comparação com a autenticação com senha (fator único). No entanto, o uso de SMS como um segundo fator não é mais recomendado. Existem muitos vetores de ataque conhecidos para esse tipo de implementação.

Diretrizes do NIST

Configurar a MFA para páginas de administração por meio do ASP.NET Core Identity

A MFA pode ser forçada aos usuários ao acessar páginas confidenciais em um aplicativo ASP.NET CoreIdentity. Isso pode ser útil para aplicativos em que existem diferentes níveis de acesso para as diferentes identidades. Por exemplo, os usuários podem exibir os dados do perfil usando um logon de senha, mas um administrador seria obrigado a usar a MFA para acessar as páginas administrativas.

Estender o logon com uma declaração de MFA

O código de demonstração é configurado usando ASP.NET Core com Páginas Identity e Razor. O método AddIdentity é usado em vez da AddDefaultIdentity, portanto, uma implementação IUserClaimsPrincipalFactory pode ser usada para adicionar declarações à identity após um logon.

Aviso

Este artigo mostra o uso de cadeias de conexão. Com um banco de dados local, o usuário não precisa ser autenticado, mas na produção, as cadeias de conexão às vezes incluem uma senha para autenticação. Uma credencial de senha do proprietário do recurso (ROPC) é um risco de segurança que deve ser evitado em bancos de dados de produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos implantados em ambientes de teste ou produção, consulte Fluxos de autenticação seguros.

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();

A classe AdditionalUserClaimsPrincipalFactory adiciona a declaração amr às declarações do usuário somente após um logon bem-sucedido. O valor da declaração é lido do banco de dados. A declaração é adicionada aqui porque o usuário só deve acessar a exibição protegida mais alta se a identity tiver feito logon com a MFA. Se a exibição do banco de dados for lida diretamente do banco de dados em vez de usar a declaração, será possível acessar a exibição sem MFA diretamente depois de ativar a 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;
        }
    }
}

Como a configuração do serviço Identity foi alterada na classe Startup, os layouts do Identity precisam ser atualizados. Faça o scaffold das páginas Identity no aplicativo. Defina o layout no arquivo Identity/Account/Manage/_Layout.cshtml.

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

Atribua também o layout para todas as páginas de gerenciamento das páginas Identity:

@{
    Layout = "_Layout.cshtml";
}

Validar o requisito de MFA na página de administração

A página de administração Razor valida se o usuário fez logon por meio da MFA. No método OnGet, a identity é usada para acessar as declarações do usuário. A declaração amr é verificada quanto ao valor mfa. Se a identity não tiver essa declaração ou for false, a página será redirecionada para a página Habilitar MFA. Isso é possível porque o usuário já fez logon, mas sem 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();
        }
    }
}

Lógica da interface do usuário para alternar informações de logon do usuário

Uma política de autorização foi adicionada na inicialização. A política exige a declaração amr com o valor mfa.

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

Essa política pode ser usada no modo de exibição _Layout para mostrar ou ocultar o menu Administração com o aviso:

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

Se a identity tiver feito logon usando a MFA, o menuAdminastração será exibido sem o aviso de dica de ferramenta. Quando o usuário faz logon sem MFA, o menu Administração (Não Habilitado) é exibido junto com a dica de ferramenta que informa o usuário (explicando o aviso).

@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>
    }
}

Se o usuário fizer logon sem MFA, o aviso será exibido:

Autenticação do Administrador por MFA

O usuário é redirecionado para o modo de exibição habilitar MFA ao clicar no link Administração:

Autenticação do Administrador por MFA

Enviar requisito de entrada com MFA para o servidor OpenID Connect

O parâmetro acr_values pode ser usado para passar o valor mfa necessário do cliente para o servidor em uma solicitação de autenticação.

Observação

O parâmetro acr_values precisa ser tratado no servidor do OpenID Connect para que isso funcione.

Cliente ASP.NET Core do OpenID Connect

O aplicativo cliente Páginas Razor do ASP.NET Core do OpenID Connect usa o método AddOpenIdConnect para fazer logon no servidor do OpenID Connect. O parâmetro acr_values é definido com o valor mfa e enviado com a solicitação de autenticação. O OpenIdConnectEvents é usado para adicioná-lo.

Para obter valores de parâmetro acr_values recomendados, consulteValores de referência do método de autenticação.

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);
		}
	};
});

Exemplo de servidor do Duende IdentityServer do OpenID Connect com o ASP.NET Core Identity

No servidor do OpenID Connect, que é implementado usando ASP.NET Core Identity com o Razor Pages, uma nova página chamada ErrorEnable2FA.cshtml é criada. A exibição:

  • Ele exibe se o Identity vem de um aplicativo que requer MFA, mas o usuário não ativou essa opção no Identity.
  • Ele informa o usuário e adiciona um link para ativar a MFA.
@{
    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>

No método Login, a IIdentityServerInteractionServiceimplementação da interface _interaction é usada para acessar os parâmetros de solicitação do OpenID Connect. O parâmetro acr_values é acessado por meio da propriedade AcrValues. Como o cliente enviou isso com a definição mfa, isso pode ser verificado.

Se a MFA for necessária e o usuário no ASP.NET Core Identity tiver a MFA habilitada, o logon continuará. Quando o usuário não tem nenhuma MFA habilitada, ele é redirecionado para o modo de exibição personalizada ErrorEnable2FA.cshtml. Em seguida, o ASP.NET Core Identity realiza a entrada do usuário.

O Fido2Store é usado para verificar se o usuário ativou a MFA usando um Provedor de Token FIDO2 personalizado.

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();
}

Se o usuário já estiver conectado, o aplicativo cliente:

  • Ainda valida a declaração amr.
  • Ele pode configurar a MFA com um link para o modo de exibição Identity do ASP.NET Core.

imagem acr_values-1

Forçar l cliente do ASP.NET Core do OpenID Connect a exigir MFA

Esse exemplo mostra como um aplicativo de Página Razor do ASP.NET Core, que usa o OpenID Connect para entrar, pode exigir que os usuários tenham se autenticado por meio de MFA.

Para validar a exigência de MFA, é criado um requisito IAuthorizationRequirement. Ele será adicionado às páginas usando uma política que requer MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Um AuthorizationHandler que usará a declaração amr é implementado e verificará o valor mfa. O amr é retornado no id_token de uma autenticação bem-sucedida e pode ter muitos valores diferentes, conforme definido na especificação Valores de Referência do Método de Autenticação .

O valor retornado depende de como a identity foi autenticada e da implementação do servidor do OpenID Connect.

O AuthorizationHandler usa o requisito RequireMfa e valida a declaração amr. O servidor OpenID Connect pode ser implementado por meio do Servidor Identity Duende com ASP.NET Core Identity. Quando um usuário faz logon usando TOTP, a declaração amr é retornada com um valor de MFA. Se estiver usando uma implementação de servido dor OpenID Connect diferente ou um tipo de MFA diferente, a declaração amr terá ou poderá ter um valor diferente. O código deve ser estendido para aceitar isso também.

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;
	}
}

No arquivo de programa, o método AddOpenIdConnect é usado como o esquema de desafio padrão. O manipulador de autorização, que é usado para verificar a declaração amr, é adicionado ao contêiner Inversão de Controle. Em seguida, é criada uma política que adiciona o requisito 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();

Essa política é usada na página Razor conforme necessário. A política também pode ser adicionada globalmente para todo o aplicativo.

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

Se o usuário se autenticar sem MFA, a declaração amr provavelmente terá um valor pwd. A solicitação não será autorizada a acessar a página. Usando os valores padrão, o usuário será redirecionado para a página Account/AccessDenied . Esse comportamento pode ser alterado ou você pode implementar sua própria lógica personalizada aqui. Nesse exemplo, um link é adicionado para que o usuário válido possa configurar a MFA para sua conta.

@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>

Agora, somente os usuários que se autenticam com MFA podem acessar a página ou o site. Se diferentes tipos de MFA forem usados ou se estiver tudo bem com a 2FA, a declaração amr terá valores diferentes e precisará ser processada corretamente. Diferentes servidores do OpenID Connect também retornam valores diferentes para essa declaração e podem não seguir a especificação Valores de Referência do Método de Autenticação .

Ao fazer logon sem MFA (por exemplo, usando apenas uma senha):

  • O amr tem o valor pwd:

    amr tme o valor pwd

  • O acesso é negado

    O acesso é negado

Como alternativa, faça logon usando OTP com Identity:

 Fazer logon usando OTP com Identity

Recursos adicionais

Por Damien Bowden

Visualizar ou baixar o código de exemplo (repositório GitHub damienbod/AspNetCoreHybridFlowWithApi)

A autenticação multifator (MFA) é um processo em que formas adicionais de identificação. são solicitadas de um usuário durante um evento de entrada. Essa solicitação pode ser de inserir um código no celular, usar um chave FIDO2 ou fornecer uma digitalização de impressão digital. Quando você exige um segundo tipo de autenticação, a segurança é aprimorada. O fator adicional não é facilmente obtido ou duplicado por um invasor cibernético.

Esse artigo aborda os seguintes tópicos:

  • O que é a MFA e quais fluxos de MFA são recomendados
  • Configurar a MFA para páginas de administração por meio do ASP.NET Core Identity
  • Enviar requisito de entrada com MFA para o servidor OpenID Connect
  • Forçar l cliente do ASP.NET Core do OpenID Connect a exigir MFA

MFA, 2FA

A MFA requer pelo menos dois ou mais tipos de prova de identity, como algo que você sabe, algo que você possui ou validação biométrica para o usuário se autenticar.

A 2FA (autenticação de dois fatores) é como um subconjunto de MFA, mas a diferença é que a MFA pode exigir dois ou mais fatores para provar a identity.

MFA TOTP (algoritmo de senha única baseado no tempo)

A MFA usando TOTP é uma implementação compatível ao usar o ASP.NET Core Identity. Ela pode ser usada junto com qualquer aplicativo autenticador compatível, incluindo:

  • Aplicativo Microsoft Authenticator
  • Aplicativo Google Authenticator

Veja o link a seguir para obter mais detalhes de implementação:

Habilitar a geração de código QR para aplicativos autenticadores TOTP no ASP.NET Core

MFA usando chaves de acesso /FIDO2 ou sem senha

Usar chaves de acesso/FIDO2 é atualmente:

  • A maneira mais segura de alcançar a MFA.
  • A MFA que protege contra ataques de phishing. (Assim como a autenticação de certificados e o Windows para empresas)

No momento, o ASP.NET Core não dá suporte diretamente a chaves de acesso/FIDO2. As chaves de acesso/FIDO2 podem ser usadas para fluxos sem senha ou MFA.

O Microsoft Entra ID fornece suporte a chaves de acesso/FIDO2 e fluxos sem senha. Para obter mais informações, confira Opções de autenticação sem senha.

Outras formas de MFA sem senha não protegem ou podem não proteger contra phishing.

MFA com SMS

A MFA com SMS aumenta substancialmente a segurança em comparação com a autenticação com senha (fator único). No entanto, o uso de SMS como um segundo fator não é mais recomendado. Existem muitos vetores de ataque conhecidos para esse tipo de implementação.

Diretrizes do NIST

Configurar a MFA para páginas de administração por meio do ASP.NET Core Identity

A MFA pode ser forçada aos usuários ao acessar páginas confidenciais em um aplicativo ASP.NET CoreIdentity. Isso pode ser útil para aplicativos em que existem diferentes níveis de acesso para as diferentes identidades. Por exemplo, os usuários podem exibir os dados do perfil usando um logon de senha, mas um administrador seria obrigado a usar a MFA para acessar as páginas administrativas.

Estender o logon com uma declaração de MFA

O código de demonstração é configurado usando ASP.NET Core com Páginas Identity e Razor. O método AddIdentity é usado em vez da AddDefaultIdentity, portanto, uma implementação IUserClaimsPrincipalFactory pode ser usada para adicionar declarações à identity após um logon.

Aviso

Este artigo mostra o uso de cadeias de conexão. Com um banco de dados local, o usuário não precisa ser autenticado, mas na produção, as cadeias de conexão às vezes incluem uma senha para autenticação. Uma credencial de senha do proprietário do recurso (ROPC) é um risco de segurança que deve ser evitado em bancos de dados de produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos implantados em ambientes de teste ou produção, consulte Fluxos de autenticação seguros.

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();
}

A classe AdditionalUserClaimsPrincipalFactory adiciona a declaração amr às declarações do usuário somente após um logon bem-sucedido. O valor da declaração é lido do banco de dados. A declaração é adicionada aqui porque o usuário só deve acessar a exibição protegida mais alta se a identity tiver feito logon com a MFA. Se a exibição do banco de dados for lida diretamente do banco de dados em vez de usar a declaração, será possível acessar a exibição sem MFA diretamente depois de ativar a 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;
        }
    }
}

Como a configuração do serviço Identity foi alterada na classe Startup, os layouts do Identity precisam ser atualizados. Faça o scaffold das páginas Identity no aplicativo. Defina o layout no arquivo Identity/Account/Manage/_Layout.cshtml.

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

Atribua também o layout para todas as páginas de gerenciamento das páginas Identity:

@{
    Layout = "_Layout.cshtml";
}

Validar o requisito de MFA na página de administração

A página de administração Razor valida se o usuário fez logon por meio da MFA. No método OnGet, a identity é usada para acessar as declarações do usuário. A declaração amr é verificada quanto ao valor mfa. Se a identity não tiver essa declaração ou for false, a página será redirecionada para a página Habilitar MFA. Isso é possível porque o usuário já fez logon, mas sem 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();
        }
    }
}

Lógica da interface do usuário para alternar informações de logon do usuário

Uma política de autorização foi adicionada no arquivo do programa. A política exige a declaração amr com o valor mfa.

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

Essa política pode ser usada no modo de exibição _Layout para mostrar ou ocultar o menu Administração com o aviso:

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

Se a identity tiver feito logon usando a MFA, o menuAdminastração será exibido sem o aviso de dica de ferramenta. Quando o usuário faz logon sem MFA, o menu Administração (Não Habilitado) é exibido junto com a dica de ferramenta que informa o usuário (explicando o aviso).

@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>
    }
}

Se o usuário fizer logon sem MFA, o aviso será exibido:

Autenticação do Administrador por MFA

O usuário é redirecionado para o modo de exibição habilitar MFA ao clicar no link Administração:

Autenticação do Administrador por MFA

Enviar requisito de entrada com MFA para o servidor OpenID Connect

O parâmetro acr_values pode ser usado para passar o valor mfa necessário do cliente para o servidor em uma solicitação de autenticação.

Observação

O parâmetro acr_values precisa ser tratado no servidor do OpenID Connect para que isso funcione.

Cliente ASP.NET Core do OpenID Connect

O aplicativo cliente Páginas Razor do ASP.NET Core do OpenID Connect usa o método AddOpenIdConnect para fazer logon no servidor do OpenID Connect. O parâmetro acr_values é definido com o valor mfa e enviado com a solicitação de autenticação. O OpenIdConnectEvents é usado para adicioná-lo.

Para obter valores de parâmetro acr_values recomendados, consulteValores de referência do método de autenticação.

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);
            }
        };
    });

Exemplo de servidor do IdentityServer 4 do OpenID Connect com o ASP.NET Core Identity

No servidor do OpenID Connect, que é implementado usando ASP.NET Core Identity com exibições MVC, um novo modo de exibição chamado ErrorEnable2FA.cshtml é criado. A exibição:

  • Ele exibe se o Identity vem de um aplicativo que requer MFA, mas o usuário não ativou essa opção no Identity.
  • Ele informa o usuário e adiciona um link para ativar a MFA.
@{
    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>

No método Login, a IIdentityServerInteractionServiceimplementação da interface _interaction é usada para acessar os parâmetros de solicitação do OpenID Connect. O parâmetro acr_values é acessado por meio da propriedade AcrValues. Como o cliente enviou isso com a definição mfa, isso pode ser verificado.

Se a MFA for necessária e o usuário no ASP.NET Core Identity tiver a MFA habilitada, o logon continuará. Quando o usuário não tem nenhuma MFA habilitada, ele é redirecionado para o modo de exibição personalizada ErrorEnable2FA.cshtml. Em seguida, o ASP.NET Core Identity realiza a entrada do usuário.

//
// 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

O método ExternalLoginCallback funciona como o logon local Identity . A propriedade AcrValues é verificada quanto ao valor mfa. Se o valor mfa estiver presente, a MFA será implementada antes da conclusão do logon (por exemplo, redirecionada para o modo de exibição 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

Se o usuário já estiver conectado, o aplicativo cliente:

  • Ainda valida a declaração amr.
  • Ele pode configurar a MFA com um link para o modo de exibição Identity do ASP.NET Core.

imagem acr_values-1

Forçar l cliente do ASP.NET Core do OpenID Connect a exigir MFA

Esse exemplo mostra como um aplicativo de Página Razor do ASP.NET Core, que usa o OpenID Connect para entrar, pode exigir que os usuários tenham se autenticado por meio de MFA.

Para validar a exigência de MFA, é criado um requisito IAuthorizationRequirement. Ele será adicionado às páginas usando uma política que requer MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

Um AuthorizationHandler que usará a declaração amr é implementado e verificará o valor mfa. O amr é retornado no id_token de uma autenticação bem-sucedida e pode ter muitos valores diferentes, conforme definido na especificação Valores de Referência do Método de Autenticação .

O valor retornado depende de como a identity foi autenticada e da implementação do servidor do OpenID Connect.

O AuthorizationHandler usa o requisito RequireMfa e valida a declaração amr. O servidor do OpenID Connect pode ser implementado usando o IdentityServer4 com o ASP.NET Core Identity. Quando um usuário faz logon usando TOTP, a declaração amr é retornada com um valor de MFA. Se estiver usando uma implementação de servido dor OpenID Connect diferente ou um tipo de MFA diferente, a declaração amr terá ou poderá ter um valor diferente. O código deve ser estendido para aceitar isso também.

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;
	}
}

No método Startup.ConfigureServices, o método AddOpenIdConnect é usado como o esquema de desafio padrão. O manipulador de autorização, que é usado para verificar a declaração amr, é adicionado ao contêiner Inversão de Controle. Em seguida, é criada uma política que adiciona o requisito 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();
}

Essa política é usada na página Razor conforme necessário. A política também pode ser adicionada globalmente para todo o aplicativo.

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

Se o usuário se autenticar sem MFA, a declaração amr provavelmente terá um valor pwd. A solicitação não será autorizada a acessar a página. Usando os valores padrão, o usuário será redirecionado para a página Account/AccessDenied . Esse comportamento pode ser alterado ou você pode implementar sua própria lógica personalizada aqui. Nesse exemplo, um link é adicionado para que o usuário válido possa configurar a MFA para sua conta.

@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>

Agora, somente os usuários que se autenticam com MFA podem acessar a página ou o site. Se diferentes tipos de MFA forem usados ou se estiver tudo bem com a 2FA, a declaração amr terá valores diferentes e precisará ser processada corretamente. Diferentes servidores do OpenID Connect também retornam valores diferentes para essa declaração e podem não seguir a especificação Valores de Referência do Método de Autenticação .

Ao fazer logon sem MFA (por exemplo, usando apenas uma senha):

  • O amr tem o valor pwd:

    amr tme o valor pwd

  • O acesso é negado

    O acesso é negado

Como alternativa, faça logon usando OTP com Identity:

 Fazer logon usando OTP com Identity

Recursos adicionais