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 10 deste artigo.

Advertência

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

Por Damien Bowden

A autenticação multifator (MFA) é um processo em que é pedido a um utilizador, ao iniciar sessão, que forneça formas adicionais de identificação. O prompt pode pedir ao utilizador que insira um código a partir de um telemóvel, use uma chave FIDO2 ou forneça uma varredura de impressões digitais. Quando você precisa de uma segunda forma de autenticação, a segurança é aprimorada. O fator extra não é facilmente obtido ou duplicado por um ciberatacante.

Este artigo apresenta uma visão geral da autenticação multifatorial no ASP.NET Core e dos fluxos recomendados de autenticação, com exemplos que mostram como:

  • Configure MFA para páginas de administração usando ASP.NET Core Identity.
  • Envie um requisito de login MFA para o servidor OpenID Connect.
  • Obrigar o cliente OpenID Connect do ASP.NET Core a exigir MFA.

Visualize ou baixe o código de exemplo (como fazer o download).

MFA vs 2FA

A MFA exige pelo menos dois ou mais tipos de prova para uma identidade. Os tipos de prova podem ser algo que conheces, algo que possuis, ou validação biométrica para o utilizador autenticar.

A autenticação de dois fatores (2FA) é como um subconjunto da MFA. A 2FA requer exatamente dois tipos de prova, enquanto a MFA pode exigir dois ou mais fatores para provar a identidade.

2FA é suportado por padrão ao usar ASP.NET Core Identity. Para habilitar ou desabilitar o 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 o 2FA.

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

A MFA com TOTP é suportada por predefinição ao utilizar o ASP.NET Core Identity. Essa abordagem pode ser usada em conjunto com qualquer aplicativo autenticador compatível, incluindo:

  • Microsoft Authenticator
  • Autenticador do Google

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

Para desativar o suporte a MFA TOTP, configure a autenticação usando o método AddIdentity em vez do método AddDefaultIdentity. O AddDefaultIdentity método chama o AddDefaultTokenProviders método internamente, que regista múltiplos fornecedores de tokens, incluindo um para MFA TOTP. Para registar apenas fornecedores específicos de tokens, chame o AddTokenProvider método para cada fornecedor necessário. Para mais informações sobre fornecedores de tokens disponíveis, consulte a fonte de referência 'AddDefaultTokenProviders' no repositório dotnet/aspnetcore GitHub.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Chaves de acesso MFA/FIDO2 ou sem senha

A abordagem de chaves de acesso/FIDO2 é atualmente:

  • A forma mais segura de alcançar o MFA.
  • MFA que protege contra ataques de phishing (também autenticação de certificados e Windows para empresas).

ASP.NET Core suporta chaves de acesso usando ASP.NET Core Identity. Passkeys/FIDO2 podem ser usados para MFA ou fluxos sem senha. Para mais informações, consulte as chaves de acesso Enable Web Authentication API (WebAuthn).

O Microsoft Entra ID fornece suporte para chaves de acesso/FIDO2 e fluxos sem senha. Para mais informações, veja Métodos de autenticação em Microsoft Entra ID - passkeys (FIDO2).

Outras formas de MFA sem palavra-passe não protegem ou podem não proteger contra phishing.

Autenticação Multi-Fator (MFA) via SMS

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

Para mais informações, consulte as Diretrizes Digitais Identity do NIST (Publicação Especial 800-63B).

Configure MFA para páginas de administração com ASP.NET Core Identity

Pode impor MFA aos utilizadores para acederem a páginas sensíveis numa aplicação ASP.NET Core Identity. Esta abordagem pode ser útil para aplicações onde existem diferentes níveis de acesso para diferentes identidades. Por exemplo, os utilizadores poderão visualizar os dados do perfil através de um processo de login por palavra-passe, mas um administrador seria obrigado a usar MFA para aceder às páginas administrativas.

Prolongar o processo de assinatura com uma reclamação MFA

