Bagikan melalui


skenario keamanan tambahan ASP.NET Core Blazor WebAssembly

Catatan

Ini bukan versi terbaru dari artikel ini. 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.

Artikel ini menjelaskan skenario keamanan tambahan untuk Blazor WebAssembly aplikasi.

Melampirkan token ke permintaan keluar

AuthorizationMessageHandler digunakan DelegatingHandler untuk memproses token akses. Token diperoleh menggunakan IAccessTokenProvider layanan, yang didaftarkan oleh kerangka kerja. Jika token tidak dapat diperoleh, token AccessTokenNotAvailableException akan dilemparkan. AccessTokenNotAvailableException memiliki Redirect metode yang menavigasi untuk AccessTokenResult.InteractiveRequestUrl menggunakan yang diberikan AccessTokenResult.InteractionOptions untuk memungkinkan refresh token akses.

Untuk kenyamanan, kerangka kerja menyediakan BaseAddressAuthorizationMessageHandler alamat dasar aplikasi yang telah dikonfigurasi sebelumnya sebagai URL resmi. Token akses hanya ditambahkan saat URI permintaan berada dalam URI dasar aplikasi. Saat URI permintaan keluar tidak berada dalam URI dasar aplikasi, gunakan kelas kustom AuthorizationMessageHandler (disarankan) atau konfigurasikan AuthorizationMessageHandler.

Catatan

Selain konfigurasi aplikasi klien untuk akses API server, API server juga harus mengizinkan permintaan lintas asal (CORS) ketika klien dan server tidak berada di alamat dasar yang sama. Untuk informasi selengkapnya tentang konfigurasi CORS sisi server, lihat bagian Berbagi Sumber Daya Lintas Asal (CORS) nanti di artikel ini.

Dalam contoh berikut:

Dalam contoh berikut, HttpClientFactoryServiceCollectionExtensions.AddHttpClient adalah ekstensi di Microsoft.Extensions.Http. Tambahkan paket ke aplikasi yang belum mereferensikannya.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Untuk solusi yang dihosting Blazorberdasarkan Blazor WebAssembly templat proyek, URI permintaan berada dalam URI dasar aplikasi secara default. Oleh karena itu, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) ditetapkan ke HttpClient.BaseAddress dalam aplikasi yang dihasilkan dari templat proyek.

Yang dikonfigurasi HttpClient digunakan untuk membuat permintaan resmi menggunakan try-catch pola:

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    try
    {
        var examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

Skenario permintaan autentikasi kustom

Skenario berikut menunjukkan cara menyesuaikan permintaan autentikasi dan cara mendapatkan jalur masuk dari opsi autentikasi.

Menyesuaikan proses masuk

Kelola parameter tambahan untuk permintaan masuk dengan metode berikut satu atau beberapa kali pada instans InteractiveRequestOptionsbaru :

Dalam contoh komponen berikut LoginDisplay , parameter tambahan ditambahkan ke permintaan masuk:

  • prompt diatur ke login: Memaksa pengguna untuk memasukkan kredensial mereka pada permintaan tersebut, menimpa akses menyeluruh.
  • loginHint diatur ke peter@contoso.com: Mengisi sebelumnya bidang nama pengguna/alamat email dari halaman masuk bagi pengguna ke peter@contoso.com. Aplikasi sering menggunakan parameter ini selama autentikasi ulang, setelah mengekstrak nama pengguna dari masuk sebelumnya menggunakan preferred_username klaim.

Shared/LoginDisplay.razor:

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity?.Name!
        <button @onclick="BeginLogOut">Log out</button>
    </Authorized>
    <NotAuthorized>
        <button @onclick="BeginLogIn">Log in</button>
    </NotAuthorized>
</AuthorizeView>

@code{
    public void BeginLogOut()
    {
        Navigation.NavigateToLogout("authentication/logout");
    }

    public void BeginLogIn()
    {
        InteractiveRequestOptions requestOptions =
            new()
            {
                Interaction = InteractionType.SignIn,
                ReturnUrl = Navigation.Uri,
            };

        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");

        Navigation.NavigateToLogin("authentication/login", requestOptions);
    }
}

Untuk informasi selengkapnya, lihat sumber daya berikut:

Menyesuaikan opsi sebelum mendapatkan token secara interaktif

AccessTokenNotAvailableException Jika terjadi, kelola parameter tambahan untuk permintaan token akses idP baru dengan metode berikut satu atau beberapa kali pada instans baru :InteractiveRequestOptions

Dalam contoh berikut yang mendapatkan JSdata ON melalui API web, parameter tambahan ditambahkan ke permintaan pengalihan jika token akses tidak tersedia (AccessTokenNotAvailableException dilemparkan):

  • prompt diatur ke login: Memaksa pengguna untuk memasukkan kredensial mereka pada permintaan tersebut, menimpa akses menyeluruh.
  • loginHint diatur ke peter@contoso.com: Mengisi sebelumnya bidang nama pengguna/alamat email dari halaman masuk bagi pengguna ke peter@contoso.com. Aplikasi sering menggunakan parameter ini selama autentikasi ulang, setelah mengekstrak nama pengguna dari masuk sebelumnya menggunakan preferred_username klaim.
try
{
    var examples = await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

    ...
}
catch (AccessTokenNotAvailableException ex)
{
    ex.Redirect(requestOptions => {
        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");
    });
}

Contoh sebelumnya mengasumsikan bahwa:

Untuk informasi selengkapnya, lihat sumber daya berikut:

Menyesuaikan opsi saat menggunakan IAccessTokenProvider

Jika mendapatkan token gagal saat menggunakan IAccessTokenProvider, kelola parameter tambahan untuk permintaan token akses idP baru dengan metode berikut satu atau beberapa kali pada instans baru :InteractiveRequestOptions

Dalam contoh berikut yang mencoba mendapatkan token akses untuk pengguna, parameter tambahan ditambahkan ke permintaan masuk jika upaya untuk mendapatkan token gagal ketika TryGetToken dipanggil:

  • prompt diatur ke login: Memaksa pengguna untuk memasukkan kredensial mereka pada permintaan tersebut, menimpa akses menyeluruh.
  • loginHint diatur ke peter@contoso.com: Mengisi sebelumnya bidang nama pengguna/alamat email dari halaman masuk bagi pengguna ke peter@contoso.com. Aplikasi sering menggunakan parameter ini selama autentikasi ulang, setelah mengekstrak nama pengguna dari masuk sebelumnya menggunakan preferred_username klaim.
var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { ... }
    });

