Condividi tramite


Proteggere un ASP.NET Core Blazor Web App con Microsoft Entra ID

Questo articolo descrive come proteggere un Blazor Web App oggetto con identity microsoft platform/Microsoft Web packages for Microsoft Identity Entra ID usando un'app di esempio.

La specifica seguente è descritta:

  • Blazor Web App usa la modalità di rendering automatico con interattività globale (InteractiveAuto).
  • Il progetto server chiama AddAuthenticationStateSerialization per aggiungere un provider di stato di autenticazione lato server che usa PersistentComponentState per trasferire lo stato di autenticazione al client. Il client chiama AddAuthenticationStateDeserialization per deserializzare e usare lo stato di autenticazione passato dal server. Lo stato di autenticazione è fisso per la durata dell'applicazione WebAssembly.
  • L'app usa l'ID Microsoft Entra, basato su pacchetti Web MicrosoftIdentity.
  • L'aggiornamento automatico dei token non interattivi viene gestito dal framework.
  • L'app usa astrazioni del servizio lato server e lato client per visualizzare i dati meteo generati:
    • Quando si esegue il rendering del Weather componente nel server per visualizzare i dati meteo, il componente usa sul ServerWeatherForecaster server per ottenere direttamente i dati meteo (non tramite una chiamata API Web).
    • Quando viene eseguito il rendering del Weather componente nel client, il componente usa l'implementazione del ClientWeatherForecaster servizio, che usa un preconfigurato HttpClient (nel file del Program progetto client) per effettuare una chiamata API Web all'API minima del progetto server (/weather-forecast) per i dati meteo. L'endpoint API minimo ottiene i dati meteo dalla ServerWeatherForecaster classe e lo restituisce al client per il rendering dal componente.

Esempio di app

L'app di esempio è costituita da due progetti:

  • BlazorWebAppEntra: progetto lato server di Blazor Web App, contenente un esempio endpoint API minimo per i dati meteo.
  • BlazorWebAppEntra.Client: progetto lato client dell'oggetto Blazor Web App.

Accedere alle app di esempio tramite la cartella della versione più recente dalla radice del repository con il collegamento seguente. I progetti si trovano nella BlazorWebAppEntra cartella per .NET 9 o versione successiva.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Progetto lato Blazor Web App server (BlazorWebAppEntra)

Il BlazorWebAppEntra progetto è il progetto lato server dell'oggetto Blazor Web App.

Il BlazorWebAppEntra.http file può essere usato per testare la richiesta di dati meteo. Si noti che il BlazorWebAppEntra progetto deve essere in esecuzione per testare l'endpoint e l'endpoint è hardcoded nel file. Per altre informazioni, vedere Usare file .http in Visual Studio 2022.

Progetto lato client Blazor Web App (BlazorWebAppEntra.Client)

Il BlazorWebAppEntra.Client progetto è il progetto lato client di Blazor Web App.

Se l'utente deve accedere o disconnettersi durante il rendering lato client, viene avviato un ricaricamento di pagina completo.

Impostazione

Questa sezione illustra come configurare l'app di esempio.

