Come usare Identity per proteggere un back-end dell'API Web per le applicazioni a pagina singola

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

ASP.NET Core Identity fornisce API che gestiscono l'autenticazione, l'autorizzazione e identity la gestione. Le API consentono di proteggere gli endpoint di un back-end dell'API Web con cookiel'autenticazione basata su . Un'opzione basata su token è disponibile per i client che non possono usare i cookie, ma in questo modo si è responsabili della sicurezza dei token. È consigliabile usare i cookie per le applicazioni basate su browser, perché, per impostazione predefinita, il browser li gestisce automaticamente senza esporli a JavaScript.

Questo articolo illustra come usare Identity per proteggere un back-end dell'API Web per le app spA, ad esempio Angular, React e Vue. Le stesse API back-end possono essere usate per proteggere Blazor WebAssembly le app.

Prerequisiti

I passaggi illustrati in questo articolo aggiungono l'autenticazione e l'autorizzazione a un'app api Web core di ASP.NET che:

  • Non è già configurato per l'autenticazione.
  • Destinazioni net8.0 o versioni successive.
  • Può essere un'API minima o un'API basata su controller.

Alcune delle istruzioni di test in questo articolo usano l'interfaccia utente di Swagger inclusa nel modello di progetto. L'interfaccia utente di Swagger non è necessaria per l'uso Identity con un back-end dell'API Web.

Installare i pacchetti NuGet

Installare i pacchetti NuGet seguenti:

Per iniziare più rapidamente, usare il database in memoria.

Modificare il database in un secondo momento in SQLite o SQL Server per salvare i dati utente tra sessioni durante i test o per l'uso in produzione. Ciò introduce una certa complessità rispetto alla memoria, perché richiede che il database venga creato tramite migrazioni, come illustrato nell'esercitazione EF Coreintroduttiva.

Installare questi pacchetti usando gestione pacchetti NuGet in Visual Studio o il comando dotnet add package CLI.

Creare una chiave IdentityDbContext

Aggiungere una classe denominata ApplicationDbContext che eredita da IdentityDbContext<TUser>:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :
        base(options)
    { }
}

Il codice illustrato fornisce un costruttore speciale che consente di configurare il database per ambienti diversi.

Aggiungere una o più delle direttive seguenti using in base alle esigenze quando si aggiunge il codice illustrato in questi passaggi.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

Configurare il EF Core contesto

Come indicato in precedenza, il modo più semplice per iniziare consiste nell'usare il database in memoria. Ogni esecuzione in memoria inizia con un nuovo database e non è necessario usare le migrazioni. Dopo la chiamata a WebApplication.CreateBuilder(args), aggiungere il codice seguente per configurare Identity per l'uso di un database in memoria:

builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseInMemoryDatabase("AppDb"));

Per salvare i dati utente tra sessioni durante i test o per l'uso in produzione, modificare il database in un secondo momento in SQLite o SQL Server.

Aggiungere Identity servizi al contenitore

Dopo la chiamata a WebApplication.CreateBuilder(args), chiamare AddAuthorization per aggiungere servizi al contenitore di inserimento delle dipendenze:

builder.Services.AddAuthorization();

Attivare Identity le API

Dopo la chiamata a WebApplication.CreateBuilder(args), chiamare AddIdentityApiEndpoints<TUser>(IServiceCollection) e AddEntityFrameworkStores<TContext>(IdentityBuilder).

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Per impostazione predefinita, vengono attivati sia i cookie che i token proprietari. I cookie e i token vengono emessi all'account di accesso se il parametro della useCookies stringa di query nell'endpoint di accesso è true.

Mappa Identity itinerari

Dopo la chiamata a builder.Build(), chiamare MapIdentityApi<TUser>(IEndpointRouteBuilder) per eseguire il mapping degli Identity endpoint:

app.MapIdentityApi<IdentityUser>();

Proteggere gli endpoint selezionati

Per proteggere un endpoint, usare il RequireAuthorization metodo di estensione nella Map{Method} chiamata che definisce la route. Ad esempio:

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();

Il RequireAuthorization metodo può essere usato anche per:

  • Proteggere gli endpoint dell'interfaccia utente di Swagger, come illustrato nell'esempio seguente:

    app.MapSwagger().RequireAuthorization();
    
  • Proteggere con un'attestazione o un'autorizzazione specifica, come illustrato nell'esempio seguente:

    .RequireAuthorization("Admin");
    

In un progetto api Web basato su controller, gli endpoint sicuri applicando l'attributo [Authorize] a un controller o a un'azione.

Testare l'API

Un modo rapido per testare l'autenticazione consiste nell'usare il database in memoria e l'interfaccia utente di Swagger inclusa nel modello di progetto. I passaggi seguenti illustrano come testare l'API con l'interfaccia utente di Swagger. Assicurarsi che gli endpoint dell'interfaccia utente di Swagger non siano protetti.

Tentare di accedere a un endpoint protetto

  • Eseguire l'app e passare all'interfaccia utente di Swagger.
  • Espandere un endpoint protetto, ad esempio /weatherforecast in un progetto creato dal modello api Web.
  • Selezionare Prova.
  • Seleziona Execute. La risposta è 401 - not authorized.

Testare la registrazione

  • Espandere /register e selezionare Prova.

  • Nella sezione Parametri dell'interfaccia utente viene visualizzato un corpo della richiesta di esempio:

    {
      "email": "string",
      "password": "string"
    }
    
  • Sostituire "string" con un indirizzo di posta elettronica e una password validi e quindi selezionare Esegui.

    Per rispettare le regole di convalida della password predefinite, la password deve avere una lunghezza di almeno sei caratteri e contenere almeno uno dei caratteri seguenti:

    • Lettera maiuscola
    • Lettera minuscola
    • Cifra numerica
    • Carattere non alfanumerico

    Se si immette un indirizzo di posta elettronica non valido o una password non valida, il risultato include gli errori di convalida. Ecco un esempio di corpo della risposta con errori di convalida:

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    Gli errori vengono restituiti nel formato ProblemDetails in modo che il client possa analizzarli e visualizzare gli errori di convalida in base alle esigenze.

    Una registrazione riuscita restituisce una 200 - OK risposta.

Testare l'account di accesso

  • Espandere /login e selezionare Prova. Il corpo della richiesta di esempio mostra due parametri aggiuntivi:

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    Le proprietà JSON aggiuntive non sono necessarie per questo esempio e possono essere eliminate. Impostare useCookies su true.

  • Sostituire "string" con l'indirizzo di posta elettronica e la password usati per la registrazione e quindi selezionare Esegui.

    Un account di accesso riuscito restituisce una 200 - OK risposta con un cookie nell'intestazione della risposta.

