Bagikan melalui


Autentikasi multifaktor di ASP.NET Core

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh Damien Bowden

Melihat atau mengunduh kode sampel (repositori GitHub damienbod/AspNetCoreHybridFlowWithApi)

Autentikasi multifaktor (MFA) adalah proses di mana pengguna diminta selama peristiwa masuk untuk bentuk identifikasi tambahan. Perintah ini bisa untuk memasukkan kode dari ponsel, menggunakan kunci FIDO2, atau untuk memberikan pemindaian sidik jari. Ketika Anda memerlukan bentuk autentikasi kedua, keamanan ditingkatkan. Faktor tambahan tidak mudah diperoleh atau diduplikasi oleh penyerang.

Artikel ini membahas area berikut:

  • Apa itu MFA dan alur MFA apa yang direkomendasikan
  • Mengonfigurasi MFA untuk halaman administrasi menggunakan ASP.NET Core Identity
  • Mengirim persyaratan masuk MFA ke server OpenID Connect
  • Paksa ASP.NET klien Core OpenID Connect untuk mewajibkan MFA

MFA, 2FA

MFA memerlukan setidaknya dua jenis bukti atau lebih untuk identitas seperti sesuatu yang Anda ketahui, sesuatu yang Anda miliki, atau validasi biometrik bagi pengguna untuk diautentikasi.

Autentikasi dua faktor (2FA) seperti subset MFA, tetapi perbedaannya adalah bahwa MFA dapat memerlukan dua faktor atau lebih untuk membuktikan identitas.

2FA didukung secara default saat menggunakan ASP.NET Core Identity. Untuk mengaktifkan atau menonaktifkan 2FA untuk pengguna tertentu, atur IdentityUser<TKey>.TwoFactorEnabled properti . UI Default ASP.NET Core Identity menyertakan halaman untuk mengonfigurasi 2FA.

MFA TOTP (Algoritma Kata Sandi Satu Kali Berbasis Waktu)

MFA yang menggunakan TOTP didukung secara default saat menggunakan ASP.NET Core Identity. Pendekatan ini dapat digunakan bersama dengan aplikasi pengautentikasi yang sesuai, termasuk:

  • Microsoft Authenticator
  • Google Authenticator

Untuk detail implementasi, lihat Mengaktifkan pembuatan Kode QR untuk aplikasi pengautentikasi TOTP di ASP.NET Core.

Untuk menonaktifkan dukungan untuk MFA TOTP, konfigurasikan autentikasi menggunakan AddIdentity alih-alih AddDefaultIdentity. AddDefaultIdentityAddDefaultTokenProviders memanggil secara internal, yang mendaftarkan beberapa penyedia token termasuk satu untuk MFA TOTP. Untuk mendaftarkan hanya penyedia token tertentu, panggil AddTokenProvider untuk setiap penyedia yang diperlukan. Untuk informasi selengkapnya tentang penyedia token yang tersedia, lihat sumber AddDefaultTokenProviders di GitHub.

Kode akses MFA/FIDO2 atau tanpa kata sandi

passkeys/FIDO2 saat ini:

  • Cara paling aman untuk mencapai MFA.
  • MFA yang melindungi dari serangan phishing. (Serta autentikasi sertifikat dan Windows untuk bisnis)

Saat ini, ASP.NET Core tidak mendukung passkeys/FIDO2 secara langsung. Passkeys/FIDO2 dapat digunakan untuk alur MFA atau tanpa kata sandi.

MICROSOFT Entra ID menyediakan dukungan untuk passkeys/FIDO2 dan alur tanpa kata sandi. Untuk informasi selengkapnya, lihat Opsi autentikasi tanpa kata sandi.

Bentuk lain dari MFA tanpa kata sandi tidak atau mungkin tidak melindungi dari phishing.

MFA SMS

MFA dengan SMS meningkatkan keamanan secara besar-besaran dibandingkan dengan autentikasi kata sandi (faktor tunggal). Namun, menggunakan SMS sebagai faktor kedua tidak lagi direkomendasikan. Terlalu banyak vektor serangan yang diketahui ada untuk jenis implementasi ini.

Panduan NIST

Mengonfigurasi MFA untuk halaman administrasi menggunakan ASP.NET Core Identity

MFA dapat dipaksa pada pengguna untuk mengakses halaman sensitif dalam aplikasi ASP.NET Core Identity . Ini bisa berguna untuk aplikasi di mana berbagai tingkat akses ada untuk identitas yang berbeda. Misalnya, pengguna mungkin dapat melihat data profil menggunakan login kata sandi, tetapi administrator akan diminta untuk menggunakan MFA untuk mengakses halaman administratif.

Memperpanjang login dengan klaim MFA