AddMicrosoftIdentityWebApp da Microsoft Identity Web (Microsoft.Identity.Web pacchetto NuGet, documentazione dell'API) è configurato dalla AzureAd sezione del file del appsettings.json progetto server.

Nella registrazione dell'app in Entra o portale di Azure usare una configurazione della piattaforma Web con un URI di reindirizzamento di https://localhost/signin-oidc (una porta non è necessaria). Verificare che i token ID e i token di accesso in Concessione implicita e flussi ibridi non siano selezionati. Il gestore OpenID Connect richiede automaticamente i token appropriati usando il codice restituito dall'endpoint di autorizzazione.

Configurare l'app

Nel file delle impostazioni dell'app del progetto server (appsettings.json) specificare la configurazione della sezione dell'app AzureAd . Ottenere l'ID applicazione (client), il dominio tenant (editore) e l'ID della directory (tenant) dalla registrazione dell'app in Entra o portale di Azure:

"AzureAd": {
  "CallbackPath": "/signin-oidc",
  "ClientId": "{CLIENT ID}",
  "Domain": "{DOMAIN}",
  "Instance": "https://login.microsoftonline.com/",
  "ResponseType": "code",
  "TenantId": "{TENANT ID}"
},

Segnaposto nell'esempio precedente:

  • {CLIENT ID}: ID applicazione (client).
  • {DOMAIN}: dominio tenant (server di pubblicazione).
  • {TENANT ID}: ID directory (tenant).

Esempio:

"AzureAd": {
  "CallbackPath": "/signin-oidc",
  "ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
  "Domain": "contoso.onmicrosoft.com",
  "Instance": "https://login.microsoftonline.com/",
  "ResponseType": "code",
  "TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
},

Il percorso di callback (CallbackPath) deve corrispondere all'URI di reindirizzamento (percorso di callback di accesso) configurato durante la registrazione dell'applicazione in Entra o portale di Azure. I percorsi vengono configurati nel pannello Autenticazione della registrazione dell'app. Il valore predefinito di è /signin-oidc per un URI di CallbackPath reindirizzamento registrato di https://localhost/signin-oidc (non è necessaria una porta).

Avviso

Non archiviare segreti dell'app, stringa di connessione, credenziali, password, numeri di identificazione personale (PIN), codice C#/.NET privato o chiavi/token privati nel codice lato client, che è sempre non sicuro. Negli ambienti di test/gestione temporanea e produzione, il codice lato Blazor server e le API Web devono usare flussi di autenticazione sicuri che evitano di mantenere le credenziali all'interno del codice del progetto o dei file di configurazione. Al di fuori dei test di sviluppo locali, è consigliabile evitare l'uso di variabili di ambiente per archiviare i dati sensibili, perché le variabili di ambiente non sono l'approccio più sicuro. Per i test di sviluppo locali, lo strumento Secret Manager è consigliato per proteggere i dati sensibili. Per altre informazioni, vedere Gestire in modo sicuro dati e credenziali sensibili.

Stabilire il segreto client

Creare un segreto client nella registrazione dell'ID Entra dell'app in Entra o portale di Azure (Gestisci>certificati e segreti>Nuovo segreto client). Usare il valore del nuovo segreto nelle indicazioni seguenti.

Usare uno o entrambi gli approcci seguenti per fornire il segreto client all'app:

  • Strumento Secret Manager: lo strumento Secret Manager archivia i dati privati nel computer locale e viene usato solo durante lo sviluppo locale.
  • Azure Key Vault: è possibile archiviare il segreto client in un insieme di credenziali delle chiavi da usare in qualsiasi ambiente, incluso per l'ambiente di sviluppo quando si lavora in locale. Alcuni sviluppatori preferiscono usare gli insiemi di credenziali delle chiavi per le distribuzioni di staging e di produzione e usare lo strumento Secret Manager per lo sviluppo locale.

È consigliabile evitare di archiviare segreti client nel codice del progetto o nei file di configurazione. Usare flussi di autenticazione sicuri, ad esempio uno o entrambi gli approcci descritti in questa sezione.

Strumento Secret Manager

Lo strumento Secret Manager può archiviare il segreto client dell'app server nella chiave AzureAd:ClientSecretdi configurazione .

L'app di esempio non è stata inizializzata per lo strumento Secret Manager. Usare una shell dei comandi, ad esempio la shell dei comandi di Developer PowerShell in Visual Studio, per eseguire il comando seguente. Prima di eseguire il comando, modificare la directory con il cd comando nella directory del progetto server. Il comando stabilisce un identificatore di segreti utente (<UserSecretsId>) nel file di progetto dell'app server, che viene usato internamente dagli strumenti per tenere traccia dei segreti per l'app:

dotnet user-secrets init

Eseguire il comando seguente per impostare il segreto client. Il {SECRET} segnaposto è il segreto client ottenuto dalla registrazione entra dell'app:

dotnet user-secrets set "AzureAd:ClientSecret" "{SECRET}"

Se si usa Visual Studio, è possibile verificare che il segreto sia impostato facendo clic con il pulsante destro del mouse sul progetto server in Esplora soluzioni e scegliendo Gestisci segreti utente.

Azure Key Vault

Azure Key Vault offre un approccio sicuro per fornire il segreto client dell'app all'app.

Per creare un insieme di credenziali delle chiavi e impostare un segreto client, vedere Informazioni sui segreti di Azure Key Vault (documentazione di Azure) che collega tra le risorse per iniziare a usare Azure Key Vault. Per implementare il codice in questa sezione, registrare l'URI dell'insieme di credenziali delle chiavi e il nome del segreto da Azure quando si crea l'insieme di credenziali delle chiavi e il segreto. Quando si impostano i criteri di accesso per il segreto nel pannello Criteri di accesso:

  • È necessaria solo l'autorizzazione Ottieni segreto.
  • Selezionare l'applicazione come entità per il segreto.

Importante

Viene creato un segreto dell'insieme di credenziali delle chiavi con una data di scadenza. Assicurarsi di tenere traccia della scadenza di un segreto dell'insieme di credenziali delle chiavi e creare un nuovo segreto per l'app prima del passaggio di tale data.

Il metodo seguente GetKeyVaultSecret recupera un segreto da un insieme di credenziali delle chiavi. Aggiungere questo metodo al progetto server. Modificare lo spazio dei nomi (BlazorSample.Helpers) in modo che corrisponda allo schema dello spazio dei nomi del progetto.

Helpers/AzureHelper.cs:

using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

namespace BlazorSample.Helpers;

public static class AzureHelper
{
    public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
    {
        DefaultAzureCredentialOptions options = new()
        {
            // Specify the tenant ID to use the dev credentials when running the app locally
            // in Visual Studio.
            VisualStudioTenantId = tenantId,
            SharedTokenCacheTenantId = tenantId
        };

        var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
        var secret = client.GetSecretAsync(secretName).Result;

        return secret.Value.Value;
    }
}

Dove i servizi vengono registrati nel file del progetto server Program , ottenere e applicare il segreto client usando il codice seguente:

var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
var secretName = builder.Configuration.GetValue<string>("AzureAd:SecretName")!;

builder.Services.Configure<MicrosoftIdentityOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
    {
        options.ClientSecret = 
            AzureHelper.GetKeyVaultSecret(tenantId, vaultUri, secretName);
    });

Se si vuole controllare l'ambiente in cui opera il codice precedente, ad esempio per evitare di eseguire il codice in locale perché si è scelto di usare lo strumento Secret Manager per lo sviluppo locale, è possibile eseguire il wrapping del codice precedente in un'istruzione condizionale che controlla l'ambiente:

if (!context.HostingEnvironment.IsDevelopment())
{
    ...
}

AzureAd Nella sezione di appsettings.jsonaggiungere le chiavi e SecretName i valori di configurazione seguentiVaultUri:

"VaultUri": "{VAULT URI}",
"SecretName": "{SECRET NAME}"

Nell'esempio precedente:

  • Il {VAULT URI} segnaposto è l'URI dell'insieme di credenziali delle chiavi. Includere la barra finale sull'URI.
  • Il {SECRET NAME} segnaposto è il nome del segreto.

Esempio:

"VaultUri": "https://contoso.vault.azure.net/",
"SecretName": "BlazorWebAppEntra"

La configurazione viene usata per facilitare la fornitura di insiemi di credenziali delle chiavi e nomi segreti dedicati in base ai file di configurazione ambientale dell'app. Ad esempio, è possibile specificare valori di configurazione diversi per appsettings.Development.json in fase di sviluppo, appsettings.Staging.json durante la gestione temporanea e appsettings.Production.json per la distribuzione di produzione. Per altre informazioni, vedere configurazione di ASP.NET CoreBlazor.

Reindirizzare alla pagina alla home disconnessione

Quando un utente si sposta all'interno dell'app, il LogInOrOut componente (Layout/LogInOrOut.razor) imposta un campo nascosto per l'URL restituito (ReturnUrl) sul valore dell'URL corrente (currentURL). Quando l'utente si disconnette dall'app, il identity provider li restituisce alla pagina da cui si è disconnesso.

Se l'utente si disconnette da una pagina sicura, viene restituito alla stessa pagina protetta dopo la disconnessione solo per essere restituito tramite il processo di autenticazione. Questo comportamento è corretto quando gli utenti devono cambiare account di frequente. Tuttavia, una specifica dell'app alternativa può richiedere che l'utente venga restituito alla pagina dell'app home o ad altre pagine dopo la disconnessione. L'esempio seguente illustra come impostare la pagina dell'app come URL restituito per le operazioni di home disconnessione.

Le modifiche importanti apportate al LogInOrOut componente sono illustrate nell'esempio seguente. Non è necessario fornire un campo nascosto per il ReturnUrl set alla home pagina in / perché si tratta del percorso predefinito. IDisposable non è più implementato. l'oggetto NavigationManager non viene più inserito. L'intero @code blocco viene rimosso.

Layout/LogInOrOut.razor:

@using Microsoft.AspNetCore.Authorization

<div class="nav-item px-3">
    <AuthorizeView>
        <Authorized>
            <form action="authentication/logout" method="post">
                <AntiforgeryToken />
                <button type="submit" class="nav-link">
                    <span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true">
                    </span> Logout @context.User.Identity?.Name
                </button>
            </form>
        </Authorized>
        <NotAuthorized>
            <a class="nav-link" href="authentication/login">
                <span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span> 
                Login
            </a>
        </NotAuthorized>
    </AuthorizeView>
</div>

Risoluzione dei problemi

Registrazione

L'app server è un'app standard ASP.NET Core. Vedere le linee guida per la registrazione di ASP.NET Core per abilitare un livello di registrazione inferiore nell'app server.

Per abilitare la registrazione di debug o traccia per Blazor WebAssembly l'autenticazione, vedere la sezione Registrazione dell'autenticazione lato client di ASP.NET Core Blazor con il selettore della versione dell'articolo impostato su ASP.NET Core 7.0 o versione successiva.

Errori comuni

  • Configurazione errata dell'app o Identity del provider (IP)

    Gli errori più comuni sono causati da una configurazione errata. Di seguito sono riportati alcuni esempi:

    • A seconda dei requisiti dello scenario, un'autorità mancante o non corretta, istanza, ID tenant, dominio tenant, ID client o URI di reindirizzamento impedisce a un'app di autenticare i client.
    • Gli ambiti di richiesta non corretti impediscono ai client di accedere agli endpoint DELL'API Web del server.
    • Autorizzazioni DELL'API server non corrette o mancanti impediscono ai client di accedere agli endpoint DELL'API Web del server.
    • L'esecuzione dell'app in una porta diversa da quella configurata nell'URI di reindirizzamento della registrazione dell'app ip. Si noti che non è necessaria una porta per Microsoft Entra ID e un'app in esecuzione in un localhost indirizzo di test di sviluppo, ma la configurazione della porta dell'app e la porta in cui è in esecuzione l'app devono corrispondere per gli indirizzi nonlocalhost .

    La copertura della configurazione in questo articolo illustra esempi della configurazione corretta. Controllare attentamente la configurazione alla ricerca di errori di configurazione di app e IP.

    Se la configurazione è corretta:

    • Analizzare i log delle applicazioni.

    • Esaminare il traffico di rete tra l'app client e l'app IP o server con gli strumenti di sviluppo del browser. Spesso, un messaggio di errore esatto o un messaggio con un indizio sulla causa del problema viene restituito al client dall'app IP o server dopo aver effettuato una richiesta. Strumenti di sviluppo materiale sussidiario sono disponibili negli articoli seguenti:

    Il team della documentazione risponde al feedback e ai bug dei documenti negli articoli (aprire un problema dalla sezione Commenti e suggerimenti della pagina ), ma non è in grado di fornire supporto tecnico. Sono disponibili diversi forum di supporto pubblico per facilitare la risoluzione dei problemi di un'app. Consigliamo quanto segue:

    I forum precedenti non sono di proprietà o controllati da Microsoft.

    Per i report sui bug del framework non riservati, non sensibili e non riservati, aprire un problema con l'unità del prodotto ASP.NET Core. Non aprire un problema con l'unità di prodotto fino a quando non hai approfondito la causa di un problema e non puoi risolverlo autonomamente e con l'aiuto della community in un forum di supporto pubblico. L'unità prodotto non è in grado di risolvere i problemi relativi alle singole app interrotte a causa di semplici errori di configurazione o casi d'uso che coinvolgono servizi di terze parti. Se un report è sensibile o riservato in natura o descrive un potenziale difetto di sicurezza nel prodotto che potrebbero sfruttare i cyberattacker, vedere Segnalazione di problemi e bug di sicurezza (dotnet/aspnetcorerepository GitHub).

  • Client non autorizzato per ME-ID

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Autorizzazione non riuscita. Questi requisiti non sono stati soddisfatti: DenyAnonymousAuthorizationRequirement: richiede un utente autenticato.

    Errore di callback di accesso da ME-ID:

    • Errore: unauthorized_client
    • Description (Descrizione): AADB2C90058: The provided application is not configured to allow public clients.

    Per risolvere l'errore:

    1. Nella portale di Azure accedere al manifesto dell'app.
    2. Impostare l'attributo allowPublicClient su null o .true