A configuração de código de exemplo usa ASP.NET Core com Identity e Razor Pages. O método AddIdentity é utilizado em vez de AddDefaultIdentity, pelo que uma implementação de IUserClaimsPrincipalFactory pode ser usada para adicionar declarações à identidade após um início de sessão com êxito.

Advertência

Este artigo mostra o uso de cadeias de conexão. Quando uma base de dados local é usada para desenvolvimento e testes, a autenticação do utilizador da base de dados através da cadeia de ligação não é necessária. Em ambientes de produção, as strings de ligação por vezes incluem uma palavra-passe para autenticar o acesso ou operações da base de dados. Uma credencial de palavra-passe do proprietário do recurso (ROPC) numa cadeia de ligação constitui um risco de segurança e deve ser evitada em aplicações em produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para mais informações sobre autenticação para aplicações implementadas em ambientes de teste ou produção, consulte ASP.NET tópicos de segurança Core.

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 (declaração de Referências de Métodos de Autenticação) às declarações do utilizador apenas depois de um início de sessão com êxito. O valor da reclamação é lido da base de dados. A declaração é adicionada aqui porque o utilizador só deve aceder à vista com maior nível de proteção se a identidade tiver iniciado sessão com MFA. Se a exibição do banco de dados for lida diretamente do banco de dados em vez de usar a declaração, é possível acessar a exibição sem MFA diretamente após ativar o 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 mudou na classe Startup, os layouts de Identity precisam de ser atualizados:

  • Estruture as páginas Identity no aplicativo.

  • Defina o layout no Identityficheiro /Account/Manage/_Layout.cshtml .

    @{
        Layout = "/Pages/Shared/_Layout.cshtml";
    }
    
  • Além disso, atribui o layout de todas as páginas de gestão a partir das Identity páginas:

    @{
        Layout = "_Layout.cshtml";
    }
    

Validar o requisito de autenticação multifator (MFA) na página de administração

A página Administração Razor verifica se o utilizador tem sessão iniciada através de MFA. No método OnGet, a identidade é usada para acessar as declarações de usuário. A reclamação amr é verificada para o valor mfa. Se a identidade não contiver esta declaração ou estiver false, a página redirecionará para a página Ativar MFA. Esta ação é possível porque o utilizador já está logado, 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 para mostrar/ocultar as informações de início de sessão do utilizador

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

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

Essa política pode ser usada na visualização _Layout para mostrar ou ocultar o menu Admin com o aviso:

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

Se a identidade for iniciada via MFA, o menu de Administração aparece sem o aviso de tooltip.