if (!tokenResult.TryGetToken(out var token))
{
    tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
    tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint", 
        "peter@contoso.com");

    Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, 
        accessTokenResult.InteractionOptions);
}

Contoh sebelumnya mengasumsikan:

Untuk informasi selengkapnya, lihat sumber daya berikut:

Keluar dengan URL pengembalian kustom

Contoh berikut mengeluarkan pengguna dan mengembalikan pengguna ke /goodbye titik akhir:

Navigation.NavigateToLogout("authentication/logout", "goodbye");

Mendapatkan jalur masuk dari opsi autentikasi

Dapatkan jalur masuk yang dikonfigurasi dari RemoteAuthenticationOptions:

var loginPath = 
    RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;

Contoh sebelumnya mengasumsikan:

Kelas kustom AuthorizationMessageHandler

Panduan di bagian ini direkomendasikan untuk aplikasi klien yang membuat permintaan keluar ke URI yang tidak berada dalam URI dasar aplikasi.

Dalam contoh berikut, kelas kustom diperluas AuthorizationMessageHandler untuk digunakan sebagai DelegatingHandler untuk HttpClient. ConfigureHandler mengonfigurasi handler ini untuk mengotorisasi permintaan HTTP keluar menggunakan token akses. Token akses hanya dilampirkan jika setidaknya salah satu URL yang diotorisasi adalah basis URI permintaan (HttpRequestMessage.RequestUri).

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigation)
        : base(provider, navigation)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://api.contoso.com/v1.0" },
            scopes: new[] { "example.read", "example.write" });
    }
}

Dalam kode sebelumnya, cakupan example.read dan example.write merupakan contoh umum yang tidak dimaksudkan untuk mencerminkan cakupan yang valid untuk penyedia tertentu.

Program Dalam file, CustomAuthorizationMessageHandler terdaftar sebagai layanan sementara dan dikonfigurasi sebagai DelegatingHandler untuk instans keluar HttpResponseMessage yang dibuat oleh bernama HttpClient.

Dalam contoh berikut, HttpClientFactoryServiceCollectionExtensions.AddHttpClient adalah ekstensi di Microsoft.Extensions.Http. Tambahkan paket ke aplikasi yang belum mereferensikannya.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Catatan

Dalam contoh sebelumnya, CustomAuthorizationMessageHandlerDelegatingHandler terdaftar sebagai layanan sementara untuk AddHttpMessageHandler. Pendaftaran sementara direkomendasikan untuk IHttpClientFactory, yang mengelola cakupan DI-nya sendiri. Untuk informasi selengkapnya, lihat sumber daya berikut:

Untuk solusi yang dihosting Blazor berdasarkan Blazor WebAssembly templat proyek, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) ditetapkan ke HttpClient.BaseAddress secara default.

Yang dikonfigurasi HttpClient digunakan untuk membuat permintaan resmi menggunakan try-catch pola . Di mana klien dibuat dengan CreateClient (Microsoft.Extensions.Http paket), HttpClient instans yang disediakan yang menyertakan token akses saat membuat permintaan ke API server. Jika URI permintaan adalah URI relatif, seperti dalam contoh berikut (ExampleAPIMethod), URI permintaan dikombinasikan dengan BaseAddress saat aplikasi klien membuat permintaan:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            var examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

            ...
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Mengkonfigurasi AuthorizationMessageHandler

AuthorizationMessageHandlerdapat dikonfigurasi dengan URL resmi, cakupan, dan URL pengembalian menggunakan metode .ConfigureHandler ConfigureHandler mengonfigurasi handler untuk mengotorisasi permintaan HTTP keluar menggunakan token akses. Token akses hanya dilampirkan jika setidaknya salah satu URL yang diotorisasi adalah basis URI permintaan (HttpRequestMessage.RequestUri). Jika URI permintaan adalah URI relatif, URI tersebut dikombinasikan dengan BaseAddress.

Dalam contoh berikut, AuthorizationMessageHandler mengonfigurasi HttpClient dalam Program file:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://api.contoso.com/v1.0" },
        scopes: new[] { "example.read", "example.write" }))
    {
        BaseAddress = new Uri("https://api.contoso.com/v1.0")
    });

Dalam kode sebelumnya, cakupan example.read dan example.write merupakan contoh umum yang tidak dimaksudkan untuk mencerminkan cakupan yang valid untuk penyedia tertentu.

Untuk solusi yang dihosting Blazor berdasarkan Blazor WebAssembly templat proyek, IWebAssemblyHostEnvironment.BaseAddress ditetapkan ke yang berikut secara default:

Diketik HttpClient

Klien yang diketik dapat didefinisikan yang menangani semua masalah akuisisi HTTP dan token dalam satu kelas.

WeatherForecastClient.cs:

using System.Net.Http.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;
    private WeatherForecast[]? forecasts;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;
    private WeatherForecast[] forecasts;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}