Cookie e dati del sito

I cookie e i dati del sito possono persistere tra gli aggiornamenti delle app e interferire con i test e la risoluzione dei problemi. Cancellare quanto segue quando si apportano modifiche al codice dell'app, modifiche all'account utente con il provider o modifiche alla configurazione dell'app del provider:

  • Cookie di accesso utente
  • Cookie dell'app
  • Dati del sito memorizzati nella cache e archiviati

Un approccio per evitare che i cookie e i dati del sito persistenti interferiscano con i test e la risoluzione dei problemi consiste nel:

  • Configurare un browser
    • Usare un browser per i test che è possibile configurare per eliminare tutti i cookie dati del sito e ogni volta che il browser viene chiuso.
    • Assicurarsi che il browser venga chiuso manualmente o dall'IDE per qualsiasi modifica apportata alla configurazione dell'app, dell'utente di test o del provider.
  • Usare un comando personalizzato per aprire un browser in modalità InPrivate o In incognito in Visual Studio:
    • Aprire la finestra di dialogo Sfoglia con dal pulsante Esegui di Visual Studio.
    • Seleziona il pulsante Aggiungi.
    • Specificare il percorso del browser nel campo Programma . I percorsi eseguibili seguenti sono percorsi di installazione tipici per Windows 10. Se il browser è installato in un percorso diverso o non si usa Windows 10, specificare il percorso dell'eseguibile del browser.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • Nel campo Argomenti specificare l'opzione della riga di comando usata dal browser per aprire in modalità InPrivate o Incognito. Alcuni browser richiedono l'URL dell'app.
      • Microsoft Edge: usare -inprivate.
      • Google Chrome: usare --incognito --new-window {URL}, dove il {URL} segnaposto è l'URL da aprire (ad esempio, https://localhost:5001).
      • Mozilla Firefox: usare -private -url {URL}, dove il {URL} segnaposto è l'URL da aprire (ad esempio, https://localhost:5001).
    • Specificare un nome nel campo Nome descrittivo. Ad esempio: Firefox Auth Testing.
    • Selezionare il pulsante OK.
    • Per evitare di dover selezionare il profilo del browser per ogni iterazione di test con un'app, impostare il profilo come predefinito con il pulsante Imposta come predefinito .
    • Assicurarsi che il browser sia chiuso dall'IDE per qualsiasi modifica apportata all'app, all'utente di test o alla configurazione del provider.

Aggiornamenti di app

Un'app funzionante potrebbe non riuscire immediatamente dopo l'aggiornamento di .NET Core SDK nel computer di sviluppo o la modifica delle versioni dei pacchetti all'interno dell'app. In alcuni casi i pacchetti incoerenti possono interrompere un'app quando si eseguono aggiornamenti principali. La maggior parte di questi problemi può essere risolta attenendosi alle istruzioni seguenti:

  1. Cancellare le cache dei pacchetti NuGet del sistema locale eseguendo dotnet nuget locals all --clear da una shell dei comandi.
  2. Eliminare le cartelle e obj del bin progetto.
  3. Ripristinare e ricompilare il progetto.
  4. Eliminare tutti i file nella cartella di distribuzione nel server prima di ridistribuire l'app.

Nota

L'uso di versioni del pacchetto incompatibili con il framework di destinazione dell'app non è supportato. Per informazioni su un pacchetto, usare La raccolta NuGet o Esplora pacchetti FuGet.

Eseguire l'app server

Durante i test e la risoluzione dei problemi Blazor Web App, assicurarsi di eseguire l'app dal progetto server.

Esaminare l'utente

Il componente seguente UserClaims può essere usato direttamente nelle app o funge da base per un'ulteriore personalizzazione.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li><b>@claim.Type:</b> @claim.Value</li>
        }
    </ul>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    [CascadingParameter]
    private Task<AuthenticationState>? AuthState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthState == null)
        {
            return;
        }

        var authState = await AuthState;
        claims = authState.User.Claims;
    }
}

Risorse aggiuntive