Quando o utilizador tem sessão iniciada sem MFA, o menu Administrador (Não Ativado) é apresentado juntamente com a descrição que informa o utilizador (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 utilizador iniciar sessão sem MFA, a opção de Administrador mostra o aviso (Não Ativado ):

Captura de ecrã que mostra o utilizador com sessão iniciada sem autenticação multifator (MFA) e o aviso 'Não ativado' na opção Admin.

Quando o utilizador seleciona a opção Administrador , é redirecionado para uma vista onde pode ativar a MFA:

Captura de ecrã que mostra a vista 'Gerir a sua conta' onde o utilizador pode ativar a autenticação MFA como administrador.

Enviar requisito de login 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 acr_values parâmetro precisa de ser tratado no servidor OpenID Connect para que esta abordagem funcione.

Cliente OpenID Connect ASP.NET Core

A aplicação cliente ASP.NET Core Razor Pages OpenID Connect utiliza o método AddOpenIdConnect para iniciar sessão no servidor 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 acrescentar este valor.

Para valores de parâmetros recomendados acr_values , consulte a especificação de Valores de Referência do Método de Autenticação do IETF .

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: OpenID Connect Duende IdentityServer servidor

O exemplo seguinte demonstra o servidor OpenID Connect Duende IdentityServer com ASP.NET Core Identity. No servidor OpenID Connect, que é implementado usando ASP.NET Core Identity com Razor Pages, é criada uma nova página chamada ErrorEnable2FA.cshtml.

A visualização da página:

  • Exibe se vier Identity de uma aplicação que requer MFA, mas o utilizador não ativou MFA em Identity.
  • Informa o usuário e adiciona um link para ativá-lo.
@{
    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 implementação da interface IIdentityServerInteractionService_interaction é usada para acessar os parâmetros de solicitação do OpenID Connect. O acr_values parâmetro é acedido usando a AcrValues propriedade. Como o cliente enviou isto com mfa set, isto pode então ser verificado.

Se for necessário MFA e o utilizador em ASP.NET Core Identity estiver ativado com MFA, o processo de início de sessão continua. Quando o usuário não tem MFA habilitado, o usuário é redirecionado para o modo de exibição personalizado ErrorEnable2FA.cshtml. Em seguida, ASP.NET Core Identity inicia a sessão do utilizador.

A Fido2Store é usada para verificar se o utilizador ativou a MFA através de um fornecedor personalizado de fichas 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();
}

Se o utilizador já tiver iniciado sessão, a aplicação cliente:

  • Ainda valida a alegação amr.

  • Pode configurar a MFA com um link para a vista ASP.NET Core Identity, como Enable MFA:

    Captura de ecrã que mostra o cliente requer login com autenticação multifator e a opção 'Ativar MFA'.

Forçar cliente ASP.NET Core OpenID Connect a exigir autenticação multifator

Este exemplo mostra como uma aplicação ASP.NET Core Razor Page, que utiliza OpenID Connect para iniciar sessão, pode exigir que os utilizadores sejam autenticados através de MFA.

Para validar o requisito de MFA, é criado um requisito IAuthorizationRequirement. O requisito é adicionado às páginas utilizando uma política que exige autenticação multifator (MFA).

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

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

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

O AuthorizationHandler usa o requisito RequireMfa e valida a reivindicação amr. O servidor OpenID Connect pode ser implementado usando o servidor Duende Identity com ASP.NET Core Identity. Quando um utilizador inicia sessão usando TOTP, a amr reclamação é devolvida com um valor MFA. Se usar uma implementação de servidor OpenID Connect diferente ou um tipo de MFA diferente, a amr reivindicação pode ter um valor diferente. O código deve ser estendido para aceitar os outros valores possíveis.

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 de amr, é adicionado ao contêiner Inversion of Control. 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();

A política é então utilizada na página Razor, conforme necessário. A política também pode ser adicionada globalmente para toda a aplicação.

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

Se o utilizador se autenticar sem MFA, a declaração amr tem provavelmente o valor pwd (palavra-passe). Consequentemente, o pedido não tem autorização para aceder à página. Com os valores predefinidos, o utilizador é redirecionado para a página Account>AccessDenied. Este comportamento pode ser alterado ou pode implementar a sua própria lógica personalizada. Neste exemplo, é adicionado um link para que o utilizador válido possa configurar MFA para a 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>

Após esta alteração, apenas os utilizadores que se autenticam com MFA podem aceder à página ou site. Se forem usados diferentes tipos de MFA, ou se a 2FA for permitida, o amr pedido tem valores diferentes e precisa de ser processado corretamente. Diferentes servidores OpenID Connect também retornam valores diferentes para esta afirmação. Estes valores podem não seguir a especificação de Valores de Referência do Método de Autenticação do IETF .

Quando o utilizador inicia sessão sem MFA (por exemplo, usando apenas uma palavra-passe):

  • A amr afirmação tem o valor de palavra-passe pwd:

    Captura de ecrã do código-fonte e da janela do relógio onde a afirmação 'amr' tem o valor da palavra-passe 'pwd'.

  • O acesso é negado:

    A captura de ecrã que mostra o acesso é negada quando o utilizador inicia sessão sem MFA.

Alternativamente, quando o utilizador inicia sessão usando OTP com Identity, a amr reivindicação tem o valor mfa :

Captura de ecrã do código-fonte e da janela do relógio onde a afirmação 'amr' tem o valor 'mfa'.

Personalização de parâmetros OIDC e OAuth

A opção dos processadores de autenticação OAuth e OIDC AdditionalAuthorizationParameters permite personalizar os parâmetros da mensagem de autorização que são frequentemente incluídos como parte da query string de redirecionamento:

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

Por Damien Bowden

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

A autenticação multifator (MFA) é um processo no qual um usuário é solicitado durante um evento de entrada para formas adicionais de identificação. Esse prompt pode ser para inserir um código de um telemóvel, usar uma chave FIDO2 ou fornecer uma leitura de impressão digital. Quando você precisa de uma segunda forma de autenticação, a segurança é aprimorada. O fator adicional não é facilmente obtido ou duplicado por um ciberatacante.

Este artigo abrange as seguintes áreas:

  • O que é MFA e quais fluxos de MFA são recomendados
  • Configurar MFA para páginas de administração usando ASP.NET Core Identity
  • Enviar requisito de login MFA para o servidor OpenID Connect
  • Forçar cliente ASP.NET Core OpenID Connect a exigir autenticação multifator

MFA (Autenticação Multifator), 2FA (Autenticação de Dois Fatores)

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

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

2FA é suportado por padrão ao usar ASP.NET Core Identity. Para habilitar ou desabilitar o 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 o 2FA.

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

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

  • Microsoft Authenticator
  • Autenticador do Google

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

Para desativar o suporte para MFA TOTP, configure a autenticação usando AddIdentity em vez de AddDefaultIdentity. AddDefaultIdentity chama AddDefaultTokenProviders internamente, que regista múltiplos fornecedores de tokens, incluindo um para MFA TOTP. 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 os métodos de extensão em IdentityBuilderExtensions.

Chaves de acesso MFA/FIDO2 ou sem senha

passkeys/FIDO2 é atualmente:

  • A forma mais segura de alcançar a AMF.
  • MFA que protege contra ataques de phishing. (Bem como autenticação de certificado e Windows para empresas)

No momento, o ASP.NET Core não suporta chaves de acesso/FIDO2 diretamente. Passkeys/FIDO2 podem ser usados para MFA ou fluxos sem senha.

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

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

Autenticação Multi-Fator (MFA) via SMS

MFA com SMS aumenta a segurança maciçamente em comparação com a autenticação de senha (fator único). No entanto, o uso do 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 MFA para páginas de administração usando ASP.NET Core Identity

A autenticação multifator pode ser obrigatória para os utilizadores acederem a páginas sensíveis dentro de uma aplicação ASP.NET Core Identity. Isso pode ser útil para aplicativos onde existem diferentes níveis de acesso para as diferentes identidades. Por exemplo, os usuários podem visualizar os dados do perfil usando um logon de senha, mas um administrador seria obrigado a usar MFA para acessar as páginas administrativas.

Estender o login com uma declaração de MFA

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

Advertência

Este artigo mostra o uso de cadeias de conexão. Quando uma base de dados local é usada para desenvolvimento e testes, a autenticação do utilizador da base de dados através da cadeia de ligação não é necessária. Em ambientes de produção, as strings de ligação por vezes incluem uma palavra-passe para autenticar o acesso ou operações da base de dados. Uma credencial de palavra-passe do proprietário do recurso (ROPC) numa cadeia de ligação constitui um risco de segurança e deve ser evitada em aplicações em produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para mais informações sobre autenticação para aplicações implementadas em ambientes de teste ou produção, consulte ASP.NET tópicos de segurança Core.

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 de usuário somente após um login bem-sucedido. O valor da reclamação é lido da base de dados. A declaração é adicionada aqui porque o utilizador só deve acessar a vista protegida avançada se a identidade tiver feito login com MFA. Se a exibição do banco de dados for lida diretamente do banco de dados em vez de usar a declaração, é possível acessar a exibição sem MFA diretamente após ativar o 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. Estruture as 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 autenticação multifator (MFA) na página de administração

A página de administração Razor valida que o utilizador fez login usando MFA. No método OnGet, a identidade é usada para acessar as declarações de usuário. A reclamação amr é verificada para o valor mfa. Se a identidade não contiver esta declaração ou estiver false, a página redirecionará para a página Ativar MFA. Isso é possível porque o usuário já fez login, 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 para gerir as informações de login do utilizador

Uma política de autorização foi adicionada na inicialização. A política requer o pedido de indemnização amr com o valor mfa.

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

Essa política pode ser usada na visualização _Layout para mostrar ou ocultar o menu Admin com o aviso:

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

Se a identidade tiver efetuado login usando MFA, o menu Admin será exibido sem o aviso de *tooltip*. Quando o utilizador faz login sem MFA, o menu Admin (Não ativado) é exibido junto com a dica de ferramenta que informa o utilizador (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 login sem MFA, o aviso será exibido:

autenticação MFA do administrador

O utilizador é redirecionado para a vista de ativação de MFA ao clicar no link Admin.

O Administrator ativa a autenticação MFA

Enviar requisito de login 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 manipulado no servidor OpenID Connect para que isso funcione.

Cliente OpenID Connect ASP.NET Core

O aplicativo cliente OpenID Connect do ASP.NET Core Razor Pages usa o método AddOpenIdConnect para fazer login no servidor 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 adicionar isso.

Para obter os valores de parâmetro acr_values recomendados, consulte Valores de Referência de Métodos 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 OpenID Connect Duende IdentityServer com ASP.NET Core Identity

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

  • Exibe se o Identity vem de um aplicativo que requer MFA, mas o usuário não ativou isso no Identity.
  • Informa o usuário e adiciona um link para ativá-lo.
@{
    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 implementação da interface IIdentityServerInteractionService_interaction é usada para acessar os parâmetros de solicitação do OpenID Connect. O parâmetro acr_values é acessado usando a propriedade AcrValues. Como o cliente enviou isso com mfa definido, isso pode ser verificado.

Se a MFA for necessária e o usuário no ASP.NET Core Identity tiver a MFA habilitada, o login continuará. Quando o usuário não tem MFA habilitado, o usuário é redirecionado para o modo de exibição personalizado ErrorEnable2FA.cshtml. Em seguida, ASP.NET Core Identity inicia a sessão do utilizador.

O Fido2Store é usado para verificar se o usuário ativou o 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 alegação amr.
  • Pode configurar o MFA com um link para a vista ASP.NET Core Identity.

acr_values-1 imagem

Forçar cliente ASP.NET Core OpenID Connect a exigir autenticação multifator

Este exemplo mostra como um aplicativo ASP.NET Core Razor Page, que usa o OpenID Connect para entrar, pode exigir que os usuários tenham se autenticado usando MFA.

Para validar o requisito de MFA, é criado um requisito IAuthorizationRequirement. Isso será adicionado às páginas usando uma política que requer MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

É implementada uma AuthorizationHandler que utilizará a declaração amr 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 Authentication Method Reference Values.

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

O AuthorizationHandler usa o requisito RequireMfa e valida a reivindicação amr. O servidor OpenID Connect pode ser implementado usando o Duende Identity Server com ASP.NET Core Identity. Quando um usuário efetua login usando TOTP, a declaração amr é retornada com um valor MFA. Se estiver usando uma implementação de servidor OpenID Connect diferente ou um tipo de MFA diferente, a declaração de amr terá, ou poderá, 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 de amr, é adicionado ao contêiner Inversion of Control. 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();

Esta política é então 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 utilizador se autenticar sem MFA, a declaração amr provavelmente terá um valor pwd. O pedido não será autorizado para aceder à 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. Neste exemplo, um link é adicionado para que o usuário válido possa configurar o 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, apenas 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 2FA estiver bem, a declaração de amr terá valores diferentes e precisará ser processada corretamente. Diferentes servidores 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 login sem MFA (por exemplo, usando apenas uma senha):

  • O amr tem o pwd valor:

    AMR tem o valor de pwd

  • O acesso é negado:

    Acesso negado

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

Fazer login usando OTP com Identity

Recursos adicionais

Por Damien Bowden

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

A autenticação multifator (MFA) é um processo no qual um usuário é solicitado durante um evento de entrada para formas adicionais de identificação. Esse prompt pode ser para inserir um código de um telemóvel, usar uma chave FIDO2 ou fornecer uma leitura de impressão digital. Quando você precisa de uma segunda forma de autenticação, a segurança é aprimorada. O fator adicional não é facilmente obtido ou duplicado por um ciberatacante.

Este artigo abrange as seguintes áreas:

  • O que é MFA e quais fluxos de MFA são recomendados
  • Configurar MFA para páginas de administração usando ASP.NET Core Identity
  • Enviar requisito de login MFA para o servidor OpenID Connect
  • Forçar cliente ASP.NET Core OpenID Connect a exigir autenticação multifator

MFA (Autenticação Multifator), 2FA (Autenticação de Dois Fatores)

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

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

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

MFA usando TOTP é uma implementação suportada usando ASP.NET Core Identity. Isso pode ser usado em conjunto com qualquer aplicativo autenticador compatível, incluindo:

  • Aplicativo Microsoft Authenticator
  • Aplicação Google Authenticator

Consulte o link a seguir para obter detalhes sobre a implementação:

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

Chaves de acesso MFA/FIDO2 ou sem senha

passkeys/FIDO2 é atualmente:

  • A forma mais segura de alcançar a AMF.
  • MFA que protege contra ataques de phishing. (Bem como autenticação de certificado e Windows para empresas)

No momento, o ASP.NET Core não suporta chaves de acesso/FIDO2 diretamente. Passkeys/FIDO2 podem ser usados para MFA ou fluxos sem senha.

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

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

Autenticação Multi-Fator (MFA) via SMS

MFA com SMS aumenta a segurança maciçamente em comparação com a autenticação de senha (fator único). No entanto, o uso do 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 MFA para páginas de administração usando ASP.NET Core Identity

A autenticação multifator pode ser obrigatória para os utilizadores acederem a páginas sensíveis dentro de uma aplicação ASP.NET Core Identity. Isso pode ser útil para aplicativos onde existem diferentes níveis de acesso para as diferentes identidades. Por exemplo, os usuários podem visualizar os dados do perfil usando um logon de senha, mas um administrador seria obrigado a usar MFA para acessar as páginas administrativas.

Estender o login com uma declaração de MFA

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

Advertência

Este artigo mostra o uso de cadeias de conexão. Quando uma base de dados local é usada para desenvolvimento e testes, a autenticação do utilizador da base de dados através da cadeia de ligação não é necessária. Em ambientes de produção, as strings de ligação por vezes incluem uma palavra-passe para autenticar o acesso ou operações da base de dados. Uma credencial de palavra-passe do proprietário do recurso (ROPC) numa cadeia de ligação constitui um risco de segurança e deve ser evitada em aplicações em produção. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para mais informações sobre autenticação para aplicações implementadas em ambientes de teste ou produção, consulte ASP.NET tópicos de segurança Core.

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 de usuário somente após um login bem-sucedido. O valor da reclamação é lido da base de dados. A declaração é adicionada aqui porque o utilizador só deve acessar a vista protegida avançada se a identidade tiver feito login com MFA. Se a exibição do banco de dados for lida diretamente do banco de dados em vez de usar a declaração, é possível acessar a exibição sem MFA diretamente após ativar o 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. Estruture as 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 autenticação multifator (MFA) na página de administração

A página de administração Razor valida que o utilizador fez login usando MFA. No método OnGet, a identidade é usada para acessar as declarações de usuário. A reclamação amr é verificada para o valor mfa. Se a identidade não contiver esta declaração ou estiver false, a página redirecionará para a página Ativar MFA. Isso é possível porque o usuário já fez login, 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 para gerir as informações de login do utilizador

Uma política de autorização foi adicionada no arquivo de programa. A política requer o pedido de indemnização amr com o valor mfa.

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

Essa política pode ser usada na visualização _Layout para mostrar ou ocultar o menu Admin com o aviso:

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

Se a identidade tiver efetuado login usando MFA, o menu Admin será exibido sem o aviso de *tooltip*. Quando o utilizador faz login sem MFA, o menu Admin (Não ativado) é exibido junto com a dica de ferramenta que informa o utilizador (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 login sem MFA, o aviso será exibido:

autenticação MFA do administrador

O utilizador é redirecionado para a vista de ativação de MFA ao clicar no link Admin.

O Administrator ativa a autenticação MFA

Enviar requisito de login 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 manipulado no servidor OpenID Connect para que isso funcione.

Cliente OpenID Connect ASP.NET Core

O aplicativo cliente OpenID Connect do ASP.NET Core Razor Pages usa o método AddOpenIdConnect para fazer login no servidor 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 adicionar isso.

Para obter os valores de parâmetro acr_values recomendados, consulte Valores de Referência de Métodos 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 OpenID Connect IdentityServer 4 com ASP.NET Core Identity

No servidor OpenID Connect, que é implementado usando ASP.NET Core Identity com visualizações MVC, uma nova exibição chamada ErrorEnable2FA.cshtml é criada. A vista:

  • Exibe se o Identity vem de um aplicativo que requer MFA, mas o usuário não ativou isso no Identity.
  • Informa o usuário e adiciona um link para ativá-lo.
@{
    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 implementação da interface IIdentityServerInteractionService_interaction é usada para acessar os parâmetros de solicitação do OpenID Connect. O parâmetro acr_values é acessado usando a propriedade AcrValues. Como o cliente enviou isso com mfa definido, isso pode ser verificado.

Se a MFA for necessária e o usuário no ASP.NET Core Identity tiver a MFA habilitada, o login continuará. Quando o usuário não tem MFA habilitado, o usuário é redirecionado para o modo de exibição personalizado ErrorEnable2FA.cshtml. Em seguida, ASP.NET Core Identity inicia a sessão do utilizador.

//
// 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 login Identity local. A propriedade AcrValues é verificada para o valor mfa. Se o valor mfa estiver presente, o MFA será necessário antes de concluir o login (por exemplo, redirecionado para a vista 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 alegação amr.
  • Pode configurar o MFA com um link para a vista ASP.NET Core Identity.

acr_values-1 imagem

Forçar cliente ASP.NET Core OpenID Connect a exigir autenticação multifator

Este exemplo mostra como um aplicativo ASP.NET Core Razor Page, que usa o OpenID Connect para entrar, pode exigir que os usuários tenham se autenticado usando MFA.

Para validar o requisito de MFA, é criado um requisito IAuthorizationRequirement. Isso será adicionado às páginas usando uma política que requer MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

É implementada uma AuthorizationHandler que utilizará a declaração amr 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 Authentication Method Reference Values.

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

O AuthorizationHandler usa o requisito RequireMfa e valida a reivindicação amr. O servidor OpenID Connect pode ser implementado usando IdentityServer4 com ASP.NET Core Identity. Quando um usuário efetua login usando TOTP, a declaração amr é retornada com um valor MFA. Se estiver usando uma implementação de servidor OpenID Connect diferente ou um tipo de MFA diferente, a declaração de amr terá, ou poderá, 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 de amr, é adicionado ao contêiner Inversion of Control. 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();
}

Esta política é então 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 utilizador se autenticar sem MFA, a declaração amr provavelmente terá um valor pwd. O pedido não será autorizado para aceder à 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. Neste exemplo, um link é adicionado para que o usuário válido possa configurar o 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, apenas 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 2FA estiver bem, a declaração de amr terá valores diferentes e precisará ser processada corretamente. Diferentes servidores 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 login sem MFA (por exemplo, usando apenas uma senha):

  • O amr tem o pwd valor:

    AMR tem o valor de pwd

  • O acesso é negado:

    Acesso negado

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

Fazer login usando OTP com Identity

Recursos adicionais