Dalam contoh sebelumnya, jenisnya WeatherForecast adalah kelas statis yang menyimpan data prakiraan cuaca. Tempat penampung {ASSEMBLY NAME} adalah nama rakitan aplikasi (misalnya, using static BlazorSample.Data;).

Dalam contoh berikut, HttpClientFactoryServiceCollectionExtensions.AddHttpClient adalah ekstensi di Microsoft.Extensions.Http. Tambahkan paket ke aplikasi yang belum mereferensikannya.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

Dalam file Program:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Untuk solusi yang dihosting Blazor berdasarkan Blazor WebAssembly templat proyek, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) ditetapkan ke HttpClient.BaseAddress secara default.

Dalam komponen yang mengambil data cuaca:

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

Mengonfigurasi handler HttpClient

Handler dapat dikonfigurasi lebih lanjut dengan ConfigureHandler untuk permintaan HTTP keluar.

Dalam contoh berikut, HttpClientFactoryServiceCollectionExtensions.AddHttpClient adalah ekstensi di Microsoft.Extensions.Http. Tambahkan paket ke aplikasi yang belum mereferensikannya.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

Dalam file Program:

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new [] { "https://api.contoso.com/v1.0" },
        scopes: new[] { "example.read", "example.write" }));

Dalam kode sebelumnya, cakupan example.read dan example.write merupakan contoh umum yang tidak dimaksudkan untuk mencerminkan cakupan yang valid untuk penyedia tertentu.

Untuk solusi yang dihosting Blazor berdasarkan Blazor WebAssembly templat proyek, IWebAssemblyHostEnvironment.BaseAddress ditetapkan ke yang berikut secara default:

Permintaan API web yang tidak diaauthenticated atau tidak sah di aplikasi dengan klien default yang aman

Aplikasi yang biasanya menggunakan default HttpClient aman juga dapat membuat permintaan API web yang tidak diaturentikasi atau tidak sah dengan mengonfigurasi bernama HttpClient.

Dalam contoh berikut, HttpClientFactoryServiceCollectionExtensions.AddHttpClient adalah ekstensi di Microsoft.Extensions.Http. Tambahkan paket ke aplikasi yang belum mereferensikannya.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

Dalam file Program:

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"));

Untuk solusi yang dihosting Blazor berdasarkan Blazor WebAssembly templat proyek, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) ditetapkan ke HttpClient.BaseAddress secara default.

Pendaftaran sebelumnya adalah selain pendaftaran default HttpClient aman yang ada.

Komponen membuat HttpClient dari IHttpClientFactory (Microsoft.Extensions.Http paket) untuk membuat permintaan yang tidak diaauthenticated atau tidak sah:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        var examples = await client.GetFromJsonAsync<ExampleType[]>(
            "ExampleNoAuthentication");

        ...
    }
}

Catatan

Pengontrol di API server, ExampleNoAuthenticationController untuk contoh sebelumnya, tidak ditandai dengan [Authorize] atribut .

Keputusan apakah akan menggunakan klien yang aman atau klien yang tidak aman karena instans default HttpClient terserah pengembang. Salah satu cara untuk membuat keputusan ini adalah dengan mempertimbangkan jumlah titik akhir yang diautentikasi versus tidak diautentikasi yang dihubungi aplikasi. Jika sebagian besar permintaan aplikasi adalah mengamankan titik akhir API, gunakan instans terautentikasi HttpClient sebagai default. Jika tidak, daftarkan instans yang tidak diaauthenticated HttpClient sebagai default.

Pendekatan alternatif untuk menggunakan IHttpClientFactory adalah membuat klien yang diketik untuk akses yang tidak diaturentikasi ke titik akhir anonim.

Meminta token akses tambahan

Token akses dapat diperoleh secara manual dengan memanggil IAccessTokenProvider.RequestAccessToken. Dalam contoh berikut, cakupan tambahan diperlukan oleh aplikasi untuk default HttpClient. Contoh Microsoft Authentication Library (MSAL) mengonfigurasi cakupan dengan MsalProviderOptions:

Dalam file Program:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

Tempat {CUSTOM SCOPE 1} penampung dan {CUSTOM SCOPE 2} dalam contoh sebelumnya adalah cakupan kustom.

Catatan

AdditionalScopesToConsent tidak dapat menyediakan izin pengguna yang didelegasikan untuk Microsoft Graph melalui UI persetujuan ID Microsoft Entra saat pengguna pertama kali menggunakan aplikasi yang terdaftar di Microsoft Azure. Untuk informasi selengkapnya, lihat Menggunakan Graph API dengan ASP.NET Core Blazor WebAssembly.

Metode ini IAccessTokenProvider.RequestAccessToken menyediakan kelebihan beban yang memungkinkan aplikasi untuk menyediakan token akses dengan serangkaian cakupan tertentu.

Razor Dalam komponen:

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

Tempat {CUSTOM SCOPE 1} penampung dan {CUSTOM SCOPE 2} dalam contoh sebelumnya adalah cakupan kustom.

AccessTokenResult.TryGetToken menghasilkan:

  • truetoken dengan untuk digunakan.
  • false jika token tidak diambil.

Berbagi Sumber Daya Lintas Asal (CORS)

Saat mengirim kredensial (otorisasi cookies/header) pada permintaan CORS, Authorization header harus diizinkan oleh kebijakan CORS.

Kebijakan berikut mencakup konfigurasi untuk:

  • Asal permintaan (http://localhost:5000, https://localhost:5001).
  • Metode apa pun (kata kerja).
  • Content-Type dan Authorization header. Untuk mengizinkan header kustom (misalnya, x-custom-header), cantumkan header saat memanggil WithHeaders.
  • Kredensial yang diatur oleh kode JavaScript sisi klien (credentials properti diatur ke include).
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
        .AllowAnyMethod()
        .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, 
            "x-custom-header")
        .AllowCredentials());

