Integre o MSAL.NET com a Microsoft. Identity.Web na estrutura .NET

Este guia mostra-lhe como usar os pacotes de cache de token e certificados Microsoft.Identity.Web com MSAL.NET no .NET Framework, .NET Standard 2.0 e aplicações clássicas .NET (.NET 4.7.2+).

Compreenda a visão geral

Começando com Microsoft.Identity.Web 1.17+, pode utilizar os pacotes de utilitários Microsoft.Identity.Web com MSAL.NET em ambientes que não são ASP.NET Core.

Identificar os benefícios do pacote

Feature Benefit
Serialização da Cache de Token Adaptadores de cache reutilizáveis para in-memory, SQL Server, Redis, Cosmos DB, PostgreSQL
Ajudantes de Certificados Carregamento simplificado de certificados a partir do KeyVault, sistema de ficheiros ou lojas de certificados
Prorrogações de Reivindicações Métodos utilitários para manipulação de ClaimsPrincipal
.NET Padrão 2.0 Compatível com .NET Framework 4.7.2+, .NET Core e .NET 5+
Dependências Mínimas Pacotes direcionados sem dependências do ASP.NET Core

Analisar cenários suportados

Os seguintes cenários são suportados pelos pacotes utilitários específicos.

  • .NET Framework Console Applications (cenários de processos em segundo plano)
  • Aplicações de Ambiente de Trabalho (.NET Framework)
  • Serviços ao Trabalhador (Quadro .NET)
  • .NET Bibliotecas Standard 2.0 (compatibilidade multiplataforma)
  • Aplicações não-web MSAL.NET

Observação

Para aplicações ASP.NET MVC/Web API, veja OWIN Integration em vez disso.


Pacotes selecionados

Escolhe o pacote que se adequa ao teu cenário.

Identificar pacotes principais para MSAL.NET

Package Purpose Dependências .NET Target
Microsoft. Identity.Web.TokenCache Serializadores de cache de tokens e ClaimsPrincipal extensões Mínimo .NET Padrão 2.0
Microsoft. Identity.Web.Certificate Utilitários de carregamento de certificados Mínimo .NET Padrão 2.0

Instalar pacotes

Use um dos seguintes métodos para adicionar os pacotes ao seu projeto.

Consola do Gestor de Pacotes:

# 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

Compreender as limitações do core package

O pacote core Microsoft.Identity.Web inclui dependências do ASP.NET Core (Microsoft.AspNetCore.*), que:

  • São incompatíveis com o ASP.NET Framework
  • Aumentar o tamanho da encomenda desnecessariamente
  • Criar conflitos de dependência

Utilize pacotes direcionados para cenários do .NET Framework e .NET Standard.


Configurar serialização da cache de token

Compreender adaptadores de cache de token

Microsoft. O Identity.Web fornece adaptadores de cache de token que funcionam perfeitamente com o IConfidentialClientApplication da MSAL.NET.

Construir um cliente confidencial com cache de tokens

O exemplo seguinte cria uma aplicação cliente confidencial e anexa uma cache de token em memória.

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

Escolher opções de cache de tokens

Selecione o fornecedor de cache que melhor se adapte ao seu cenário de implantação.

Configurar cache de tokens em memória

O exemplo seguinte adiciona uma cache simples em memória:

using Microsoft.Identity.Web.TokenCacheProviders;

_app.AddInMemoryTokenCache();

Cache em memória com limites de tamanho (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
    });
});

Caraterísticas:

  • Acesso rápido
  • Sem dependências externas
  • Não partilhado entre processos
  • Perdi no reinício da aplicação

Caso de uso: Aplicações de consola de instância única, aplicações de ambiente de trabalho


Configurar cache de tokens distribuídos em memória

Use o seguinte código para adicionar uma cache distribuída em memória para ambientes de múltiplas instâncias:

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

Caraterísticas:

  • Partilhado entre instâncias de aplicação
  • Melhor para cenários de balanceamento de carga
  • Requer pacote NuGet adicional
  • Ainda estou perdido no reinício da aplicação

Caso de uso: Serviços multi-instância com reaquisição aceitável de tokens


Configurar Cache de Tokens do SQL Server

Use o seguinte código para adicionar uma cache SQL Server distribuída e persistente:

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

Execute o seguinte SQL para criar a tabela de cache necessária:

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

Caraterísticas:

  • Persistente em reinicializações
  • Partilhado entre múltiplas instâncias
  • Fiável e escalável
  • Requer configuração do SQL Server

Caso de uso: Serviços de demónios de produção, tarefas agendadas, trabalhadores multi-instância


Configurar cache de tokens Redis

Use o seguinte código para adicionar uma cache distribuída Redis de alto desempenho:

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

O exemplo seguinte mostra uma configuração Redis pronta para produção:

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

Caraterísticas:

  • Extremamente rápido
  • Partilhado entre instâncias
  • Persistente (com persistência Redis ativada)
  • Requer servidor Redis

Caso de uso: Aplicações de daemons de alto volume, sistemas distribuídos, microserviços


Configurar cache de tokens da Cosmos DB

Use o seguinte código para adicionar uma cache Cosmos DB distribuída globalmente:

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

Caraterísticas:

  • Distribuído globalmente
  • Altamente disponível
  • Dimensionamento automático
  • Latência superior à Redis
  • Custo mais elevado

Caso de uso: Serviços globais de demónios, aplicações geo-distribuídas


Configurar cache de tokens PostgreSQL

Use o seguinte código para adicionar uma cache PostgreSQL distribuída:

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

Caraterísticas:

  • Persistente em reinicializações
  • Partilhado entre múltiplas instâncias
  • Semântica SQL familiar
  • Trabalha com o Base de Dados do Azure para PostgreSQL
  • Requer um servidor PostgreSQL

Caso de Uso: Aplicações que já utilizam PostgreSQL como base de dados principal, ou serviços Azure alojados que utilizam Base de Dados do Azure para PostgreSQL


Construir uma aplicação completa de daemons

O exemplo seguinte mostra uma aplicação daemon completa que adquire tokens usando credenciais do cliente e uma cache de tokens do 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
        }
    }
}

Gerir certificados

Compreender carregamento de certificados

Microsoft. O Identity.Web simplifica o carregamento de certificados a partir de várias fontes para os fluxos de credenciais dos clientes.

Carregar certificados com o DefaultCertificateLoader

O exemplo seguinte demonstra como carregar um certificado do Azure Key Vault e criar uma aplicação cliente confidencial.

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

Escolha fontes de certificados

Carregar a partir do Azure Key Vault

Carregue um certificado armazenado no Azure Key Vault especificando a URL do cofre e o nome do certificado.

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

Pré-requisitos:

  • Identidade gerida ou Principal de Serviço com acesso ao Key Vault
  • Pacote NuGet Azure.Identity
  • Permissão de Key Vault: Get nos certificados

Carregar a partir do armazenamento de certificados

Carregue um certificado a partir da loja de certificados do Windows por nome distinto.

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

Também pode encontrar um certificado por impressão digital:

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

Carregar a partir do sistema de ficheiros

Carregar um certificado a partir de um ficheiro PFX no sistema de ficheiros local.

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 de segurança: Nunca codifiquem palavras-passe de forma fixa. Use uma configuração segura.


Carga a partir da cadeia codificada em Base64

Carregue um certificado a partir de uma cadeia codificada em Base64 armazenada em configuração.

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

Configurar o carregamento de certificados a partir de App.config

Defina as definições de certificados no seu ficheiro App.config e carregue-as em tempo de execução.

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>

Use o seguinte método auxiliar para carregar o certificado com base na configuração:

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

Explore aplicações de exemplo

Reveja estes exemplos para ver implementações a funcionar.

Rever exemplos oficiais da Microsoft

A tabela seguinte lista exemplos oficiais que demonstram cache de tokens e carregamento de certificados.

Exemplo Plataforma Descrição
ConfidentialClientTokenCache Consola (.NET Framework) Padrões de serialização da cache de tokens
active-directory-dotnetcore-daemon-v2 Consola (.NET Core) Carregamento de certificados a partir do Key Vault

Siga as melhores práticas

Aplique estes padrões para construir aplicações fiáveis e seguras.

1. Usar o padrão singleton para IConfidentialClientApplication:

Crie uma única instância e reutilize-a em toda a sua aplicação.

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. Definir a expiração adequada do cache de tokens:

Configure a expiração deslizante acima da vida útil do token para evitar reaquisições desnecessárias.

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

3. Utilizar armazenamento seguro de certificados:

Armazene certificados no Azure Key Vault ou numa loja de certificados devidamente segura.

// 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. Implementar o tratamento adequado dos erros:

Capture exceções MSAL e registe o ID de correlação para resolução de problemas.

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

5. Utilizar uma cache distribuída para produção:

A cache distribuída partilha tokens entre instâncias e persiste ao longo de reinícios.

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

Evite erros comuns

1. Não crie novas instâncias IConfidentialClientApplication repetidamente:

// 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. Não programes segredos de forma rígida:

// Wrong
.WithClientSecret("supersecretvalue123")

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

3. Não use cache em memória para serviços multi-instância:

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

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

4. Não ignore a validação de certificados:

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

// Correct - validate certificates properly

Migrar a partir do ADAL.NET

Revê as principais diferenças e atualiza o teu código para usar MSAL.NET com a Microsoft. Identidade.Web.

Compreender as principais diferenças

Aspeto ADAL.NET (descontinuado) MSAL.NET + Microsoft. Identity.Web
Escopos Baseado em recursos (https://graph.microsoft.com) Baseado em âmbito (https://graph.microsoft.com/.default)
Cache de Tokens Serialização manual necessária Adaptadores incorporados através de métodos de extensão
Certificates Carregamento manual de X509Certificate2 DefaultCertificateLoader com múltiplas fontes
Autoridade Fixo na construção Pode ser anulado por pedido

Compare exemplos de migração

ADAL.NET (Antigo):

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

MSAL.NET com Microsoft. Identity.Web (Novo):

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

Utilize estes recursos para aprender mais sobre cenários relacionados.