Eseguire il nuovo test dell'endpoint protetto

Dopo aver completato l'accesso, eseguire di nuovo l'endpoint protetto. L'autenticazione cookie viene inviata automaticamente con la richiesta e l'endpoint è autorizzato. CookieL'autenticazione basata su è integrata in modo sicuro nel browser e "funziona".

Test con client nonbrowser

Alcuni client Web potrebbero non includere cookie nell'intestazione per impostazione predefinita:

  • Se si usa uno strumento per testare le API, potrebbe essere necessario abilitare i cookie nelle impostazioni.

  • L'API JavaScript fetch non include cookie per impostazione predefinita. Abilitarli impostando credentials sul valore include nelle opzioni.

  • Un HttpClient oggetto in esecuzione in un'app Blazor WebAssembly deve HttpRequestMessage includere le credenziali, come nell'esempio seguente:

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

usare l'autenticazione basata su token

È consigliabile usare i cookie nelle applicazioni basate su browser, perché, per impostazione predefinita, il browser li gestisce automaticamente senza esporli a JavaScript.

Viene generato un token personalizzato (proprietario della piattaforma ASP.NET Core identity ) che può essere usato per autenticare le richieste successive. Il token viene passato nell'intestazione Authorization come token di connessione. Viene fornito anche un token di aggiornamento. Questo token consente all'applicazione di richiedere un nuovo token alla scadenza di quella precedente senza forzare l'accesso dell'utente.

I token non sono token Web JSON standard (JWT). L'uso di token personalizzati è intenzionale, perché l'API predefinita Identity è destinata principalmente a scenari semplici. L'opzione token non è progettata per essere un provider di servizi o un server di token completo identity , ma un'alternativa all'opzione cookie per i client che non possono usare i cookie.

Per usare l'autenticazione basata su token, impostare il parametro della useCookies stringa di query su false quando si chiama l'endpoint /login . I token usano lo schema di autenticazione di connessione . Usando il token restituito dalla chiamata a /login, le chiamate successive agli endpoint protetti devono aggiungere l'intestazione Authorization: Bearer <token> dove <token> è il token di accesso. Per altre informazioni, vedere Usare l'endpoint POST /login più avanti in questo articolo.

Effettuare la disconnessione

Per consentire all'utente di disconnettersi, definire un /logout endpoint come nell'esempio seguente:

app.MapPost("/logout", async (SignInManager<IdentityUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty != null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.Unauthorized();
})
.WithOpenApi()
.RequireAuthorization();

Specificare un oggetto JSON vuoto ({}) nel corpo della richiesta quando si chiama questo endpoint. Il codice seguente è un esempio di chiamata all'endpoint di disconnessione:

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

Endpoint MapIdentityApi<TUser>

La chiamata a MapIdentityApi<TUser> aggiunge gli endpoint seguenti all'app:

Usare l'endpoint POST /register

Il corpo della richiesta deve avere Email proprietà e Password :

{
  "email": "string",
  "password": "string",
}

Per altre informazioni, vedi:

Usare l'endpoint POST /login

Nel corpo Email della richiesta e Password sono necessari. Se l'autenticazione a due fattori (2FA) è abilitata, TwoFactorCode o TwoFactorRecoveryCode è necessaria. Se 2FA non è abilitato, omettere sia twoFactorCode che twoFactorRecoveryCode. Per altre informazioni, vedere Usare l'endpoint POST /manage/2fa più avanti in questo articolo.

Ecco un esempio di corpo della richiesta con 2FA non abilitato:

{
  "email": "string",
  "password": "string"
}

Ecco alcuni esempi di corpo della richiesta con 2FA abilitato:

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

L'endpoint prevede un parametro della stringa di query:

  • useCookies - Impostare su true per cookiel'autenticazione basata su . Impostare su o omettere false per l'autenticazione basata su token.

Per altre informazioni sull'autenticazione cookiebasata su , vedere Testare l'accesso in precedenza in questo articolo.

Autenticazione basata su token

Se useCookies viene false omesso, l'autenticazione basata su token è abilitata. Il corpo della risposta include le proprietà seguenti:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Per altre informazioni su queste proprietà, vedere AccessTokenResponse.

Inserire il token di accesso in un'intestazione per effettuare richieste autenticate, come illustrato nell'esempio seguente

Authorization: Bearer {access token}

Quando il token di accesso sta per scadere, chiamare l'endpoint /refresh .

Usare l'endpoint POST /refresh

Per l'uso solo con l'autenticazione basata su token. Ottiene un nuovo token di accesso senza forzare l'accesso dell'utente. Chiamare questo endpoint quando il token di accesso sta per scadere.

Il corpo della richiesta contiene solo .RefreshToken Ecco un esempio di corpo della richiesta:

{
  "refreshToken": "string"
}

Se la chiamata ha esito positivo, il corpo della risposta è un nuovo AccessTokenResponse, come illustrato nell'esempio seguente:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Usare l'endpoint GET /confirmEmail

Se Identity è configurata per la conferma tramite posta elettronica, una chiamata all'endpoint /register invia un messaggio di posta elettronica contenente un collegamento all'endpoint /confirmEmail . Il collegamento contiene i parametri della stringa di query seguenti:

  • userId
  • code
  • changedEmail - Incluso solo se l'utente ha modificato l'indirizzo di posta elettronica durante la registrazione.

Identity fornisce il testo predefinito per il messaggio di posta elettronica di conferma. Per impostazione predefinita, l'oggetto del messaggio di posta elettronica è "Conferma il messaggio di posta elettronica" e il corpo del messaggio di posta elettronica è simile all'esempio seguente:

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

Se la RequireConfirmedEmail proprietà è impostata su true, l'utente non può accedere finché l'indirizzo di posta elettronica non viene confermato facendo clic sul collegamento nel messaggio di posta elettronica. Endpoint /confirmEmail :

  • Conferma l'indirizzo di posta elettronica e consente all'utente di accedere.
  • Restituisce il testo "Grazie per confermare il messaggio di posta elettronica." nel corpo della risposta.

Per configurare Identity la conferma tramite posta elettronica, aggiungere il codice in Program.cs per impostare RequireConfirmedEmail true e aggiungere una classe che implementa al contenitore di inserimento delle dipendenze IEmailSender . Ad esempio:

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});

builder.Services.AddTransient<IEmailSender, EmailSender>();

Per altre informazioni, vedere Conferma dell'account e ripristino della password in ASP.NET Core.

Identity fornisce testo predefinito per gli altri messaggi di posta elettronica che devono essere inviati, ad esempio per 2FA e reimpostazione della password. Per personalizzare questi messaggi di posta elettronica, fornire un'implementazione personalizzata dell'interfaccia IEmailSender . Nell'esempio precedente è EmailSender una classe che implementa IEmailSender. Per altre informazioni, incluso un esempio di classe che implementa IEmailSender, vedere Conferma dell'account e ripristino della password in ASP.NET Core.

Usare l'endpoint POST /resendConfirmationEmail

Invia un messaggio di posta elettronica solo se l'indirizzo è valido per un utente registrato.

Il corpo della richiesta contiene solo .Email Ecco un esempio di corpo della richiesta:

{
  "email": "string"
}

Per altre informazioni, vedere Usare l'endpoint GET /confirmEmail in precedenza in questo articolo.

Usare l'endpoint POST /forgotPassword

Genera un messaggio di posta elettronica contenente un codice di reimpostazione della password. Inviare il codice a /resetPassword con una nuova password.

Il corpo della richiesta contiene solo .Email Ecco un esempio:

{
  "email": "string"
}

Per informazioni su come abilitare Identity l'invio di messaggi di posta elettronica, vedere Usare l'endpointGET /confirmEmail.

Usare l'endpoint POST /resetPassword

Chiamare questo endpoint dopo aver ottenuto un codice di reimpostazione chiamando l'endpoint /forgotPassword .

Il corpo della richiesta richiede Email, ResetCodee NewPassword. Ecco un esempio:

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

Usare l'endpoint POST /manage/2fa

Configura l'autenticazione a due fattori (2FA) per l'utente. Quando la funzionalità 2FA è abilitata, l'accesso con esito positivo richiede un codice prodotto da un'app di autenticazione oltre all'indirizzo di posta elettronica e alla password.

Abilitare 2FA

Per abilitare 2FA per l'utente attualmente autenticato:

  • Chiamare l'endpoint /manage/2fa , inviando un oggetto JSON vuoto ({}) nel corpo della richiesta.

  • Il corpo della SharedKey risposta fornisce insieme ad altre proprietà che non sono necessarie a questo punto. La chiave condivisa viene usata per configurare l'app di autenticazione. Esempio di corpo della risposta:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • Usare la chiave condivisa per ottenere una password monouso basata sul tempo (TOTP). Per altre informazioni, vedere Abilitare la generazione di codice a matrice per le app di autenticazione TOTP in ASP.NET Core.

  • Chiamare l'endpoint /manage/2fa , inviando TOTP e "enable": true nel corpo della richiesta. Ad esempio:

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • Il corpo della risposta conferma che IsTwoFactorEnabled è true e fornisce .RecoveryCodes I codici di ripristino vengono usati per accedere quando l'app di autenticazione non è disponibile. Esempio di corpo della risposta dopo aver abilitato correttamente 2FA:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

Accedere con 2FA

Chiamare l'endpoint, inviare l'indirizzo /login di posta elettronica, la password e TOTP nel corpo della richiesta. Ad esempio:

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

Se l'utente non ha accesso all'app di autenticazione, accedere chiamando l'endpoint /login con uno dei codici di ripristino forniti quando è stata abilitata la funzionalità 2FA. Il corpo della richiesta è simile all'esempio seguente:

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

Reimpostare i codici di ripristino

Per ottenere una nuova raccolta di codici di ripristino, chiamare questo endpoint con ResetRecoveryCodes impostato su true. Ecco un esempio di corpo della richiesta:

{
  "resetRecoveryCodes": true
}

Reimpostare la chiave condivisa

Per ottenere una nuova chiave condivisa casuale, chiamare questo endpoint con ResetSharedKey impostato su true. Ecco un esempio di corpo della richiesta:

{
  "resetSharedKey": true
}

La reimpostazione della chiave disabilita automaticamente il requisito di accesso a due fattori per l'utente autenticato fino a quando non viene riabilitato da una richiesta successiva.

Dimenticare la macchina

Per cancellare il cookie flag "ricordami" se presente, chiamare questo endpoint con ForgetMachine impostato su true. Ecco un esempio di corpo della richiesta:

{
  "forgetMachine": true
}

Questo endpoint non ha alcun impatto sull'autenticazione basata su token.

Usare l'endpoint GET /manage/info

Ottiene l'indirizzo di posta elettronica e lo stato di conferma tramite posta elettronica dell'utente connesso. Le attestazioni sono state omesse da questo endpoint per motivi di sicurezza. Se sono necessarie attestazioni, usare le API lato server per configurare un endpoint per le attestazioni. In alternativa, invece di condividere tutte le attestazioni degli utenti, fornire un endpoint di convalida che accetta un'attestazione e risponde se l'utente lo ha.

La richiesta non richiede parametri. Il corpo della risposta include le Email proprietà e IsEmailConfirmed , come nell'esempio seguente:

{
  "email": "string",
  "isEmailConfirmed": true
}

Usare l'endpoint POST /manage/info

Aggiorna l'indirizzo di posta elettronica e la password dell'utente connesso. Inviare NewEmail, NewPassworde OldPassword nel corpo della richiesta, come illustrato nell'esempio seguente:

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

Ecco un esempio del corpo della risposta:

{
  "email": "string",
  "isEmailConfirmed": false
}

Vedi anche

Per ulteriori informazioni, vedi le seguenti risorse:

I modelli di base ASP.NET offrono l'autenticazione in App a pagina singola usando il supporto per l'autorizzazione api. ASP.NET Core Identity per l'autenticazione e l'archiviazione degli utenti viene combinato con Duende Identity Server per l'implementazione di OpenID Connect.

Importante

Duende Software potrebbe richiedere il pagamento di una tariffa di licenza per l'uso in produzione di Duende Identity Server. Per altre informazioni, vedere Eseguire la migrazione da ASP.NET Core 5.0 a 6.0.

È stato aggiunto un parametro di autenticazione ai modelli di progetto Angular e React simili al parametro di autenticazione nei modelli di progetto Applicazione Web (Model-View-Controller) e Applicazione Web (Razor Pages). I valori dei parametri consentiti sono None e Individual. Il modello di progetto React ejs Redux non supporta attualmente il parametro di autenticazione.

Creare un'app con supporto per l'autorizzazione api

L'autenticazione e l'autorizzazione dell'utente possono essere usate sia con Angular che con i criteri di servizio React. Aprire una shell dei comandi ed eseguire il comando seguente:

Angular:

dotnet new angular -au Individual

React:

dotnet new react -au Individual

Il comando precedente crea un'app ASP.NET Core con una directory ClientApp contenente l'applicazione a pagina singola.

Descrizione generale dei componenti di base di ASP.NET dell'app

Le sezioni seguenti descrivono le aggiunte al progetto quando è incluso il supporto per l'autenticazione:

Program.cs

Gli esempi di codice seguenti si basano sul pacchetto NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServer . Gli esempi configurano l'autenticazione e l'autorizzazione api usando i AddApiAuthorization metodi di estensione e AddIdentityServerJwt . I progetti che usano i modelli di progetto React o Angular SPA con l'autenticazione includono un riferimento a questo pacchetto.

dotnet new angular -au Individual genera il file seguente Program.cs :

using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using output_directory_name.Data;
using output_directory_name.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

Il codice precedente configura:

  • Identity con l'interfaccia utente predefinita:

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(connectionString));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • IdentityServer con un metodo helper aggiuntivo AddApiAuthorization che configura alcune convenzioni predefinite ASP.NET Core su IdentityServer:

    builder.Services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    
  • Autenticazione con un metodo helper aggiuntivo AddIdentityServerJwt che configura l'app per convalidare i token JWT prodotti da IdentityServer:

    builder.Services.AddAuthentication()
    .AddIdentityServerJwt();
    
  • Middleware di autenticazione responsabile della convalida delle credenziali della richiesta e dell'impostazione dell'utente nel contesto della richiesta:

    app.UseAuthentication();
    
  • Middleware IdentityServer che espone gli endpoint OpenID Connect:

    app.UseIdentityServer();
    

Avviso

Questo articolo illustra l'uso di stringa di connessione. Con un database locale l'utente non deve essere autenticato, ma nell'ambiente di produzione stringa di connessione talvolta include una password per l'autenticazione. Una credenziale della password del proprietario della risorsa (ROPC) è un rischio per la sicurezza che deve essere evitato nei database di produzione. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app distribuite in ambienti di test o di produzione, vedere Proteggere i flussi di autenticazione.

Servizio app di Azure in Linux

Per le distribuzioni del servizio app Azure in Linux, specificare l'autorità di certificazione in modo esplicito:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

Nel codice precedente, il {AUTHORITY} segnaposto è l'oggetto Authority da usare quando si effettuano chiamate OpenID Connect.

Esempio:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Questo metodo helper configura IdentityServer per l'uso della configurazione supportata. IdentityServer è un framework potente ed estendibile per la gestione dei problemi di sicurezza delle app. Allo stesso tempo, che espone complessità non necessarie per gli scenari più comuni. Di conseguenza, viene fornito un set di convenzioni e opzioni di configurazione che sono considerate un buon punto di partenza. Una volta modificata l'autenticazione, la potenza completa di IdentityServer è ancora disponibile per personalizzare l'autenticazione in base alle proprie esigenze.

AddIdentityServerJwt

Questo metodo helper configura uno schema di criteri per l'app come gestore di autenticazione predefinito. Il criterio è configurato per consentire la Identity gestione di tutte le richieste indirizzate a qualsiasi sottopercorso nello Identity spazio URL "/Identity". JwtBearerHandler Gestisce tutte le altre richieste. Inoltre, questo metodo registra una <<ApplicationName>>API risorsa API con IdentityServer con un ambito predefinito e <<ApplicationName>>API configura il middleware del token di connessione JWT per convalidare i token rilasciati da IdentityServer per l'app.

WeatherForecastController

Nel file osservare l'attributo [Authorize] applicato alla classe che indica che l'utente deve essere autorizzato in base ai criteri predefiniti per accedere alla risorsa. I criteri di autorizzazione predefiniti vengono configurati per l'uso dello schema di autenticazione predefinito, configurato dallo AddIdentityServerJwt schema di criteri indicato in precedenza, rendendo configurato JwtBearerHandler da tale metodo helper il gestore predefinito per le richieste all'app.

ApplicationDbContext

Nel file si noti che lo stesso DbContext viene usato in Identity con l'eccezione che estende ApiAuthorizationDbContext (una classe più derivata da IdentityDbContext) per includere lo schema per IdentityServer.

Per ottenere il controllo completo dello schema del database, ereditare da una delle classi disponibili IdentityDbContext e configurare il contesto per includere lo Identity schema chiamando builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) sul OnModelCreating metodo .

OidcConfigurationController

Nel file notare l'endpoint di cui è stato effettuato il provisioning per gestire i parametri OIDC che il client deve usare.

appsettings.json

appsettings.json Nel file della radice del progetto è presente una nuova IdentityServer sezione che descrive l'elenco dei client configurati. Nell'esempio seguente è presente un singolo client. Il nome del client corrisponde al nome dell'app ed è mappato per convenzione al parametro OAuth ClientId . Il profilo indica il tipo di app configurato. Viene usata internamente per guidare convenzioni che semplificano il processo di configurazione per il server. Sono disponibili diversi profili, come illustrato nella sezione Profili applicazione.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

appsettings.Development.json Nel file della radice del progetto è presente una IdentityServer sezione che descrive la chiave usata per firmare i token. Quando si esegue la distribuzione nell'ambiente di produzione, è necessario eseguire il provisioning e la distribuzione di una chiave insieme all'app, come illustrato nella sezione Distribuisci in produzione .

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Descrizione generale dell'app Angular

Il supporto dell'autenticazione e dell'autorizzazione API nel modello Angular si trova nel proprio modulo Angular nella directory ClientApp/src/api-authorization . Il modulo è costituito dagli elementi seguenti:

  • 3 componenti:
    • login.component.ts: gestisce il flusso di accesso dell'app.
    • logout.component.ts: gestisce il flusso di disconnessione dell'app.
    • login-menu.component.ts: widget che visualizza uno dei set di collegamenti seguenti:
      • Gestione dei profili utente e collegamenti di disconnesso quando l'utente viene autenticato.
      • Collegamenti di registrazione e accesso quando l'utente non è autenticato.
  • Una protezione AuthorizeGuard della route che può essere aggiunta alle route e richiede l'autenticazione di un utente prima di visitare la route.
  • Intercettore AuthorizeInterceptor HTTP che collega il token di accesso alle richieste HTTP in uscita destinate all'API quando l'utente viene autenticato.
  • Un servizio AuthorizeService che gestisce i dettagli di livello inferiore del processo di autenticazione ed espone informazioni sull'utente autenticato all'oggetto rest dell'app per l'utilizzo.
  • Modulo Angular che definisce le route associate alle parti di autenticazione dell'app. Espone il componente di menu di accesso, l'intercettore, la protezione e il servizio per l'utilizzo rest da parte dell'app.

Descrizione generale dell'app React

Il supporto per l'autenticazione e l'autorizzazione API nel modello React risiede nella directory ClientApp/src/components/api-authorization . È costituito dagli elementi seguenti:

  • 4 componenti:
    • Login.js: gestisce il flusso di accesso dell'app.
    • Logout.js: gestisce il flusso di disconnessione dell'app.
    • LoginMenu.js: widget che visualizza uno dei set di collegamenti seguenti:
      • Gestione dei profili utente e collegamenti di disconnesso quando l'utente viene autenticato.
      • Collegamenti di registrazione e accesso quando l'utente non è autenticato.
    • AuthorizeRoute.js: componente di route che richiede l'autenticazione di un utente prima di eseguire il rendering del Component componente indicato nel parametro .
  • Istanza esportata authService della classe AuthorizeService che gestisce i dettagli di livello inferiore del processo di autenticazione ed espone informazioni sull'utente autenticato all'oggetto rest dell'app per l'utilizzo.

Dopo aver visto i componenti principali della soluzione, è possibile esaminare in modo più approfondito i singoli scenari per l'app.

Richiedere l'autorizzazione in una nuova API

Per impostazione predefinita, il sistema è configurato per richiedere facilmente l'autorizzazione per le nuove API. A tale scopo, creare un nuovo controller e aggiungere l'attributo [Authorize] alla classe controller o a qualsiasi azione all'interno del controller.

Personalizzare il gestore di autenticazione API

Per personalizzare la configurazione del gestore JWT dell'API, configurarne l'istanza JwtBearerOptions :

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

Il gestore JWT dell'API genera eventi che consentono il controllo sul processo di autenticazione tramite JwtBearerEvents. Per fornire supporto per l'autorizzazione API, AddIdentityServerJwt registra i propri gestori eventi.

Per personalizzare la gestione di un evento, eseguire il wrapping del gestore eventi esistente con logica aggiuntiva in base alle esigenze. Ad esempio:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

Nel codice precedente il OnTokenValidated gestore eventi viene sostituito con un'implementazione personalizzata. Questa implementazione:

  1. Chiama l'implementazione originale fornita dal supporto dell'autorizzazione API.
  2. Eseguire la propria logica personalizzata.

Proteggere una route lato client (Angular)

La protezione di una route lato client viene eseguita aggiungendo la protezione dell'autorizzazione all'elenco di misure di sicurezza da eseguire durante la configurazione di una route. Ad esempio, è possibile vedere come viene configurata la fetch-data route all'interno del modulo Angular dell'app principale:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

È importante ricordare che la protezione di una route non protegge l'endpoint effettivo (che richiede ancora un [Authorize] attributo applicato), ma che impedisce solo all'utente di passare alla route sul lato client specificata quando non viene autenticata.

Autenticare le richieste API (Angular)

L'autenticazione delle richieste alle API ospitate insieme all'app viene eseguita automaticamente tramite l'uso dell'intercettore client HTTP definito dall'app.

Proteggere una route lato client (React)

Proteggere una route lato client usando il AuthorizeRoute componente anziché il componente normale Route . Si noti ad esempio come la fetch-data route viene configurata all'interno del App componente:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Protezione di una route:

  • Non protegge l'endpoint effettivo (che richiede comunque un [Authorize] attributo applicato).
  • Impedisce solo all'utente di passare alla route sul lato client specificata quando non è autenticata.

Autenticare le richieste API (React)

L'autenticazione delle richieste con React viene eseguita prima importando l'istanza authService da AuthorizeService. Il token di accesso viene recuperato da authService e viene collegato alla richiesta, come illustrato di seguito. Nei componenti React questo lavoro viene in genere eseguito nel metodo del componentDidMount ciclo di vita o come risultato di un'interazione dell'utente.

Importare in authService un componente

import authService from './api-authorization/AuthorizeService'

Recuperare e collegare il token di accesso alla risposta

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Distribuzione nell'ambiente di produzione

Per distribuire l'app nell'ambiente di produzione, è necessario effettuare il provisioning delle risorse seguenti:

  • Un database per archiviare gli Identity account utente e le concessioni IdentityServer.
  • Certificato di produzione da usare per la firma dei token.
    • Non esistono requisiti specifici per questo certificato; può essere un certificato autofirmato o un certificato di cui è stato effettuato il provisioning tramite un'autorità di certificazione.
    • Può essere generato tramite strumenti standard come PowerShell o OpenSSL.
    • Può essere installato nell'archivio certificati nei computer di destinazione o distribuito come file con estensione pfx con una password complessa.

Esempio: Eseguire la distribuzione in un provider di hosting Web non Di Azure

Nel pannello di hosting Web creare o caricare il certificato. Quindi, nel file dell'app appsettings.json , modificare la IdentityServer sezione in modo da includere i dettagli della chiave. Ad esempio:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

Nell'esempio precedente:

  • StoreName rappresenta il nome dell'archivio certificati in cui è archiviato il certificato. In questo caso, punta all'archivio di hosting Web.
  • StoreLocation rappresenta dove caricare il certificato da (CurrentUser in questo caso).
  • Name corrisponde all'oggetto distinto per il certificato.

Esempio: Eseguire la distribuzione nel servizio app Azure

Questa sezione descrive la distribuzione dell'app in app Azure Servizio usando un certificato archiviato nell'archivio certificati. Per modificare l'app per caricare un certificato dall'archivio certificati, è necessario un piano di servizio di livello Standard o superiore quando si configura l'app nel portale di Azure in un passaggio successivo.

Nel file dell'app appsettings.json modificare la IdentityServer sezione in modo da includere i dettagli chiave:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • Il nome dell'archivio rappresenta il nome dell'archivio certificati in cui è archiviato il certificato. In questo caso, punta all'archivio utente personale.
  • Il percorso dell'archivio rappresenta la posizione in cui caricare il certificato da (CurrentUser o LocalMachine).
  • La proprietà name del certificato corrisponde all'oggetto distinto per il certificato.

Per eseguire la distribuzione nel servizio app Azure, seguire la procedura descritta in Distribuire l'app in Azure, che illustra come creare le risorse di Azure necessarie e distribuire l'app nell'ambiente di produzione.

Dopo aver seguito le istruzioni precedenti, l'app viene distribuita in Azure ma non è ancora funzionante. Il certificato usato dall'app deve essere configurato nel portale di Azure. Individuare l'identificazione personale per il certificato e seguire i passaggi descritti in Caricare i certificati.

Anche se questi passaggi indicano SSL, è disponibile una sezione Certificati privati nella portale di Azure in cui è possibile caricare il certificato di cui è stato effettuato il provisioning da usare con l'app.

Dopo aver configurato l'app e le impostazioni dell'app nel portale di Azure, riavviare l'app nel portale.

Altre opzioni di configurazione

Il supporto per l'autorizzazione API si basa su IdentityServer con un set di convenzioni, valori predefiniti e miglioramenti per semplificare l'esperienza per le applicazioni a pagina singola. Inutile dire che la piena potenza di IdentityServer è disponibile in background se le integrazioni di ASP.NET Core non coprono lo scenario. Il supporto di ASP.NET Core è incentrato sulle app "proprietarie", in cui tutte le app vengono create e distribuite dall'organizzazione. Di conseguenza, il supporto non viene offerto per elementi come il consenso o la federazione. Per questi scenari, usare IdentityServer e seguire la relativa documentazione.

Profili applicazione

I profili applicazione sono configurazioni predefinite per le app che definiscono ulteriormente i relativi parametri. Al momento sono supportati i profili seguenti:

  • IdentityServerSPA: rappresenta un'applicazione a pagina singola ospitata insieme a IdentityServer come singola unità.
    • Il redirect_uri valore predefinito è /authentication/login-callback.
    • Il post_logout_redirect_uri valore predefinito è /authentication/logout-callback.
    • Il set di ambiti include , openidprofilee ogni ambito definito per le API nell'app.
    • Il set di tipi di risposta OIDC consentiti è id_token token o ognuno di essi singolarmente (id_token, token).
    • La modalità di risposta consentita è fragment.
  • SPA: rappresenta un'applicazione a pagina singola non ospitata con IdentityServer.
    • Il set di ambiti include , openidprofilee ogni ambito definito per le API nell'app.
    • Il set di tipi di risposta OIDC consentiti è id_token token o ognuno di essi singolarmente (id_token, token).
    • La modalità di risposta consentita è fragment.
  • IdentityServerJwt: rappresenta un'API ospitata insieme a IdentityServer.
    • L'app è configurata in modo da avere un singolo ambito che per impostazione predefinita corrisponde al nome dell'app.
  • API: rappresenta un'API non ospitata con IdentityServer.
    • L'app è configurata in modo da avere un singolo ambito che per impostazione predefinita corrisponde al nome dell'app.

Configurazione tramite AppSettings

Configurare le app tramite il sistema di configurazione aggiungendole all'elenco di Clients o Resources.

Configurare la proprietà e post_logout_redirect_uri di redirect_uri ogni client, come illustrato nell'esempio seguente:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Quando si configurano le risorse, è possibile configurare gli ambiti per la risorsa come illustrato di seguito:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configurazione tramite codice

È anche possibile configurare i client e le risorse tramite il codice usando un overload di AddApiAuthorization che esegue un'azione per configurare le opzioni.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Risorse aggiuntive

I modelli di ASP.NET Core 3.1 e versioni successive offrono l'autenticazione in App a pagina singola usando il supporto per l'autorizzazione api. ASP.NET Core Identity per l'autenticazione e l'archiviazione degli utenti viene combinato con IdentityServer per l'implementazione di OpenID Connect.

È stato aggiunto un parametro di autenticazione ai modelli di progetto Angular e React simili al parametro di autenticazione nei modelli di progetto Applicazione Web (Model-View-Controller) e Applicazione Web (Razor Pages). I valori dei parametri consentiti sono None e Individual. Il modello di progetto React ejs Redux non supporta attualmente il parametro di autenticazione.

Creare un'app con supporto per l'autorizzazione api

L'autenticazione e l'autorizzazione dell'utente possono essere usate sia con Angular che con i criteri di servizio React. Aprire una shell dei comandi ed eseguire il comando seguente:

Angular:

dotnet new angular -o <output_directory_name> 

React:

dotnet new react -o <output_directory_name> -au Individual

Il comando precedente crea un'app ASP.NET Core con una directory ClientApp contenente l'applicazione a pagina singola.

Descrizione generale dei componenti di base di ASP.NET dell'app

Le sezioni seguenti descrivono le aggiunte al progetto quando è incluso il supporto per l'autenticazione:

Classe Startup

Gli esempi di codice seguenti si basano sul pacchetto NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServer . Gli esempi configurano l'autenticazione e l'autorizzazione api usando i AddApiAuthorization metodi di estensione e AddIdentityServerJwt . I progetti che usano i modelli di progetto React o Angular SPA con l'autenticazione includono un riferimento a questo pacchetto.

La Startup classe presenta le aggiunte seguenti:

  • All'interno del Startup.ConfigureServices metodo :

    • Identity con l'interfaccia utente predefinita:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>()
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer con un metodo helper aggiuntivo AddApiAuthorization che configura alcune convenzioni predefinite ASP.NET Core su IdentityServer:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Autenticazione con un metodo helper aggiuntivo AddIdentityServerJwt che configura l'app per convalidare i token JWT prodotti da IdentityServer:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • All'interno del Startup.Configure metodo :

    • Middleware di autenticazione responsabile della convalida delle credenziali della richiesta e dell'impostazione dell'utente nel contesto della richiesta:

      app.UseAuthentication();
      
    • Middleware IdentityServer che espone gli endpoint OpenID Connect:

      app.UseIdentityServer();
      

Avviso

Questo articolo illustra l'uso di stringa di connessione. Con un database locale l'utente non deve essere autenticato, ma nell'ambiente di produzione stringa di connessione talvolta include una password per l'autenticazione. Una credenziale della password del proprietario della risorsa (ROPC) è un rischio per la sicurezza che deve essere evitato nei database di produzione. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app distribuite in ambienti di test o di produzione, vedere Proteggere i flussi di autenticazione.

Servizio app di Azure in Linux

Per le distribuzioni del servizio app Azure in Linux, specificare l'autorità di certificazione in modo esplicito in Startup.ConfigureServices:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

Nel codice precedente, il {AUTHORITY} segnaposto è l'oggetto Authority da usare quando si effettuano chiamate OpenID Connect.

Esempio:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Questo metodo helper configura IdentityServer per l'uso della configurazione supportata. IdentityServer è un framework potente ed estendibile per la gestione dei problemi di sicurezza delle app. Allo stesso tempo, che espone complessità non necessarie per gli scenari più comuni. Di conseguenza, viene fornito un set di convenzioni e opzioni di configurazione che sono considerate un buon punto di partenza. Una volta modificata l'autenticazione, la potenza completa di IdentityServer è ancora disponibile per personalizzare l'autenticazione in base alle proprie esigenze.

AddIdentityServerJwt

Questo metodo helper configura uno schema di criteri per l'app come gestore di autenticazione predefinito. Il criterio è configurato per consentire la Identity gestione di tutte le richieste indirizzate a qualsiasi sottopercorso nello Identity spazio URL "/Identity". JwtBearerHandler Gestisce tutte le altre richieste. Inoltre, questo metodo registra una <<ApplicationName>>API risorsa API con IdentityServer con un ambito predefinito e <<ApplicationName>>API configura il middleware del token di connessione JWT per convalidare i token rilasciati da IdentityServer per l'app.

WeatherForecastController

Nel file osservare l'attributo [Authorize] applicato alla classe che indica che l'utente deve essere autorizzato in base ai criteri predefiniti per accedere alla risorsa. I criteri di autorizzazione predefiniti vengono configurati per l'uso dello schema di autenticazione predefinito, configurato dallo AddIdentityServerJwt schema di criteri indicato in precedenza, rendendo configurato JwtBearerHandler da tale metodo helper il gestore predefinito per le richieste all'app.

ApplicationDbContext

Nel file si noti che lo stesso DbContext viene usato in Identity con l'eccezione che estende ApiAuthorizationDbContext (una classe più derivata da IdentityDbContext) per includere lo schema per IdentityServer.

Per ottenere il controllo completo dello schema del database, ereditare da una delle classi disponibili IdentityDbContext e configurare il contesto per includere lo Identity schema chiamando builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) sul OnModelCreating metodo .

OidcConfigurationController

Nel file notare l'endpoint di cui è stato effettuato il provisioning per gestire i parametri OIDC che il client deve usare.

appsettings.json

appsettings.json Nel file della radice del progetto è presente una nuova IdentityServer sezione che descrive l'elenco dei client configurati. Nell'esempio seguente è presente un singolo client. Il nome del client corrisponde al nome dell'app ed è mappato per convenzione al parametro OAuth ClientId . Il profilo indica il tipo di app configurato. Viene usata internamente per guidare convenzioni che semplificano il processo di configurazione per il server. Sono disponibili diversi profili, come illustrato nella sezione Profili applicazione.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

appsettings.Development.json Nel file della radice del progetto è presente una IdentityServer sezione che descrive la chiave usata per firmare i token. Quando si esegue la distribuzione nell'ambiente di produzione, è necessario eseguire il provisioning e la distribuzione di una chiave insieme all'app, come illustrato nella sezione Distribuisci in produzione .

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Descrizione generale dell'app Angular

Il supporto dell'autenticazione e dell'autorizzazione API nel modello Angular si trova nel proprio modulo Angular nella directory ClientApp/src/api-authorization . Il modulo è costituito dagli elementi seguenti:

  • 3 componenti:
    • login.component.ts: gestisce il flusso di accesso dell'app.
    • logout.component.ts: gestisce il flusso di disconnessione dell'app.
    • login-menu.component.ts: widget che visualizza uno dei set di collegamenti seguenti:
      • Gestione dei profili utente e collegamenti di disconnesso quando l'utente viene autenticato.
      • Collegamenti di registrazione e accesso quando l'utente non è autenticato.
  • Una protezione AuthorizeGuard della route che può essere aggiunta alle route e richiede l'autenticazione di un utente prima di visitare la route.
  • Intercettore AuthorizeInterceptor HTTP che collega il token di accesso alle richieste HTTP in uscita destinate all'API quando l'utente viene autenticato.
  • Un servizio AuthorizeService che gestisce i dettagli di livello inferiore del processo di autenticazione ed espone informazioni sull'utente autenticato all'oggetto rest dell'app per l'utilizzo.
  • Modulo Angular che definisce le route associate alle parti di autenticazione dell'app. Espone il componente di menu di accesso, l'intercettore, la protezione e il servizio per l'utilizzo rest da parte dell'app.

Descrizione generale dell'app React

Il supporto per l'autenticazione e l'autorizzazione API nel modello React risiede nella directory ClientApp/src/components/api-authorization . È costituito dagli elementi seguenti:

  • 4 componenti:
    • Login.js: gestisce il flusso di accesso dell'app.
    • Logout.js: gestisce il flusso di disconnessione dell'app.
    • LoginMenu.js: widget che visualizza uno dei set di collegamenti seguenti:
      • Gestione dei profili utente e collegamenti di disconnesso quando l'utente viene autenticato.
      • Collegamenti di registrazione e accesso quando l'utente non è autenticato.
    • AuthorizeRoute.js: componente di route che richiede l'autenticazione di un utente prima di eseguire il rendering del Component componente indicato nel parametro .
  • Istanza esportata authService della classe AuthorizeService che gestisce i dettagli di livello inferiore del processo di autenticazione ed espone informazioni sull'utente autenticato all'oggetto rest dell'app per l'utilizzo.

Dopo aver visto i componenti principali della soluzione, è possibile esaminare in modo più approfondito i singoli scenari per l'app.

Richiedere l'autorizzazione in una nuova API

Per impostazione predefinita, il sistema è configurato per richiedere facilmente l'autorizzazione per le nuove API. A tale scopo, creare un nuovo controller e aggiungere l'attributo [Authorize] alla classe controller o a qualsiasi azione all'interno del controller.

Personalizzare il gestore di autenticazione API

Per personalizzare la configurazione del gestore JWT dell'API, configurarne l'istanza JwtBearerOptions :

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

Il gestore JWT dell'API genera eventi che consentono il controllo sul processo di autenticazione tramite JwtBearerEvents. Per fornire supporto per l'autorizzazione API, AddIdentityServerJwt registra i propri gestori eventi.

Per personalizzare la gestione di un evento, eseguire il wrapping del gestore eventi esistente con logica aggiuntiva in base alle esigenze. Ad esempio:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

Nel codice precedente il OnTokenValidated gestore eventi viene sostituito con un'implementazione personalizzata. Questa implementazione:

  1. Chiama l'implementazione originale fornita dal supporto dell'autorizzazione API.
  2. Eseguire la propria logica personalizzata.

Proteggere una route lato client (Angular)

La protezione di una route lato client viene eseguita aggiungendo la protezione dell'autorizzazione all'elenco di misure di sicurezza da eseguire durante la configurazione di una route. Ad esempio, è possibile vedere come viene configurata la fetch-data route all'interno del modulo Angular dell'app principale:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

È importante ricordare che la protezione di una route non protegge l'endpoint effettivo (che richiede ancora un [Authorize] attributo applicato), ma che impedisce solo all'utente di passare alla route sul lato client specificata quando non viene autenticata.

Autenticare le richieste API (Angular)

L'autenticazione delle richieste alle API ospitate insieme all'app viene eseguita automaticamente tramite l'uso dell'intercettore client HTTP definito dall'app.

Proteggere una route lato client (React)

Proteggere una route lato client usando il AuthorizeRoute componente anziché il componente normale Route . Si noti ad esempio come la fetch-data route viene configurata all'interno del App componente:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Protezione di una route:

  • Non protegge l'endpoint effettivo (che richiede comunque un [Authorize] attributo applicato).
  • Impedisce solo all'utente di passare alla route sul lato client specificata quando non è autenticata.

Autenticare le richieste API (React)

L'autenticazione delle richieste con React viene eseguita prima importando l'istanza authService da AuthorizeService. Il token di accesso viene recuperato da authService e viene collegato alla richiesta, come illustrato di seguito. Nei componenti React questo lavoro viene in genere eseguito nel metodo del componentDidMount ciclo di vita o come risultato di un'interazione dell'utente.

Importare in authService un componente

import authService from './api-authorization/AuthorizeService'

Recuperare e collegare il token di accesso alla risposta

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Distribuzione nell'ambiente di produzione

Per distribuire l'app nell'ambiente di produzione, è necessario effettuare il provisioning delle risorse seguenti:

  • Un database per archiviare gli Identity account utente e le concessioni IdentityServer.
  • Certificato di produzione da usare per la firma dei token.
    • Non esistono requisiti specifici per questo certificato; può essere un certificato autofirmato o un certificato di cui è stato effettuato il provisioning tramite un'autorità di certificazione.
    • Può essere generato tramite strumenti standard come PowerShell o OpenSSL.
    • Può essere installato nell'archivio certificati nei computer di destinazione o distribuito come file con estensione pfx con una password complessa.

Esempio: Eseguire la distribuzione in un provider di hosting Web non Di Azure

Nel pannello di hosting Web creare o caricare il certificato. Quindi, nel file dell'app appsettings.json , modificare la IdentityServer sezione in modo da includere i dettagli della chiave. Ad esempio:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

Nell'esempio precedente:

  • StoreName rappresenta il nome dell'archivio certificati in cui è archiviato il certificato. In questo caso, punta all'archivio di hosting Web.
  • StoreLocation rappresenta dove caricare il certificato da (CurrentUser in questo caso).
  • Name corrisponde all'oggetto distinto per il certificato.

Esempio: Eseguire la distribuzione nel servizio app Azure

Questa sezione descrive la distribuzione dell'app in app Azure Servizio usando un certificato archiviato nell'archivio certificati. Per modificare l'app per caricare un certificato dall'archivio certificati, è necessario un piano di servizio di livello Standard o superiore quando si configura l'app nel portale di Azure in un passaggio successivo.

Nel file dell'app appsettings.json modificare la IdentityServer sezione in modo da includere i dettagli chiave:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • Il nome dell'archivio rappresenta il nome dell'archivio certificati in cui è archiviato il certificato. In questo caso, punta all'archivio utente personale.
  • Il percorso dell'archivio rappresenta la posizione in cui caricare il certificato da (CurrentUser o LocalMachine).
  • La proprietà name del certificato corrisponde all'oggetto distinto per il certificato.

Per eseguire la distribuzione nel servizio app Azure, seguire la procedura descritta in Distribuire l'app in Azure, che illustra come creare le risorse di Azure necessarie e distribuire l'app nell'ambiente di produzione.

Dopo aver seguito le istruzioni precedenti, l'app viene distribuita in Azure ma non è ancora funzionante. Il certificato usato dall'app deve essere configurato nel portale di Azure. Individuare l'identificazione personale per il certificato e seguire i passaggi descritti in Caricare i certificati.

Anche se questi passaggi indicano SSL, è disponibile una sezione Certificati privati nella portale di Azure in cui è possibile caricare il certificato di cui è stato effettuato il provisioning da usare con l'app.

Dopo aver configurato l'app e le impostazioni dell'app nel portale di Azure, riavviare l'app nel portale.

Altre opzioni di configurazione

Il supporto per l'autorizzazione API si basa su IdentityServer con un set di convenzioni, valori predefiniti e miglioramenti per semplificare l'esperienza per le applicazioni a pagina singola. Inutile dire che la piena potenza di IdentityServer è disponibile in background se le integrazioni di ASP.NET Core non coprono lo scenario. Il supporto di ASP.NET Core è incentrato sulle app "proprietarie", in cui tutte le app vengono create e distribuite dall'organizzazione. Di conseguenza, il supporto non viene offerto per elementi come il consenso o la federazione. Per questi scenari, usare IdentityServer e seguire la relativa documentazione.

Profili applicazione

I profili applicazione sono configurazioni predefinite per le app che definiscono ulteriormente i relativi parametri. Al momento sono supportati i profili seguenti:

  • IdentityServerSPA: rappresenta un'applicazione a pagina singola ospitata insieme a IdentityServer come singola unità.
    • Il redirect_uri valore predefinito è /authentication/login-callback.
    • Il post_logout_redirect_uri valore predefinito è /authentication/logout-callback.
    • Il set di ambiti include , openidprofilee ogni ambito definito per le API nell'app.
    • Il set di tipi di risposta OIDC consentiti è id_token token o ognuno di essi singolarmente (id_token, token).
    • La modalità di risposta consentita è fragment.
  • SPA: rappresenta un'applicazione a pagina singola non ospitata con IdentityServer.
    • Il set di ambiti include , openidprofilee ogni ambito definito per le API nell'app.
    • Il set di tipi di risposta OIDC consentiti è id_token token o ognuno di essi singolarmente (id_token, token).
    • La modalità di risposta consentita è fragment.
  • IdentityServerJwt: rappresenta un'API ospitata insieme a IdentityServer.
    • L'app è configurata in modo da avere un singolo ambito che per impostazione predefinita corrisponde al nome dell'app.
  • API: rappresenta un'API non ospitata con IdentityServer.
    • L'app è configurata in modo da avere un singolo ambito che per impostazione predefinita corrisponde al nome dell'app.

Configurazione tramite AppSettings

Configurare le app tramite il sistema di configurazione aggiungendole all'elenco di Clients o Resources.

Configurare la proprietà e post_logout_redirect_uri di redirect_uri ogni client, come illustrato nell'esempio seguente:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Quando si configurano le risorse, è possibile configurare gli ambiti per la risorsa come illustrato di seguito:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configurazione tramite codice

È anche possibile configurare i client e le risorse tramite il codice usando un overload di AddApiAuthorization che esegue un'azione per configurare le opzioni.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Risorse aggiuntive