Solusi yang dihosting Blazor berdasarkan Blazor WebAssembly templat proyek menggunakan alamat dasar yang sama untuk aplikasi klien dan server. Aplikasi HttpClient.BaseAddress klien diatur ke URI secara builder.HostEnvironment.BaseAddress default. Konfigurasi CORS tidak diperlukan dalam konfigurasi default solusi yang dihostingBlazor. Aplikasi klien tambahan yang tidak dihosting oleh proyek server dan tidak berbagi alamat dasar aplikasi server memerlukan konfigurasi CORS dalam proyek server.

Untuk informasi selengkapnya, lihat Mengaktifkan Permintaan Lintas Asal (CORS) di ASP.NET Core dan komponen Penguji Permintaan HTTP aplikasi sampel (Components/HTTPRequestTester.razor).

Menangani kesalahan permintaan token

Ketika aplikasi satu halaman (SPA) mengautentikasi pengguna menggunakan OpenID Koneksi (OIDC), status autentikasi dipertahankan secara lokal dalam SPA dan di Identity Penyedia (IP) dalam bentuk sesi cookie yang ditetapkan sebagai hasil dari pengguna yang memberikan kredensial mereka.

Token yang dikeluarkan IP untuk pengguna biasanya berlaku untuk waktu yang singkat, sekitar satu jam biasanya, sehingga aplikasi klien harus secara teratur mengambil token baru. Jika tidak, pengguna akan keluar setelah token yang diberikan kedaluwarsa. Dalam kebanyakan kasus, klien OIDC dapat menyediakan token baru tanpa mengharuskan pengguna untuk mengautentikasi lagi berkat status autentikasi atau "sesi" yang disimpan dalam IP.

Ada beberapa kasus di mana klien tidak bisa mendapatkan token tanpa interaksi pengguna, misalnya, ketika karena alasan tertentu pengguna secara eksplisit keluar dari IP. Skenario ini terjadi jika pengguna mengunjungi https://login.microsoftonline.com dan keluar. Dalam skenario ini, aplikasi tidak segera tahu bahwa pengguna telah keluar. Token apa pun yang dipegang klien mungkin tidak lagi valid. Selain itu, klien tidak dapat menyediakan token baru tanpa interaksi pengguna setelah token saat ini kedaluwarsa.

Skenario ini tidak spesifik untuk autentikasi berbasis token. Mereka adalah bagian dari sifat SPAs. SPA yang menggunakan cookies juga gagal memanggil API server jika autentikasi cookie dihapus.

Saat aplikasi melakukan panggilan API ke sumber daya yang dilindungi, Anda harus mengetahui hal-hal berikut:

  • Untuk menyediakan token akses baru untuk memanggil API, pengguna mungkin diminta untuk mengautentikasi lagi.
  • Bahkan jika klien memiliki token yang tampaknya valid, panggilan ke server mungkin gagal karena token dicabut oleh pengguna.

Saat aplikasi meminta token, ada dua kemungkinan hasil:

  • Permintaan berhasil, dan aplikasi memiliki token yang valid.
  • Permintaan gagal, dan aplikasi harus mengautentikasi pengguna lagi untuk mendapatkan token baru.

Ketika permintaan token gagal, Anda perlu memutuskan apakah Anda ingin menyimpan status saat ini sebelum melakukan pengalihan. Beberapa pendekatan ada untuk menyimpan status dengan tingkat kompleksitas yang meningkat:

  • Simpan status halaman saat ini di penyimpanan sesi. OnInitializedAsync Selama metode siklus hidup (OnInitializedAsync), periksa apakah status dapat dipulihkan sebelum melanjutkan.
  • Tambahkan parameter string kueri dan gunakan sebagai cara untuk memberi sinyal aplikasi bahwa aplikasi perlu menghidrasi ulang status yang disimpan sebelumnya.
  • Tambahkan parameter string kueri dengan pengidentifikasi unik untuk menyimpan data di penyimpanan sesi tanpa memperkosa tabrakan dengan item lain.

Menyimpan status aplikasi sebelum operasi autentikasi dengan penyimpanan sesi

Contoh berikut menunjukkan cara membatalkan pekerjaan.

  • Pertahankan status sebelum mengalihkan ke halaman masuk.
  • Pulihkan status sebelumnya setelah autentikasi menggunakan parameter string kueri.
...
@using System.Text.Json
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation

<EditForm Model="User" OnSubmit="OnSaveAsync">
    <label>
        First Name: 
        <InputText @bind-Value="User!.Name" />
    </label>
    <label>
        Last Name: 
        <InputText @bind-Value="User!.LastName" />
    </label>
    <button type="submit">Save User</button>
</EditForm>

@code {
    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            var user = await JS.InvokeAsync<string>("sessionStorage.getItem",
                "resumeSavingProfile");

            if (!string.IsNullOrEmpty(user))
            {
                User = JsonSerializer.Deserialize<Profile>(user);
            }
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setItem", 
                "resumeSavingProfile", JsonSerializer.Serialize(User));
            Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
        }
    }

    public class Profile
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }
}

Menyimpan status aplikasi sebelum operasi autentikasi dengan penyimpanan sesi dan kontainer status

Selama operasi autentikasi, ada kasus di mana Anda ingin menyimpan status aplikasi sebelum browser dialihkan ke IP. Ini bisa terjadi ketika Anda menggunakan kontainer status dan ingin memulihkan status setelah autentikasi berhasil. Anda dapat menggunakan objek status autentikasi kustom untuk mempertahankan status khusus aplikasi atau referensi ke objek tersebut dan memulihkan status tersebut setelah operasi autentikasi berhasil diselesaikan. Contoh berikut menunjukkan pendekatan.