Kode demo disiapkan menggunakan ASP.NET Core dengan Identity dan Razor Pages. Metode AddIdentity ini digunakan sebagai gantinya AddDefaultIdentity , sehingga IUserClaimsPrincipalFactory implementasi dapat digunakan untuk menambahkan klaim ke identitas setelah berhasil masuk.

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

Kelas AdditionalUserClaimsPrincipalFactory menambahkan amr klaim ke klaim pengguna hanya setelah berhasil masuk. Nilai klaim dibaca dari database. Klaim ditambahkan di sini karena pengguna hanya boleh mengakses tampilan yang dilindungi yang lebih tinggi jika identitas telah masuk dengan MFA. Jika tampilan database dibaca dari database secara langsung alih-alih menggunakan klaim, dimungkinkan untuk mengakses tampilan tanpa MFA langsung setelah mengaktifkan MFA.

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

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

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

            var claims = new List<Claim>();

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

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

Identity Karena pengaturan layanan berubah di Startup kelas, tata letak Identity perlu diperbarui. Perancah Identity halaman ke dalam aplikasi. Tentukan tata letak dalam Identity/Account/Manage/_Layout.cshtml file.

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

Tetapkan juga tata letak untuk semua halaman kelola dari Identity halaman:

@{
    Layout = "_Layout.cshtml";
}

Memvalidasi persyaratan MFA di halaman administrasi

Halaman administrasi Razor memvalidasi bahwa pengguna telah masuk menggunakan MFA. Dalam metode ini OnGet , identitas digunakan untuk mengakses klaim pengguna. Klaim amr diperiksa untuk nilai mfa. Jika identitas tidak memiliki klaim ini atau , falsehalaman akan dialihkan ke halaman Aktifkan MFA. Ini dimungkinkan karena pengguna telah masuk, tetapi tanpa 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();
        }
    }
}

Logika UI untuk mengalihkan informasi masuk pengguna

Kebijakan otorisasi ditambahkan saat startup. Kebijakan memerlukan amr klaim dengan nilai mfa.

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

Kebijakan ini kemudian dapat digunakan dalam _Layout tampilan untuk menampilkan atau menyembunyikan menu Admin dengan peringatan:

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

Jika identitas telah masuk menggunakan MFA, menu Admin ditampilkan tanpa peringatan tipsalat. Ketika pengguna telah masuk tanpa MFA, menu Admin (Tidak Diaktifkan) ditampilkan bersama dengan tipsalat yang memberi tahu pengguna (menjelaskan peringatan).

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

Jika pengguna masuk tanpa MFA, peringatan akan ditampilkan:

Autentikasi MFA Administrator

Pengguna dialihkan ke tampilan aktifkan MFA saat mengklik tautan Admin :

Administrator mengaktifkan autentikasi MFA

Mengirim persyaratan masuk MFA ke server OpenID Connect

Parameter acr_values dapat digunakan untuk meneruskan nilai yang mfa diperlukan dari klien ke server dalam permintaan autentikasi.

Catatan

Parameter acr_values perlu ditangani pada server OpenID Connect agar ini berfungsi.

Klien OpenID Connect ASP.NET Core

Aplikasi klien OpenID Connect Halaman Inti Razor ASP.NET menggunakan AddOpenIdConnect metode untuk masuk ke server OpenID Connect. Parameter acr_values diatur dengan mfa nilai dan dikirim dengan permintaan autentikasi. OpenIdConnectEvents digunakan untuk menambahkan ini.

Untuk nilai parameter yang direkomendasikan acr_values , lihat Nilai Referensi Metode Autentikasi.

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

Contoh server OpenID Connect Duende IdentityServer dengan ASP.NET Core Identity

Pada server OpenID Connect, yang diimplementasikan menggunakan ASP.NET Core Identity dengan Razor Pages, halaman baru bernama ErrorEnable2FA.cshtml dibuat. Tampilan:

  • Menampilkan jika Identity berasal dari aplikasi yang memerlukan MFA tetapi pengguna belum mengaktifkan ini di Identity.
  • Memberi tahu pengguna dan menambahkan tautan untuk mengaktifkan ini.
@{
    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>

Dalam metode ini Login , IIdentityServerInteractionService implementasi _interaction antarmuka digunakan untuk mengakses parameter permintaan OpenID Connect. Parameter acr_values diakses menggunakan AcrValues properti . Saat klien mengirim ini dengan mfa set, ini kemudian dapat diperiksa.

Jika MFA diperlukan, dan pengguna di ASP.NET Core Identity mengaktifkan MFA, maka login berlanjut. Ketika pengguna tidak mengaktifkan MFA, pengguna dialihkan ke tampilan ErrorEnable2FA.cshtmlkustom . Kemudian ASP.NET Core Identity memasukkan pengguna.

Fido2Store digunakan untuk memeriksa apakah pengguna telah mengaktifkan MFA menggunakan Penyedia Token FIDO2 kustom.

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

Jika pengguna sudah masuk, aplikasi klien:

  • Masih memvalidasi amr klaim.
  • Dapat menyiapkan MFA dengan tautan ke tampilan ASP.NET Core Identity .

gambar acr_values-1

Paksa ASP.NET klien Core OpenID Connect untuk mewajibkan MFA

Contoh ini menunjukkan bagaimana aplikasi ASP.NET Core Razor Page, yang menggunakan OpenID Connect untuk masuk, dapat mengharuskan pengguna telah mengautentikasi menggunakan MFA.

Untuk memvalidasi persyaratan MFA, IAuthorizationRequirement persyaratan dibuat. Ini akan ditambahkan ke halaman menggunakan kebijakan yang memerlukan MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Diimplementasikan AuthorizationHandler yang akan menggunakan amr klaim dan memeriksa nilai mfa. amr dikembalikan dalam id_token autentikasi yang berhasil dan dapat memiliki banyak nilai yang berbeda seperti yang didefinisikan dalam spesifikasi Nilai Referensi Metode Autentikasi.

Nilai yang dikembalikan tergantung pada bagaimana identitas diautentikasi dan pada implementasi server OpenID Connect.

menggunakan AuthorizationHandlerRequireMfa persyaratan dan memvalidasi amr klaim. Server OpenID Connect dapat diimplementasikan menggunakan Duende Identity Server dengan ASP.NET Core Identity. Saat pengguna masuk menggunakan TOTP, amr klaim dikembalikan dengan nilai MFA. Jika menggunakan implementasi server OpenID Connect yang berbeda atau jenis MFA yang berbeda, amr klaim akan, atau dapat, memiliki nilai yang berbeda. Kode harus diperluas untuk menerima ini juga.

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

Dalam file program, AddOpenIdConnect metode digunakan sebagai skema tantangan default. Handler otorisasi, yang digunakan untuk memeriksa amr klaim, ditambahkan ke kontainer Inversion of Control. Kebijakan kemudian dibuat yang menambahkan RequireMfa persyaratan.

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

Kebijakan ini kemudian digunakan di Razor halaman sesuai kebutuhan. Kebijakan ini juga dapat ditambahkan secara global untuk seluruh aplikasi.

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

Jika pengguna mengautentikasi tanpa MFA, amr klaim mungkin akan memiliki pwd nilai. Permintaan tidak akan diotorisasi untuk mengakses halaman. Dengan menggunakan nilai default, pengguna akan diarahkan ke halaman Akun/AccessDenied . Perilaku ini dapat diubah atau Anda dapat menerapkan logika kustom Anda sendiri di sini. Dalam contoh ini, tautan ditambahkan sehingga pengguna yang valid dapat menyiapkan MFA untuk akun mereka.

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

Sekarang hanya pengguna yang mengautentikasi dengan MFA yang dapat mengakses halaman atau situs web. Jika jenis MFA yang berbeda digunakan atau jika 2FA baik-baik saja, amr klaim akan memiliki nilai yang berbeda dan perlu diproses dengan benar. Server OpenID Connect yang berbeda juga mengembalikan nilai yang berbeda untuk klaim ini dan mungkin tidak mengikuti spesifikasi Nilai Referensi Metode Autentikasi.

Saat masuk tanpa MFA (misalnya, hanya menggunakan kata sandi):

  • memiliki amrpwd nilai:

    amr memiliki nilai pwd

  • Akses ditolak:

    Akses ditolak

Atau, masuk menggunakan OTP dengan Identity:

Masuk menggunakan OTP dengan Identity

Sumber Daya Tambahan:

Oleh Damien Bowden

Melihat atau mengunduh kode sampel (repositori GitHub damienbod/AspNetCoreHybridFlowWithApi)

Autentikasi multifaktor (MFA) adalah proses di mana pengguna diminta selama peristiwa masuk untuk bentuk identifikasi tambahan. Perintah ini bisa untuk memasukkan kode dari ponsel, menggunakan kunci FIDO2, atau untuk memberikan pemindaian sidik jari. Ketika Anda memerlukan bentuk autentikasi kedua, keamanan ditingkatkan. Faktor tambahan tidak mudah diperoleh atau diduplikasi oleh penyerang.

Artikel ini membahas area berikut:

  • Apa itu MFA dan alur MFA apa yang direkomendasikan
  • Mengonfigurasi MFA untuk halaman administrasi menggunakan ASP.NET Core Identity
  • Mengirim persyaratan masuk MFA ke server OpenID Connect
  • Paksa ASP.NET klien Core OpenID Connect untuk mewajibkan MFA

MFA, 2FA

MFA memerlukan setidaknya dua jenis bukti atau lebih untuk identitas seperti sesuatu yang Anda ketahui, sesuatu yang Anda miliki, atau validasi biometrik bagi pengguna untuk diautentikasi.

Autentikasi dua faktor (2FA) seperti subset MFA, tetapi perbedaannya adalah bahwa MFA dapat memerlukan dua faktor atau lebih untuk membuktikan identitas.

2FA didukung secara default saat menggunakan ASP.NET Core Identity. Untuk mengaktifkan atau menonaktifkan 2FA untuk pengguna tertentu, atur IdentityUser<TKey>.TwoFactorEnabled properti . UI Default ASP.NET Core Identity menyertakan halaman untuk mengonfigurasi 2FA.

MFA TOTP (Algoritma Kata Sandi Satu Kali Berbasis Waktu)

MFA yang menggunakan TOTP didukung secara default saat menggunakan ASP.NET Core Identity. Pendekatan ini dapat digunakan bersama dengan aplikasi pengautentikasi yang sesuai, termasuk:

  • Microsoft Authenticator
  • Google Authenticator

Untuk detail implementasi, lihat Mengaktifkan pembuatan Kode QR untuk aplikasi pengautentikasi TOTP di ASP.NET Core.

Untuk menonaktifkan dukungan untuk MFA TOTP, konfigurasikan autentikasi menggunakan AddIdentity alih-alih AddDefaultIdentity. AddDefaultIdentityAddDefaultTokenProviders memanggil secara internal, yang mendaftarkan beberapa penyedia token termasuk satu untuk MFA TOTP. Untuk mendaftarkan hanya penyedia token tertentu, panggil AddTokenProvider untuk setiap penyedia yang diperlukan. Untuk informasi selengkapnya tentang penyedia token yang tersedia, lihat sumber AddDefaultTokenProviders di GitHub.

Kode akses MFA/FIDO2 atau tanpa kata sandi

passkeys/FIDO2 saat ini:

  • Cara paling aman untuk mencapai MFA.
  • MFA yang melindungi dari serangan phishing. (Serta autentikasi sertifikat dan Windows untuk bisnis)

Saat ini, ASP.NET Core tidak mendukung passkeys/FIDO2 secara langsung. Passkeys/FIDO2 dapat digunakan untuk alur MFA atau tanpa kata sandi.

MICROSOFT Entra ID menyediakan dukungan untuk passkeys/FIDO2 dan alur tanpa kata sandi. Untuk informasi selengkapnya, lihat Opsi autentikasi tanpa kata sandi.

Bentuk lain dari MFA tanpa kata sandi tidak atau mungkin tidak melindungi dari phishing.

MFA SMS

MFA dengan SMS meningkatkan keamanan secara besar-besaran dibandingkan dengan autentikasi kata sandi (faktor tunggal). Namun, menggunakan SMS sebagai faktor kedua tidak lagi direkomendasikan. Terlalu banyak vektor serangan yang diketahui ada untuk jenis implementasi ini.

Panduan NIST

Mengonfigurasi MFA untuk halaman administrasi menggunakan ASP.NET Core Identity

MFA dapat dipaksa pada pengguna untuk mengakses halaman sensitif dalam aplikasi ASP.NET Core Identity . Ini bisa berguna untuk aplikasi di mana berbagai tingkat akses ada untuk identitas yang berbeda. Misalnya, pengguna mungkin dapat melihat data profil menggunakan login kata sandi, tetapi administrator akan diminta untuk menggunakan MFA untuk mengakses halaman administratif.

Memperpanjang login dengan klaim MFA

Kode demo disiapkan menggunakan ASP.NET Core dengan Identity dan Razor Pages. Metode AddIdentity ini digunakan sebagai gantinya AddDefaultIdentity , sehingga IUserClaimsPrincipalFactory implementasi dapat digunakan untuk menambahkan klaim ke identitas setelah berhasil masuk.

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

Kelas AdditionalUserClaimsPrincipalFactory menambahkan amr klaim ke klaim pengguna hanya setelah berhasil masuk. Nilai klaim dibaca dari database. Klaim ditambahkan di sini karena pengguna hanya boleh mengakses tampilan yang dilindungi yang lebih tinggi jika identitas telah masuk dengan MFA. Jika tampilan database dibaca dari database secara langsung alih-alih menggunakan klaim, dimungkinkan untuk mengakses tampilan tanpa MFA langsung setelah mengaktifkan MFA.

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

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

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

            var claims = new List<Claim>();

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

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

Identity Karena pengaturan layanan berubah di Startup kelas, tata letak Identity perlu diperbarui. Perancah Identity halaman ke dalam aplikasi. Tentukan tata letak dalam Identity/Account/Manage/_Layout.cshtml file.

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

Tetapkan juga tata letak untuk semua halaman kelola dari Identity halaman:

@{
    Layout = "_Layout.cshtml";
}

Memvalidasi persyaratan MFA di halaman administrasi

Halaman administrasi Razor memvalidasi bahwa pengguna telah masuk menggunakan MFA. Dalam metode ini OnGet , identitas digunakan untuk mengakses klaim pengguna. Klaim amr diperiksa untuk nilai mfa. Jika identitas tidak memiliki klaim ini atau , falsehalaman akan dialihkan ke halaman Aktifkan MFA. Ini dimungkinkan karena pengguna telah masuk, tetapi tanpa 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();
        }
    }
}

Logika UI untuk mengalihkan informasi masuk pengguna

Kebijakan otorisasi ditambahkan saat startup. Kebijakan memerlukan amr klaim dengan nilai mfa.

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

Kebijakan ini kemudian dapat digunakan dalam _Layout tampilan untuk menampilkan atau menyembunyikan menu Admin dengan peringatan:

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

Jika identitas telah masuk menggunakan MFA, menu Admin ditampilkan tanpa peringatan tipsalat. Ketika pengguna telah masuk tanpa MFA, menu Admin (Tidak Diaktifkan) ditampilkan bersama dengan tipsalat yang memberi tahu pengguna (menjelaskan peringatan).

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

Jika pengguna masuk tanpa MFA, peringatan akan ditampilkan:

Autentikasi MFA Administrator

Pengguna dialihkan ke tampilan aktifkan MFA saat mengklik tautan Admin :

Administrator mengaktifkan autentikasi MFA

Mengirim persyaratan masuk MFA ke server OpenID Connect

Parameter acr_values dapat digunakan untuk meneruskan nilai yang mfa diperlukan dari klien ke server dalam permintaan autentikasi.

Catatan

Parameter acr_values perlu ditangani pada server OpenID Connect agar ini berfungsi.

Klien OpenID Connect ASP.NET Core

Aplikasi klien OpenID Connect Halaman Inti Razor ASP.NET menggunakan AddOpenIdConnect metode untuk masuk ke server OpenID Connect. Parameter acr_values diatur dengan mfa nilai dan dikirim dengan permintaan autentikasi. OpenIdConnectEvents digunakan untuk menambahkan ini.

Untuk nilai parameter yang direkomendasikan acr_values , lihat Nilai Referensi Metode Autentikasi.

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

Contoh server OpenID Connect Duende IdentityServer dengan ASP.NET Core Identity

Pada server OpenID Connect, yang diimplementasikan menggunakan ASP.NET Core Identity dengan Razor Pages, halaman baru bernama ErrorEnable2FA.cshtml dibuat. Tampilan:

  • Menampilkan jika Identity berasal dari aplikasi yang memerlukan MFA tetapi pengguna belum mengaktifkan ini di Identity.
  • Memberi tahu pengguna dan menambahkan tautan untuk mengaktifkan ini.
@{
    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>

Dalam metode ini Login , IIdentityServerInteractionService implementasi _interaction antarmuka digunakan untuk mengakses parameter permintaan OpenID Connect. Parameter acr_values diakses menggunakan AcrValues properti . Saat klien mengirim ini dengan mfa set, ini kemudian dapat diperiksa.

Jika MFA diperlukan, dan pengguna di ASP.NET Core Identity mengaktifkan MFA, maka login berlanjut. Ketika pengguna tidak mengaktifkan MFA, pengguna dialihkan ke tampilan ErrorEnable2FA.cshtmlkustom . Kemudian ASP.NET Core Identity memasukkan pengguna.

Fido2Store digunakan untuk memeriksa apakah pengguna telah mengaktifkan MFA menggunakan Penyedia Token FIDO2 kustom.

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

Jika pengguna sudah masuk, aplikasi klien:

  • Masih memvalidasi amr klaim.
  • Dapat menyiapkan MFA dengan tautan ke tampilan ASP.NET Core Identity .

gambar acr_values-1

Paksa ASP.NET klien Core OpenID Connect untuk mewajibkan MFA

Contoh ini menunjukkan bagaimana aplikasi ASP.NET Core Razor Page, yang menggunakan OpenID Connect untuk masuk, dapat mengharuskan pengguna telah mengautentikasi menggunakan MFA.

Untuk memvalidasi persyaratan MFA, IAuthorizationRequirement persyaratan dibuat. Ini akan ditambahkan ke halaman menggunakan kebijakan yang memerlukan MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Diimplementasikan AuthorizationHandler yang akan menggunakan amr klaim dan memeriksa nilai mfa. amr dikembalikan dalam id_token autentikasi yang berhasil dan dapat memiliki banyak nilai yang berbeda seperti yang didefinisikan dalam spesifikasi Nilai Referensi Metode Autentikasi.

Nilai yang dikembalikan tergantung pada bagaimana identitas diautentikasi dan pada implementasi server OpenID Connect.

menggunakan AuthorizationHandlerRequireMfa persyaratan dan memvalidasi amr klaim. Server OpenID Connect dapat diimplementasikan menggunakan Duende Identity Server dengan ASP.NET Core Identity. Saat pengguna masuk menggunakan TOTP, amr klaim dikembalikan dengan nilai MFA. Jika menggunakan implementasi server OpenID Connect yang berbeda atau jenis MFA yang berbeda, amr klaim akan, atau dapat, memiliki nilai yang berbeda. Kode harus diperluas untuk menerima ini juga.

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

Dalam file program, AddOpenIdConnect metode digunakan sebagai skema tantangan default. Handler otorisasi, yang digunakan untuk memeriksa amr klaim, ditambahkan ke kontainer Inversion of Control. Kebijakan kemudian dibuat yang menambahkan RequireMfa persyaratan.

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

Kebijakan ini kemudian digunakan di Razor halaman sesuai kebutuhan. Kebijakan ini juga dapat ditambahkan secara global untuk seluruh aplikasi.

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

Jika pengguna mengautentikasi tanpa MFA, amr klaim mungkin akan memiliki pwd nilai. Permintaan tidak akan diotorisasi untuk mengakses halaman. Dengan menggunakan nilai default, pengguna akan diarahkan ke halaman Akun/AccessDenied . Perilaku ini dapat diubah atau Anda dapat menerapkan logika kustom Anda sendiri di sini. Dalam contoh ini, tautan ditambahkan sehingga pengguna yang valid dapat menyiapkan MFA untuk akun mereka.

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

Sekarang hanya pengguna yang mengautentikasi dengan MFA yang dapat mengakses halaman atau situs web. Jika jenis MFA yang berbeda digunakan atau jika 2FA baik-baik saja, amr klaim akan memiliki nilai yang berbeda dan perlu diproses dengan benar. Server OpenID Connect yang berbeda juga mengembalikan nilai yang berbeda untuk klaim ini dan mungkin tidak mengikuti spesifikasi Nilai Referensi Metode Autentikasi.

Saat masuk tanpa MFA (misalnya, hanya menggunakan kata sandi):

  • memiliki amrpwd nilai:

    amr memiliki nilai pwd

  • Akses ditolak:

    Akses ditolak

Atau, masuk menggunakan OTP dengan Identity:

Masuk menggunakan OTP dengan Identity

Sumber Daya Tambahan:

Oleh Damien Bowden

Melihat atau mengunduh kode sampel (repositori GitHub damienbod/AspNetCoreHybridFlowWithApi)

Autentikasi multifaktor (MFA) adalah proses di mana pengguna diminta selama peristiwa masuk untuk bentuk identifikasi tambahan. Perintah ini bisa untuk memasukkan kode dari ponsel, menggunakan kunci FIDO2, atau untuk memberikan pemindaian sidik jari. Ketika Anda memerlukan bentuk autentikasi kedua, keamanan ditingkatkan. Faktor tambahan tidak mudah diperoleh atau diduplikasi oleh penyerang.

Artikel ini membahas area berikut:

  • Apa itu MFA dan alur MFA apa yang direkomendasikan
  • Mengonfigurasi MFA untuk halaman administrasi menggunakan ASP.NET Core Identity
  • Mengirim persyaratan masuk MFA ke server OpenID Connect
  • Paksa ASP.NET klien Core OpenID Connect untuk mewajibkan MFA

MFA, 2FA

MFA memerlukan setidaknya dua jenis bukti atau lebih untuk identitas seperti sesuatu yang Anda ketahui, sesuatu yang Anda miliki, atau validasi biometrik bagi pengguna untuk diautentikasi.

Autentikasi dua faktor (2FA) seperti subset MFA, tetapi perbedaannya adalah bahwa MFA dapat memerlukan dua faktor atau lebih untuk membuktikan identitas.

MFA TOTP (Algoritma Kata Sandi Satu Kali Berbasis Waktu)

MFA menggunakan TOTP adalah implementasi yang didukung menggunakan ASP.NET Core Identity. Ini dapat digunakan bersama dengan aplikasi pengautentikasi yang sesuai, termasuk:

  • Aplikasi Microsoft Authenticator
  • Aplikasi Google Authenticator

Lihat tautan berikut untuk detail implementasi:

Mengaktifkan pembuatan Kode QR untuk aplikasi pengautentikasi TOTP di ASP.NET Core

Kode akses MFA/FIDO2 atau tanpa kata sandi

passkeys/FIDO2 saat ini:

  • Cara paling aman untuk mencapai MFA.
  • MFA yang melindungi dari serangan phishing. (Serta autentikasi sertifikat dan Windows untuk bisnis)

Saat ini, ASP.NET Core tidak mendukung passkeys/FIDO2 secara langsung. Passkeys/FIDO2 dapat digunakan untuk alur MFA atau tanpa kata sandi.

MICROSOFT Entra ID menyediakan dukungan untuk passkeys/FIDO2 dan alur tanpa kata sandi. Untuk informasi selengkapnya, lihat Opsi autentikasi tanpa kata sandi.

Bentuk lain dari MFA tanpa kata sandi tidak atau mungkin tidak melindungi dari phishing.

MFA SMS

MFA dengan SMS meningkatkan keamanan secara besar-besaran dibandingkan dengan autentikasi kata sandi (faktor tunggal). Namun, menggunakan SMS sebagai faktor kedua tidak lagi direkomendasikan. Terlalu banyak vektor serangan yang diketahui ada untuk jenis implementasi ini.

Panduan NIST

Mengonfigurasi MFA untuk halaman administrasi menggunakan ASP.NET Core Identity

MFA dapat dipaksa pada pengguna untuk mengakses halaman sensitif dalam aplikasi ASP.NET Core Identity . Ini bisa berguna untuk aplikasi di mana berbagai tingkat akses ada untuk identitas yang berbeda. Misalnya, pengguna mungkin dapat melihat data profil menggunakan login kata sandi, tetapi administrator akan diminta untuk menggunakan MFA untuk mengakses halaman administratif.

Memperpanjang login dengan klaim MFA

Kode demo disiapkan menggunakan ASP.NET Core dengan Identity dan Razor Pages. Metode AddIdentity ini digunakan sebagai gantinya AddDefaultIdentity , sehingga IUserClaimsPrincipalFactory implementasi dapat digunakan untuk menambahkan klaim ke identitas setelah berhasil masuk.

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

Kelas AdditionalUserClaimsPrincipalFactory menambahkan amr klaim ke klaim pengguna hanya setelah berhasil masuk. Nilai klaim dibaca dari database. Klaim ditambahkan di sini karena pengguna hanya boleh mengakses tampilan yang dilindungi yang lebih tinggi jika identitas telah masuk dengan MFA. Jika tampilan database dibaca dari database secara langsung alih-alih menggunakan klaim, dimungkinkan untuk mengakses tampilan tanpa MFA langsung setelah mengaktifkan MFA.

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

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

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

            var claims = new List<Claim>();

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

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

Identity Karena pengaturan layanan berubah di Startup kelas, tata letak Identity perlu diperbarui. Perancah Identity halaman ke dalam aplikasi. Tentukan tata letak dalam Identity/Account/Manage/_Layout.cshtml file.

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

Tetapkan juga tata letak untuk semua halaman kelola dari Identity halaman:

@{
    Layout = "_Layout.cshtml";
}

Memvalidasi persyaratan MFA di halaman administrasi

Halaman administrasi Razor memvalidasi bahwa pengguna telah masuk menggunakan MFA. Dalam metode ini OnGet , identitas digunakan untuk mengakses klaim pengguna. Klaim amr diperiksa untuk nilai mfa. Jika identitas tidak memiliki klaim ini atau , falsehalaman akan dialihkan ke halaman Aktifkan MFA. Ini dimungkinkan karena pengguna telah masuk, tetapi tanpa 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();
        }
    }
}

Logika UI untuk mengalihkan informasi masuk pengguna

Kebijakan otorisasi ditambahkan dalam file program. Kebijakan memerlukan amr klaim dengan nilai mfa.

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

Kebijakan ini kemudian dapat digunakan dalam _Layout tampilan untuk menampilkan atau menyembunyikan menu Admin dengan peringatan:

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

Jika identitas telah masuk menggunakan MFA, menu Admin ditampilkan tanpa peringatan tipsalat. Ketika pengguna telah masuk tanpa MFA, menu Admin (Tidak Diaktifkan) ditampilkan bersama dengan tipsalat yang memberi tahu pengguna (menjelaskan peringatan).

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

Jika pengguna masuk tanpa MFA, peringatan akan ditampilkan:

Autentikasi MFA Administrator

Pengguna dialihkan ke tampilan aktifkan MFA saat mengklik tautan Admin :

Administrator mengaktifkan autentikasi MFA

Mengirim persyaratan masuk MFA ke server OpenID Connect

Parameter acr_values dapat digunakan untuk meneruskan nilai yang mfa diperlukan dari klien ke server dalam permintaan autentikasi.

Catatan

Parameter acr_values perlu ditangani pada server OpenID Connect agar ini berfungsi.

Klien OpenID Connect ASP.NET Core

Aplikasi klien OpenID Connect Halaman Inti Razor ASP.NET menggunakan AddOpenIdConnect metode untuk masuk ke server OpenID Connect. Parameter acr_values diatur dengan mfa nilai dan dikirim dengan permintaan autentikasi. OpenIdConnectEvents digunakan untuk menambahkan ini.

Untuk nilai parameter yang direkomendasikan acr_values , lihat Nilai Referensi Metode Autentikasi.

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

Contoh server OpenID Connect IdentityServer 4 dengan ASP.NET Core Identity

Pada server OpenID Connect, yang diimplementasikan menggunakan ASP.NET Core Identity dengan tampilan MVC, tampilan baru bernama ErrorEnable2FA.cshtml dibuat. Tampilan:

  • Menampilkan jika Identity berasal dari aplikasi yang memerlukan MFA tetapi pengguna belum mengaktifkan ini di Identity.
  • Memberi tahu pengguna dan menambahkan tautan untuk mengaktifkan ini.
@{
    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>

Dalam metode ini Login , IIdentityServerInteractionService implementasi _interaction antarmuka digunakan untuk mengakses parameter permintaan OpenID Connect. Parameter acr_values diakses menggunakan AcrValues properti . Saat klien mengirim ini dengan mfa set, ini kemudian dapat diperiksa.

Jika MFA diperlukan, dan pengguna di ASP.NET Core Identity mengaktifkan MFA, maka login berlanjut. Ketika pengguna tidak mengaktifkan MFA, pengguna dialihkan ke tampilan ErrorEnable2FA.cshtmlkustom . Kemudian ASP.NET Core Identity memasukkan pengguna.

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

Metode ini ExternalLoginCallback berfungsi seperti login lokal Identity . Properti AcrValues diperiksa untuk nilainya mfa . mfa Jika nilai ada, MFA dipaksa sebelum login selesai (misalnya, dialihkan ke ErrorEnable2FA tampilan).

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

Jika pengguna sudah masuk, aplikasi klien:

  • Masih memvalidasi amr klaim.
  • Dapat menyiapkan MFA dengan tautan ke tampilan ASP.NET Core Identity .

gambar acr_values-1

Paksa ASP.NET klien Core OpenID Connect untuk mewajibkan MFA

Contoh ini menunjukkan bagaimana aplikasi ASP.NET Core Razor Page, yang menggunakan OpenID Connect untuk masuk, dapat mengharuskan pengguna telah mengautentikasi menggunakan MFA.

Untuk memvalidasi persyaratan MFA, IAuthorizationRequirement persyaratan dibuat. Ini akan ditambahkan ke halaman menggunakan kebijakan yang memerlukan MFA.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

Diimplementasikan AuthorizationHandler yang akan menggunakan amr klaim dan memeriksa nilai mfa. amr dikembalikan dalam id_token autentikasi yang berhasil dan dapat memiliki banyak nilai yang berbeda seperti yang didefinisikan dalam spesifikasi Nilai Referensi Metode Autentikasi.

Nilai yang dikembalikan tergantung pada bagaimana identitas diautentikasi dan pada implementasi server OpenID Connect.

menggunakan AuthorizationHandlerRequireMfa persyaratan dan memvalidasi amr klaim. Server OpenID Connect dapat diimplementasikan menggunakan IdentityServer4 dengan ASP.NET Core Identity. Saat pengguna masuk menggunakan TOTP, amr klaim dikembalikan dengan nilai MFA. Jika menggunakan implementasi server OpenID Connect yang berbeda atau jenis MFA yang berbeda, amr klaim akan, atau dapat, memiliki nilai yang berbeda. Kode harus diperluas untuk menerima ini juga.

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

Dalam metode , Startup.ConfigureServicesAddOpenIdConnect metode ini digunakan sebagai skema tantangan default. Handler otorisasi, yang digunakan untuk memeriksa amr klaim, ditambahkan ke kontainer Inversion of Control. Kebijakan kemudian dibuat yang menambahkan RequireMfa persyaratan.

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

Kebijakan ini kemudian digunakan di Razor halaman sesuai kebutuhan. Kebijakan ini juga dapat ditambahkan secara global untuk seluruh aplikasi.

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

Jika pengguna mengautentikasi tanpa MFA, amr klaim mungkin akan memiliki pwd nilai. Permintaan tidak akan diotorisasi untuk mengakses halaman. Dengan menggunakan nilai default, pengguna akan diarahkan ke halaman Akun/AccessDenied . Perilaku ini dapat diubah atau Anda dapat menerapkan logika kustom Anda sendiri di sini. Dalam contoh ini, tautan ditambahkan sehingga pengguna yang valid dapat menyiapkan MFA untuk akun mereka.

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

Sekarang hanya pengguna yang mengautentikasi dengan MFA yang dapat mengakses halaman atau situs web. Jika jenis MFA yang berbeda digunakan atau jika 2FA baik-baik saja, amr klaim akan memiliki nilai yang berbeda dan perlu diproses dengan benar. Server OpenID Connect yang berbeda juga mengembalikan nilai yang berbeda untuk klaim ini dan mungkin tidak mengikuti spesifikasi Nilai Referensi Metode Autentikasi.

Saat masuk tanpa MFA (misalnya, hanya menggunakan kata sandi):

  • memiliki amrpwd nilai:

    amr memiliki nilai pwd

  • Akses ditolak:

    Akses ditolak

Atau, masuk menggunakan OTP dengan Identity:

Masuk menggunakan OTP dengan Identity

Sumber Daya Tambahan: