Integrare MSAL.NET con Microsoft. Identity.Web in .NET Framework

Questa guida illustra come utilizzare Microsoft.Identity.Web per la cache dei token e i pacchetti di certificati con MSAL.NET nel .NET Framework, in .NET Standard 2.0 e nelle applicazioni classiche di .NET (.NET 4.7.2+).

Informazioni sulla panoramica

A partire da Microsoft. Identity.Web 1.17+, è possibile usare Microsoft. Pacchetti di utilità Identity.Web con MSAL.NET in ambienti non ASP.NET Core.

Identificare i vantaggi dei pacchetti

Feature Beneficio
Serializzazione della cache dei token Adattatori cache riutilizzabili per in-memory, SQL Server, Redis, Cosmos DB, PostgreSQL
Assistenti certificato Caricamento semplificato dei certificati da Key Vault, file system o archivi di certificati
Estensioni delle attestazioni Metodi di utilità per la manipolazione di ClaimsPrincipal
.NET Standard 2.0 Compatibile con .NET Framework 4.7.2+, .NET Core e .NET 5+
Dipendenze minime Pacchetti mirati senza dipendenze da ASP.NET Core

Esaminare gli scenari supportati

Gli scenari seguenti sono supportati dai pacchetti di utilità mirati.

  • .NET Framework Console Applications (scenari demone)
  • Desktop Applications (.NET Framework)
  • Servizi di lavoro (.NET Framework)
  • librerie .NET Standard 2.0 (compatibilità multipiattaforma)
  • Applicazione di MSAL.NET Non Web

Annotazioni

Per ASP.NET MVC/applicazioni API Web, vedere invece OWIN Integration.


Selezionare i pacchetti

Scegliere il pacchetto corrispondente allo scenario.

Identificare i pacchetti principali per MSAL.NET

Package Scopo Dipendenze destinazione .NET
Microsoft. Identity.Web.TokenCache Serializzatori della cache dei token, ClaimsPrincipal estensioni Minime .NET Standard 2.0
Microsoft. Identity.Web.Certificate Utilità di caricamento dei certificati Minime .NET Standard 2.0

Installare i pacchetti

Usare uno dei metodi seguenti per aggiungere i pacchetti al progetto.

Gestione pacchetti Console:

# Token cache serialization
Install-Package Microsoft.Identity.Web.TokenCache

# Certificate management
Install-Package Microsoft.Identity.Web.Certificate

.NET CLI:

dotnet add package Microsoft.Identity.Web.TokenCache
dotnet add package Microsoft.Identity.Web.Certificate

Informazioni sulle limitazioni dei pacchetti principali

Il pacchetto core Microsoft.Identity.Web include le dipendenze di ASP.NET Core (Microsoft.AspNetCore.*), che:

  • Non sono compatibili con ASP.NET Framework
  • Aumentare le dimensioni del pacchetto inutilmente
  • Creare conflitti di dipendenza

Usare invece pacchetti di destinazione per .NET Framework e .NET scenari Standard.


Configurare la serializzazione della cache dei token

Informazioni sugli adattatori della cache dei token

Microsoft. Identity.Web fornisce adattatori della cache dei token che funzionano perfettamente con MSAL.NET IConfidentialClientApplication.

Creare un client riservato con la cache dei token

L'esempio seguente crea un'applicazione client riservata e collega una cache dei token in memoria.

using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders;

public class MsalAppBuilder
{
    private static IConfidentialClientApplication _app;

    public static IConfidentialClientApplication BuildConfidentialClientApplication()
    {
        if (_app == null)
        {
            string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];
            string clientSecret = ConfigurationManager.AppSettings["AzureAd:ClientSecret"];
            string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];

            // Create the confidential client application
            _app = ConfidentialClientApplicationBuilder.Create(clientId)
                .WithClientSecret(clientSecret)
                .WithTenantId(tenantId)
                .WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
                .Build();

            // Add token cache serialization (choose one option below)
            _app.AddInMemoryTokenCache();
        }

        return _app;
    }
}

Scegliere le opzioni della cache dei token

Selezionare il provider di cache più adatto allo scenario di distribuzione.

Configurare la cache dei token in memoria

L'esempio seguente aggiunge una semplice cache in memoria:

using Microsoft.Identity.Web.TokenCacheProviders;

_app.AddInMemoryTokenCache();

Cache in memoria con limiti di dimensione (Microsoft.Identity.Web 1.20+)

using Microsoft.Extensions.Caching.Memory;

_app.AddInMemoryTokenCache(services =>
{
    // Configure memory cache options
    services.Configure<MemoryCacheOptions>(options =>
    {
        options.SizeLimit = 5000000;  // 5 MB limit
    });
});

Caratteristiche:

  • Accesso rapido
  • Nessuna dipendenza esterna
  • Non condiviso tra processi
  • Perso al riavvio dell'app

Caso d'uso: App console a istanza singola, applicazioni desktop


Configurare la cache dei token in memoria distribuita

Usare il codice seguente per aggiungere una cache distribuita in memoria per ambienti a istanze multipla:

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.Memory (NuGet)
    services.AddDistributedMemoryCache();
});

Caratteristiche:

  • Condiviso tra istanze dell'app
  • Migliore per gli scenari con carico bilanciato
  • Richiede un pacchetto NuGet aggiuntivo
  • Ancora perso al riavvio dell'app

Caso d'uso: Servizi a istanze multipla con acquisizione di token accettabile


Configurare la cache dei token di SQL Server

Usare il codice seguente per aggiungere una cache SQL Server persistente distribuita:

using Microsoft.Extensions.Caching.SqlServer;

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.SqlServer (NuGet)
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = ConfigurationManager.ConnectionStrings["TokenCache"].ConnectionString;
        options.SchemaName = "dbo";
        options.TableName = "TokenCache";

        // IMPORTANT: Set expiration above token lifetime
        // Access tokens typically expire after 1 hour
        options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
    });
});

Eseguire il codice SQL seguente per creare la tabella della cache richiesta:

-- Create the cache table
CREATE TABLE [dbo].[TokenCache] (
    [Id] NVARCHAR(449) NOT NULL,
    [Value] VARBINARY(MAX) NOT NULL,
    [ExpiresAtTime] DATETIMEOFFSET NOT NULL,
    [SlidingExpirationInSeconds] BIGINT NULL,
    [AbsoluteExpiration] DATETIMEOFFSET NULL,
    PRIMARY KEY ([Id])
);

-- Create index for performance
CREATE INDEX [Index_ExpiresAtTime] ON [dbo].[TokenCache] ([ExpiresAtTime]);

Caratteristiche:

  • Persistente attraverso i riavvii
  • Condiviso tra più istanze
  • Affidabile e scalabile
  • Richiede SQL Server configurazione

Caso d'uso: Servizi daemon di produzione, attività pianificate, ruoli di lavoro a istanze multiple


Configurare la cache dei token Redis

Usare il codice seguente per aggiungere una cache distribuita Redis ad alte prestazioni:

using StackExchange.Redis;
using Microsoft.Extensions.Caching.StackExchangeRedis;

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.StackExchangeRedis (NuGet)
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = ConfigurationManager.AppSettings["Redis:ConnectionString"];
        options.InstanceName = "TokenCache_";
    });
});

L'esempio seguente illustra una configurazione redis pronta per la produzione:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = ConfigurationManager.AppSettings["Redis:ConnectionString"];
    options.InstanceName = "MyDaemonApp_";

    // Optional: Configure Redis options
    options.ConfigurationOptions = new ConfigurationOptions
    {
        AbortOnConnectFail = false,
        ConnectTimeout = 5000,
        SyncTimeout = 5000
    };
});

Caratteristiche:

  • Estremamente veloce
  • Condivisi tra istanze
  • Persistente (con persistenza Redis abilitata)
  • Richiede il server Redis

Caso d'uso: App daemon con volumi elevati, sistemi distribuiti, microservizi


Configurare la cache dei token di Cosmos DB

Usare il codice seguente per aggiungere una cache Cosmos DB distribuita a livello globale:

using Microsoft.Extensions.Caching.Cosmos;

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.Cosmos (preview)
    services.AddCosmosCache(options =>
    {
        options.ContainerName = "TokenCache";
        options.DatabaseName = "IdentityCache";
        options.ClientBuilder = new CosmosClientBuilder(
            ConfigurationManager.AppSettings["CosmosConnectionString"]);
        options.CreateIfNotExists = true;
    });
});

Caratteristiche:

  • Distribuito a livello globale
  • A disponibilità elevata
  • Scalabilità automatica
  • Latenza più elevata rispetto a Redis
  • Costo più elevato

Caso d'uso: Servizi daemon globali, applicazioni con distribuzione geografica


Configurare la cache dei token PostgreSQL

Usare il codice seguente per aggiungere una cache PostgreSQL distribuita:

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.Postgres (NuGet)
    services.AddDistributedPostgresCache(options =>
    {
        options.ConnectionString = ConfigurationManager.ConnectionStrings["PostgresCache"].ConnectionString;
        options.SchemaName = ConfigurationManager.AppSettings["PostgresCache:SchemaName"];
        options.TableName = ConfigurationManager.AppSettings["PostgresCache:TableName"];
        options.CreateIfNotExists = bool.Parse(
            ConfigurationManager.AppSettings["PostgresCache:CreateIfNotExists"] ?? "true");

        // Set expiration above token lifetime.
        // Access tokens typically expire after 1 hour.
        options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
    });
});

Caratteristiche:

  • Persistente attraverso i riavvii
  • Condiviso tra più istanze
  • Semantica SQL familiare
  • Funziona con Database di Azure per PostgreSQL
  • Richiede un server PostgreSQL

Caso d'uso: Applicazioni che già utilizzano PostgreSQL come database primario, o servizi ospitati su Azure che utilizzano Azure Database per PostgreSQL


Costruire un'applicazione daemon completa

L'esempio seguente mostra un'applicazione daemon completa che acquisisce token usando le credenziali client e una cache dei token SQL Server.

using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders;
using System;
using System.Threading.Tasks;

namespace DaemonApp
{
    class Program
    {
        private static IConfidentialClientApplication _app;

        static async Task Main(string[] args)
        {
            // Build confidential client with token cache
            _app = BuildConfidentialClient();

            // Acquire token for app-only access
            string[] scopes = new[] { "https://graph.microsoft.com/.default" };

            try
            {
                var result = await _app.AcquireTokenForClient(scopes)
                    .ExecuteAsync();

                Console.WriteLine($"Token acquired successfully!");
                Console.WriteLine($"Token source: {result.AuthenticationResultMetadata.TokenSource}");
                Console.WriteLine($"Expires on: {result.ExpiresOn}");

                // Use token to call API
                await CallProtectedApi(result.AccessToken);
            }
            catch (MsalServiceException ex)
            {
                Console.WriteLine($"Error acquiring token: {ex.ErrorCode}");
                Console.WriteLine($"CorrelationId: {ex.CorrelationId}");
            }
        }

        private static IConfidentialClientApplication BuildConfidentialClient()
        {
            var app = ConfidentialClientApplicationBuilder
                .Create(ConfigurationManager.AppSettings["ClientId"])
                .WithClientSecret(ConfigurationManager.AppSettings["ClientSecret"])
                .WithTenantId(ConfigurationManager.AppSettings["TenantId"])
                .Build();

            // Add SQL Server token cache for persistence
            app.AddDistributedTokenCaches(services =>
            {
                services.AddDistributedSqlServerCache(options =>
                {
                    options.ConnectionString = ConfigurationManager
                        .ConnectionStrings["TokenCache"].ConnectionString;
                    options.SchemaName = "dbo";
                    options.TableName = "TokenCache";
                    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
                });
            });

            return app;
        }

        private static async Task CallProtectedApi(string accessToken)
        {
            // Your API call logic
        }
    }
}

Gestire i certificati

Informazioni sul caricamento dei certificati

Microsoft. Identity.Web semplifica il caricamento dei certificati da varie origini per i flussi di credenziali client.

Caricare i certificati con DefaultCertificateLoader

L'esempio seguente illustra come caricare un certificato da Azure Key Vault e creare un'applicazione client riservata.

using Microsoft.Identity.Web;
using Microsoft.Identity.Client;

public class CertificateHelper
{
    public static IConfidentialClientApplication CreateAppWithCertificate()
    {
        string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];
        string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];

        // Define certificate source
        var certDescription = CertificateDescription.FromKeyVault(
            keyVaultUrl: "https://my-keyvault.vault.azure.net",
            keyVaultCertificateName: "MyCertificate"
        );

        // Load certificate
        ICertificateLoader certificateLoader = new DefaultCertificateLoader();
        certificateLoader.LoadIfNeeded(certDescription);

        // Create confidential client with certificate
        var app = ConfidentialClientApplicationBuilder.Create(clientId)
            .WithCertificate(certDescription.Certificate)
            .WithTenantId(tenantId)
            .Build();

        // Add token cache
        app.AddInMemoryTokenCache();

        return app;
    }
}

Scegliere le origini dei certificati

Caricare da Azure Key Vault

Caricare un certificato archiviato in Azure Key Vault specificando l'URL dell'insieme di credenziali e il nome del certificato.

var certDescription = CertificateDescription.FromKeyVault(
    keyVaultUrl: "https://my-keyvault.vault.azure.net",
    keyVaultCertificateName: "MyApplicationCert"
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithCertificate(certDescription.Certificate)
    .WithTenantId(tenantId)
    .Build();

Prerequisiti:

  • Identità gestita o Principale del Servizio con accesso a Key Vault
  • pacchetto NuGet Azure.Identity
  • autorizzazione Key Vault: Get sui certificati

Caricare dall'archivio certificati

Carica un certificato dall'archivio dei certificati di Windows utilizzando il nome distintivo.

var certDescription = CertificateDescription.FromStoreWithDistinguishedName(
    distinguishedName: "CN=MyApp.contoso.com",
    storeName: StoreName.My,
    storeLocation: StoreLocation.CurrentUser
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithCertificate(certDescription.Certificate)
    .WithTenantId(tenantId)
    .Build();

È anche possibile trovare un certificato tramite impronta digitale:

var certDescription = CertificateDescription.FromStoreWithThumbprint(
    thumbprint: "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
    storeName: StoreName.My,
    storeLocation: StoreLocation.LocalMachine
);

Caricare dal file system

Caricare un certificato da un file PFX nel file system locale.

var certDescription = CertificateDescription.FromPath(
    path: @"C:\Certificates\MyAppCert.pfx",
    password: ConfigurationManager.AppSettings["Certificate:Password"]
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithCertificate(certDescription.Certificate)
    .WithTenantId(tenantId)
    .Build();

Nota sulla sicurezza: Non memorizzare mai password nel codice. Usare la configurazione sicura.


Caricare dalla stringa con codifica Base64

Caricare un certificato da una stringa con codifica Base64 archiviata nella configurazione.

string base64Cert = ConfigurationManager.AppSettings["Certificate:Base64"];

var certDescription = CertificateDescription.FromBase64Encoded(
    base64EncodedValue: base64Cert,
    password: ConfigurationManager.AppSettings["Certificate:Password"]  // Optional
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

Configurare il caricamento dei certificati da App.config

Definire le impostazioni del certificato nel file App.config e caricarle in fase di esecuzione.

App.config:

<appSettings>
  <add key="AzureAd:ClientId" value="your-client-id" />
  <add key="AzureAd:TenantId" value="your-tenant-id" />

  <!-- Option 1: KeyVault -->
  <add key="Certificate:SourceType" value="KeyVault" />
  <add key="Certificate:KeyVaultUrl" value="https://my-vault.vault.azure.net" />
  <add key="Certificate:KeyVaultCertificateName" value="MyCert" />

  <!-- Option 2: Store -->
  <!--
  <add key="Certificate:SourceType" value="StoreWithThumbprint" />
  <add key="Certificate:CertificateThumbprint" value="ABCD..." />
  <add key="Certificate:CertificateStorePath" value="CurrentUser/My" />
  -->
</appSettings>

<connectionStrings>
  <add name="TokenCache"
       connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TokenCache;Integrated Security=True;" />
</connectionStrings>

Usare il metodo helper seguente per caricare il certificato in base alla configurazione:

public static CertificateDescription GetCertificateFromConfig()
{
    string sourceType = ConfigurationManager.AppSettings["Certificate:SourceType"];

    return sourceType switch
    {
        "KeyVault" => CertificateDescription.FromKeyVault(
            ConfigurationManager.AppSettings["Certificate:KeyVaultUrl"],
            ConfigurationManager.AppSettings["Certificate:KeyVaultCertificateName"]
        ),

        "StoreWithThumbprint" => CertificateDescription.FromStoreWithThumbprint(
            ConfigurationManager.AppSettings["Certificate:CertificateThumbprint"],
            StoreName.My,
            StoreLocation.CurrentUser
        ),

        _ => throw new ConfigurationErrorsException("Invalid certificate source type")
    };
}

Esplorare le applicazioni di esempio

Esaminare questi esempi per visualizzare le implementazioni funzionanti.

Esaminare gli esempi ufficiali di Microsoft

Nella tabella seguente sono elencati esempi ufficiali che illustrano la memorizzazione nella cache dei token e il caricamento del certificato.

Esempio Piattaforma Descrizione
ConfidentialClientTokenCache Console (Framework .NET) Modelli di serializzazione della cache dei token
active-directory-dotnetcore-daemon-v2 Console (.NET Core) Caricamento di certificati da Key Vault

Seguire le migliori pratiche

Applicare questi modelli per creare applicazioni affidabili e sicure.

1. Usare il modello singleton per IConfidentialClientApplication:

Creare una singola istanza e riutilizzarla nell'applicazione.

private static IConfidentialClientApplication _app;

public static IConfidentialClientApplication GetApp()
{
    if (_app == null)
    {
        _app = ConfidentialClientApplicationBuilder.Create(clientId)
            .WithClientSecret(clientSecret)
            .WithTenantId(tenantId)
            .Build();

        _app.AddDistributedTokenCaches(/* ... */);
    }

    return _app;
}

2. Impostare la scadenza appropriata della cache dei token:

Configurare la scadenza progressiva al di sopra della durata del token per impedire la riacquisizione non necessaria.

// Access tokens typically expire after 1 hour
// Set cache expiration ABOVE token lifetime
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);

3. Usare l'archiviazione sicura dei certificati:

Archiviare i certificati in Azure Key Vault o in un archivio certificati protetto correttamente.

// Azure Key Vault (production)
var cert = CertificateDescription.FromKeyVault(keyVaultUrl, certName);

// Certificate store with proper permissions
var cert = CertificateDescription.FromStoreWithThumbprint(
    thumbprint, StoreName.My, StoreLocation.LocalMachine);

4. Implementare una corretta gestione degli errori:

Rilevare le eccezioni MSAL e registrare l'ID di correlazione per la risoluzione dei problemi.

try
{
    var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
}
catch (MsalServiceException ex)
{
    logger.Error($"Token acquisition failed. CorrelationId: {ex.CorrelationId}, ErrorCode: {ex.ErrorCode}");
    throw;
}

5. Utilizzare una cache distribuita per la produzione:

Una cache distribuita condivide i token tra istanze e persiste tra i riavvii.

// Correct for daemon services
app.AddDistributedTokenCaches(services =>
{
    services.AddDistributedSqlServerCache(/* ... */);
});

Evitare errori comuni

1. Non creare ripetutamente nuove istanze IConfidentialClientApplication:

// Wrong - creates new instance every time
public void AcquireToken()
{
    var app = ConfidentialClientApplicationBuilder.Create(clientId).Build();
    // ...
}

// Correct - use singleton
private static readonly IConfidentialClientApplication _app = BuildApp();

2. Non inserire dati sensibili nel codice:

// Wrong
.WithClientSecret("supersecretvalue123")

// Correct
.WithClientSecret(ConfigurationManager.AppSettings["AzureAd:ClientSecret"])

3. Non usare la cache in memoria per i servizi a istanze multipla:

// Wrong for services with multiple instances
app.AddInMemoryTokenCache();

// Correct - use distributed cache
app.AddDistributedTokenCaches(services =>
{
    services.AddDistributedSqlServerCache(/* ... */);
});

4. Non ignorare la convalida del certificato:

// Wrong - skips validation
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => true;

// Correct - validate certificates properly

Eseguire la migrazione da ADAL.NET

Esaminare le differenze principali e aggiornare il codice per usare MSAL.NET con Microsoft. Identity.Web.

Comprendere le differenze principali

Aspetto ADAL.NET (deprecato) MSAL.NET + Microsoft. Identity.Web
Ambiti Basato sulle risorse (https://graph.microsoft.com) Basato sull'ambito (https://graph.microsoft.com/.default)
Token Cache Serializzazione manuale richiesta Adattatori integrati tramite metodi di estensione
Attestati Caricamento manuale di X509Certificate2 DefaultCertificateLoader con più origini
Authority Fissato durante la costruzione Può essere sovrascritto per richiesta

Confrontare esempi di migrazione

ADAL.NET (Old):

AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);

MSAL.NET con Microsoft. Identity.Web (New):

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithClientSecret(clientSecret)
    .WithTenantId(tenantId)
    .Build();

app.AddInMemoryTokenCache();  // Add token cache

string[] scopes = new[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync();

Usare queste risorse per altre informazioni sugli scenari correlati.