Kelas kontainer status dibuat di aplikasi dengan properti untuk menyimpan nilai status aplikasi. Dalam contoh berikut, kontainer digunakan untuk mempertahankan nilai penghitung komponen templat proyek default Blazor (Counter.razor).Counter Metode untuk menserialisasikan dan mendeserialisasi kontainer didasarkan pada System.Text.Json.

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage()
    {
        return JsonSerializer.Serialize(this);
    }

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

Komponen Counter menggunakan kontainer status untuk mempertahankan currentCount nilai di luar komponen:

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

ApplicationAuthenticationState Buat dari RemoteAuthenticationState. Id Berikan properti, yang berfungsi sebagai pengidentifikasi untuk status tersimpan secara lokal.

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string? Id { get; set; }
}
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string Id { get; set; }
}

Komponen Authentication (Authentication.razor) menyimpan dan memulihkan status aplikasi menggunakan penyimpanan sesi lokal dengan StateContainer metode serialisasi dan deserialisasi, GetStateForLocalStorage dan SetStateFromLocalStorage:

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string? Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

Contoh ini menggunakan Microsoft Entra (ME-ID) untuk autentikasi. Dalam file Program:

  • dikonfigurasi ApplicationAuthenticationState sebagai jenis Microsoft Authentication Library (MSAL). RemoteAuthenticationState
  • Kontainer status terdaftar dalam kontainer layanan.
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Menyesuaikan rute aplikasi

Secara default, Microsoft.AspNetCore.Components.WebAssembly.Authentication pustaka menggunakan rute yang diperlihatkan dalam tabel berikut untuk mewakili status autentikasi yang berbeda.

Rute Tujuan
authentication/login Memicu operasi masuk.
authentication/login-callback Menangani hasil operasi masuk apa pun.
authentication/login-failed Menampilkan pesan kesalahan ketika operasi masuk gagal karena beberapa alasan.
authentication/logout Memicu operasi keluar.
authentication/logout-callback Menangani hasil operasi keluar.
authentication/logout-failed Menampilkan pesan kesalahan ketika operasi keluar gagal karena beberapa alasan.
authentication/logged-out Menunjukkan bahwa pengguna telah berhasil keluar.
authentication/profile Memicu operasi untuk mengedit profil pengguna.
authentication/register Memicu operasi untuk mendaftarkan pengguna baru.

Rute yang ditampilkan dalam tabel sebelumnya dapat dikonfigurasi melalui RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths. Saat mengatur opsi untuk menyediakan rute kustom, konfirmasikan bahwa aplikasi memiliki rute yang menangani setiap jalur.

Dalam contoh berikut, semua jalur diawali dengan /security.

Authentication komponen (Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string Action { get; set; }
}

Dalam file Program:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

Jika persyaratan memanggil jalur yang sama sekali berbeda, atur rute seperti yang dijelaskan sebelumnya dan render RemoteAuthenticatorView dengan parameter tindakan eksplisit:

@page "/register"

<RemoteAuthenticatorView Action="RemoteAuthenticationActions.Register" />

Anda diizinkan untuk memecah UI ke halaman yang berbeda jika Anda memilih untuk melakukannya.

Menyesuaikan antarmuka pengguna autentikasi

RemoteAuthenticatorView menyertakan sekumpulan fragmen UI default untuk setiap status autentikasi. Setiap status dapat disesuaikan dengan meneruskan kustom RenderFragment. Untuk menyesuaikan teks yang ditampilkan selama proses masuk awal, dapat mengubah RemoteAuthenticatorView sebagai berikut.

Authentication komponen (Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string Action { get; set; }
}

RemoteAuthenticatorView memiliki satu fragmen yang dapat digunakan per rute autentikasi yang diperlihatkan dalam tabel berikut.

Rute Fragmen
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

Mengkustomisasi pengguna

Pengguna yang terikat ke aplikasi dapat disesuaikan.

Menyesuaikan pengguna dengan klaim payload

Dalam contoh berikut, pengguna terautentikasi aplikasi menerima amr klaim untuk setiap metode autentikasi pengguna. Klaim amr mengidentifikasi bagaimana subjek token diautentikasi dalam klaim payload Microsoft Identity Platform v1.0. Contohnya menggunakan kelas akun pengguna kustom berdasarkan RemoteUserAccount.

Buat kelas yang memperluas RemoteUserAccount kelas. Contoh berikut mengatur AuthenticationMethod properti ke array amrJSnilai properti ON pengguna. AuthenticationMethod diisi secara otomatis oleh kerangka kerja saat pengguna diautentikasi.

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[]? AuthenticationMethod { get; set; }
}
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[] AuthenticationMethod { get; set; }
}

Buat pabrik yang diperluas AccountClaimsPrincipalFactory<TAccount> untuk membuat klaim dari metode autentikasi pengguna yang disimpan di CustomUserAccount.AuthenticationMethod:

using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigation, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = (ClaimsIdentity)initialUser.Identity;

            if (account.AuthenticationMethod is not null)
            {
                foreach (var value in account.AuthenticationMethod)
                {
                    userIdentity.AddClaim(new Claim("amr", value));
                }
            }
        }

        return initialUser;
    }
}
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigation, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = (ClaimsIdentity)initialUser.Identity;

            foreach (var value in account.AuthenticationMethod)
            {
                userIdentity.AddClaim(new Claim("amr", value));
            }
        }

        return initialUser;
    }
}

Daftarkan CustomAccountFactory untuk penyedia autentikasi yang digunakan. Salah satu pendaftaran berikut ini valid:

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

Grup dan peran keamanan ME-ID dengan kelas akun pengguna kustom

Untuk contoh tambahan yang berfungsi dengan grup keamanan ME-ID dan Peran Administrator ME-ID dan kelas akun pengguna kustom, lihat ASP.NET Core Blazor WebAssembly dengan grup dan peran ID Microsoft Entra.

Pra-penyajian dengan autentikasi

Konten pra-penyajian yang memerlukan autentikasi dan otorisasi saat ini tidak didukung. Setelah mengikuti panduan di salah Blazor WebAssembly satu topik aplikasi keamanan, gunakan instruksi berikut untuk membuat aplikasi yang:

  • Jalur prarender yang otorisasinya tidak diperlukan.
  • Tidak merender jalur yang otorisasinya diperlukan.

Client Untuk file proyekProgram, faktor pendaftaran layanan umum ke dalam metode terpisah (misalnya, buat ConfigureCommonServices metode dalam Client proyek). Layanan umum adalah layanan yang didaftarkan pengembang untuk digunakan oleh proyek klien dan server.

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

Dalam file Program:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

Server Dalam file proyekProgram, daftarkan layanan tambahan berikut dan panggil ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddRazorPages();
builder.Services.TryAddScoped<AuthenticationStateProvider, 
    ServerAuthenticationStateProvider>();

Client.Program.ConfigureCommonServices(services);

Server Dalam metode proyekStartup.ConfigureServices, daftarkan layanan tambahan berikut dan panggil ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRazorPages();
    services.AddScoped<AuthenticationStateProvider, 
        ServerAuthenticationStateProvider>();
    services.AddScoped<SignOutSessionStateManager>();

    Client.Program.ConfigureCommonServices(services);
}

Untuk informasi selengkapnya tentang Blazor penyedia autentikasi server kerangka kerja (ServerAuthenticationStateProvider), lihat autentikasi dan otorisasi inti Blazor ASP.NET.

Server Dalam file proyekPages/_Host.cshtml, ganti Pembantu Component Tag (<component ... />) dengan yang berikut ini:

<div id="app">
    @if (HttpContext.Request.Path.StartsWithSegments("/authentication"))
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssembly" />
    }
    else
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssemblyPrerendered" />
    }
</div>

Dalam contoh sebelumnya:

  • Tempat penampung {CLIENT APP ASSEMBLY NAME} adalah nama rakitan aplikasi klien (misalnya BlazorSample.Client).
  • Pemeriksaan kondisi untuk /authentication segmen jalur:
    • Menghindari pra-penyajian (render-mode="WebAssembly") untuk jalur autentikasi.
    • Prarender (render-mode="WebAssemblyPrerendered") untuk jalur non-autentikasi.

Opsi untuk aplikasi yang dihosting dan penyedia login pihak ketiga

Saat mengautentikasi dan mengotorisasi aplikasi yang dihosting Blazor WebAssembly dengan penyedia pihak ketiga, ada beberapa opsi yang tersedia untuk mengautentikasi pengguna. Mana yang Anda pilih tergantung pada skenario Anda.

Untuk informasi lebih lanjut, lihat Menyimpan klaim dan token tambahan dari penyedia eksternal di ASP.NET Core.

Mengautentikasi pengguna untuk hanya memanggil API pihak ketiga yang dilindungi

Autentikasi pengguna dengan alur OAuth sisi klien terhadap penyedia API pihak ketiga:

builder.services.AddOidcAuthentication(options => { ... });

Dalam skenario ini:

  • Server yang menghosting aplikasi tidak memainkan peran.
  • API di server tidak dapat dilindungi.
  • Aplikasi ini hanya dapat memanggil API pihak ketiga yang dilindungi.

Mengautentikasi pengguna dengan penyedia pihak ketiga dan memanggil API yang dilindungi di server host dan pihak ketiga

Konfigurasikan Identity dengan penyedia login pihak ketiga. Dapatkan token yang diperlukan untuk akses API pihak ketiga dan simpan.

Saat pengguna masuk, Identity mengumpulkan token akses dan refresh sebagai bagian dari proses autentikasi. Pada saat itu, ada beberapa pendekatan yang tersedia untuk melakukan panggilan API ke API pihak ketiga.

Menggunakan token akses server untuk mengambil token akses pihak ketiga

Gunakan token akses yang dihasilkan di server untuk mengambil token akses pihak ketiga dari titik akhir API server. Dari sana, gunakan token akses pihak ketiga untuk memanggil sumber daya API pihak ketiga langsung dari Identity klien.

Kami tidak merekomendasikan pendekatan ini. Pendekatan ini mengharuskan memperlakukan token akses pihak ketiga seolah-olah dibuat untuk klien publik. Dalam istilah OAuth, aplikasi publik tidak memiliki rahasia klien karena tidak dapat dipercaya untuk menyimpan rahasia dengan aman, dan token akses diproduksi untuk klien rahasia. Klien rahasia adalah klien yang memiliki rahasia klien dan diasumsikan dapat menyimpan rahasia dengan aman.

  • Token akses pihak ketiga mungkin diberikan cakupan tambahan untuk melakukan operasi sensitif berdasarkan fakta bahwa pihak ketiga memancarkan token untuk klien yang lebih tepercaya.
  • Demikian pula, token refresh tidak boleh dikeluarkan untuk klien yang tidak tepercaya, karena melakukannya memberi klien akses tak terbatas kecuali pembatasan lain diberlakukan.

Lakukan panggilan API dari klien ke API server untuk memanggil API pihak ketiga

Lakukan panggilan API dari klien ke API server. Dari server, ambil token akses untuk sumber daya API pihak ketiga dan terbitkan panggilan apa pun yang diperlukan.

Kami merekomendasikan pendekatan ini. Meskipun pendekatan ini memerlukan hop jaringan tambahan melalui server untuk memanggil API pihak ketiga, pendekatan ini pada akhirnya menghasilkan pengalaman yang lebih aman:

  • Server dapat menyimpan token refresh dan memastikan bahwa aplikasi tidak kehilangan akses ke sumber daya pihak ketiga.
  • Aplikasi tidak dapat membocorkan token akses dari server yang mungkin berisi izin yang lebih sensitif.

Menggunakan titik akhir OpenID Koneksi (OIDC) v2.0

Pustaka autentikasi dan Blazor templat proyek menggunakan titik akhir OpenID Koneksi (OIDC) v1.0. Untuk menggunakan titik akhir v2.0, konfigurasikan opsi Pembawa JwtBearerOptions.Authority JWT. Dalam contoh berikut, ME-ID dikonfigurasi untuk v2.0 dengan menambahkan v2.0 segmen ke Authority properti :

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.Configure<JwtBearerOptions>(
    JwtBearerDefaults.AuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

Atau, pengaturan dapat dibuat dalam file pengaturan aplikasi (appsettings.json):

{
  "Local": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

Jika tacking pada segmen ke otoritas tidak sesuai untuk penyedia OIDC aplikasi, seperti dengan penyedia non-ME-ID, atur properti secara Authority langsung. Atur properti di atau JwtBearerOptions di file pengaturan aplikasi (appsettings.json) dengan Authority kunci .

Daftar klaim dalam token ID berubah untuk titik akhir v2.0. Dokumentasi Microsoft tentang perubahan telah dihentikan, tetapi panduan tentang klaim dalam token ID tersedia dalam referensi klaim token ID.

Mengonfigurasi dan menggunakan gRPC dalam komponen

Untuk mengonfigurasi Blazor WebAssembly aplikasi untuk menggunakan kerangka kerja ASP.NET Core gRPC:

  • Aktifkan gRPC-Web di server. Untuk informasi selengkapnya, lihat gRPC-Web di aplikasi gRPC ASP.NET Core.
  • Daftarkan layanan gRPC untuk penanganan pesan aplikasi. Contoh berikut mengonfigurasi handler pesan otorisasi aplikasi untuk menggunakan GreeterClient layanan dari tutorial gRPC ( Program file).

Catatan

Pra-penyajian diaktifkan secara default di Blazor Web Apps, jadi Anda harus memperhitungkan penyajian komponen terlebih dahulu dari server lalu dari klien. Status yang telah dirender harus mengalir ke klien sehingga dapat digunakan kembali. Untuk informasi selengkapnya, lihat Prarender komponen ASP.NET CoreRazor.

Catatan

Pra-penyajian diaktifkan secara default di aplikasi yang dihosting, jadi Anda harus memperhitungkan Blazor WebAssembly penyajian komponen terlebih dahulu dari server lalu dari klien. Status yang telah dirender harus mengalir ke klien sehingga dapat digunakan kembali. Untuk informasi selengkapnya, lihat Merender dan mengintegrasikan komponen ASP.NET CoreRazor.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

Komponen dalam aplikasi klien dapat melakukan panggilan gRPC menggunakan klien gRPC (Grpc.razor):

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string? serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

Untuk menggunakan Status.DebugException properti , gunakan Grpc.Net.Client versi 2.30.0 atau yang lebih baru.

Untuk informasi selengkapnya, lihat gRPC-Web di aplikasi gRPC ASP.NET Core.

AuthenticationService Ganti implementasi

Sub bagian berikut menjelaskan cara mengganti:

  • Implementasi JavaScript AuthenticationService apa pun.
  • Pustaka Autentikasi Microsoft untuk JavaScript (MSAL.js).

Ganti implementasi JavaScript AuthenticationService apa pun

Buat pustaka JavaScript untuk menangani detail autentikasi kustom Anda.

Peringatan

Panduan di bagian ini adalah detail implementasi dari default RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProviderOptions>. Kode TypeScript di bagian ini berlaku khusus untuk ASP.NET Core di .NET 7 dan dapat berubah tanpa pemberitahuan dalam rilis ASP.NET Core mendatang.

// .NET makes calls to an AuthenticationService object in the Window.
declare global {
  interface Window { AuthenticationService: AuthenticationService }
}

export interface AuthenticationService {
  // Init is called to initialize the AuthenticationService.
  public static init(settings: UserManagerSettings & AuthorizeServiceSettings, logger: any) : Promise<void>;

  // Gets the currently authenticated user.
  public static getUser() : Promise<{[key: string] : string }>;

  // Tries to get an access token silently.
  public static getAccessToken(options: AccessTokenRequestOptions) : Promise<AccessTokenResult>;

  // Tries to sign in the user or get an access token interactively.
  public static signIn(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the sign-in process when a redirect is used.
  public static async completeSignIn(url: string) : Promise<AuthenticationResult>;

  // Signs the user out.
  public static signOut(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the signout callback when a redirect is used.
  public static async completeSignOut(url: string) : Promise<AuthenticationResult>;
}

// The rest of these interfaces match their C# definitions.

export interface AccessTokenRequestOptions {
  scopes: string[];
  returnUrl: string;
}

export interface AccessTokenResult {
  status: AccessTokenResultStatus;
  token?: AccessToken;
}

export interface AccessToken {
  value: string;
  expires: Date;
  grantedScopes: string[];
}

export enum AccessTokenResultStatus {
  Success = 'Success',
  RequiresRedirect = 'RequiresRedirect'
}

export enum AuthenticationResultStatus {
  Redirect = 'Redirect',
  Success = 'Success',
  Failure = 'Failure',
  OperationCompleted = 'OperationCompleted'
};

export interface AuthenticationResult {
  status: AuthenticationResultStatus;
  state?: unknown;
  message?: string;
}

export interface AuthenticationContext {
  state?: unknown;
  interactiveRequest: InteractiveAuthenticationRequest;
}

export interface InteractiveAuthenticationRequest {
  scopes?: string[];
  additionalRequestParameters?: { [key: string]: any };
};

Anda dapat mengimpor pustaka dengan menghapus tag asli <script> dan menambahkan <script> tag yang memuat pustaka kustom. Contoh berikut menunjukkan penggantian tag default <script> dengan yang memuat pustaka bernama CustomAuthenticationService.js dari wwwroot/js folder.

Di wwwroot/index.html sebelum skrip (_framework/blazor.webassembly.js) di dalam tag penutup </body>Blazor:

- <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>

Untuk informasi selengkapnya, lihat AuthenticationService.ts di dotnet/aspnetcore repositori GitHub.

Catatan

Tautan dokumentasi ke sumber referensi .NET biasanya memuat cabang default repositori, yang mewakili pengembangan saat ini untuk rilis .NET berikutnya. Untuk memilih tag rilis tertentu, gunakan daftar dropdown Beralih cabang atau tag. Untuk informasi lebih lanjut, lihat Cara memilih tag versi kode sumber ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Ganti Pustaka Autentikasi Microsoft untuk JavaScript (MSAL.js)

Jika aplikasi memerlukan versi kustom Pustaka Autentikasi Microsoft untuk JavaScript (MSAL.js), lakukan langkah-langkah berikut:

  1. Konfirmasikan sistem memiliki pengembang terbaru .NET SDK atau dapatkan dan instal SDK pengembang terbaru dari .NET Core SDK: Penginstal dan Biner. Konfigurasi umpan NuGet internal tidak diperlukan untuk skenario ini.
  2. Siapkan dotnet/aspnetcore repositori GitHub untuk pengembangan setelah dokumentasi di Build ASP.NET Core dari Sumber. Fork dan kloning atau unduh arsip ZIP repositori dotnet/aspnetcoreGitHub.
  3. src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json Buka file dan atur versi yang diinginkan dari @azure/msal-browser. Untuk daftar versi yang dirilis, kunjungi @azure/msal-browser situs web npm dan pilih tab Versi .
  4. Authentication.Msal Buat proyek di src/Components/WebAssembly/Authentication.Msal/src folder dengan yarn build perintah dalam shell perintah.
  5. Jika aplikasi menggunakan aset terkompresi (Brotli/Gzip), kompres Interop/dist/Release/AuthenticationService.js file.
  6. AuthenticationService.js Salin file dan versi terkompresi (.br/.gz) file, jika diproduksi, dari Interop/dist/Release folder ke folder aplikasi publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal di aset yang diterbitkan aplikasi.

Meneruskan opsi penyedia kustom

Tentukan kelas untuk meneruskan data ke pustaka JavaScript yang mendasar.

Penting

Struktur kelas harus sesuai dengan apa yang diharapkan pustaka ketika JSON diserialisasikan dengan System.Text.Json.

Contoh berikut menunjukkan kelas dengan atribut yang ProviderOptions cocok dengan JsonPropertyName harapan pustaka penyedia kustom hipotetis:

public class ProviderOptions
{
    public string? Authority { get; set; }
    public string? MetadataUrl { get; set; }

    [JsonPropertyName("client_id")]
    public string? ClientId { get; set; }

    public IList<string> DefaultScopes { get; } = 
        new List<string> { "openid", "profile" };

    [JsonPropertyName("redirect_uri")]
    public string? RedirectUri { get; set; }

    [JsonPropertyName("post_logout_redirect_uri")]
    public string? PostLogoutRedirectUri { get; set; }

    [JsonPropertyName("response_type")]
    public string? ResponseType { get; set; }

    [JsonPropertyName("response_mode")]
    public string? ResponseMode { get; set; }
}
public class ProviderOptions
{
    public string Authority { get; set; }
    public string MetadataUrl { get; set; }

    [JsonPropertyName("client_id")]
    public string ClientId { get; set; }

    public IList<string> DefaultScopes { get; } = 
        new List<string> { "openid", "profile" };

    [JsonPropertyName("redirect_uri")]
    public string RedirectUri { get; set; }

    [JsonPropertyName("post_logout_redirect_uri")]
    public string PostLogoutRedirectUri { get; set; }

    [JsonPropertyName("response_type")]
    public string ResponseType { get; set; }

    [JsonPropertyName("response_mode")]
    public string ResponseMode { get; set; }
}

Daftarkan opsi penyedia dalam sistem DI dan konfigurasikan nilai yang sesuai:

builder.Services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount,
    ProviderOptions>(options => {
        options.Authority = "...";
        options.MetadataUrl = "...";
        options.ClientId = "...";
        options.DefaultScopes = new List<string> { "openid", "profile", "myApi" };
        options.RedirectUri = "https://localhost:5001/authentication/login-callback";
        options.PostLogoutRedirectUri = "https://localhost:5001/authentication/logout-callback";
        options.ResponseType = "...";
        options.ResponseMode = "...";
    });

Contoh sebelumnya menetapkan URI pengalihan dengan literal string reguler. Alternatif berikut tersedia:

  • TryCreate menggunakan IWebAssemblyHostEnvironment.BaseAddress:

    Uri.TryCreate(
        $"{builder.HostEnvironment.BaseAddress}authentication/login-callback", 
        UriKind.Absolute, out var redirectUri);
    options.RedirectUri = redirectUri;
    
  • Konfigurasi penyusun host:

    options.RedirectUri = builder.Configuration["RedirectUri"];
    

    wwwroot/appsettings.json:

    {
      "RedirectUri": "https://localhost:5001/authentication/login-callback"
    }
    

Sumber Daya Tambahan: