Integre MSAL.NET com Microsoft. Identity.Web no .NET Framework

Este guia mostra como usar os pacotes de cache de token e de certificado do Microsoft.Identity.Web com MSAL.NET no .NET Framework, .NET Standard 2.0 e aplicativos .NET clássicos (.NET 4.7.2+).

Entender a visão geral

Começando com Microsoft.Identity.Web 1.17+, você pode usar os pacotes de utilitários do Microsoft.Identity.Web com o MSAL.NET em ambientes não ASP.NET Core.

Identificar benefícios do pacote

Característica Benefício
Serialização do cache de token Adaptadores de cache reutilizáveis para memória, SQL Server, Redis, Cosmos DB, PostgreSQL
Auxiliares de Certificado Carregamento de certificado simplificado do KeyVault, sistema de arquivos ou repositórios de certificados
Extensões de Claims Métodos utilitários para manipulação de ClaimsPrincipal
.NET Standard 2.0 Compatível com .NET Framework 4.7.2+, .NET Core e .NET 5+
Dependências mínimas Pacotes de destino sem dependências de ASP.NET Core

Revisar cenários suportados

Os seguintes cenários são suportados pelos pacotes utilitários direcionados.

  • .NET Framework Console Applications (cenários daemon)
  • Desktop Applications (.NET Framework)
  • Worker Services (.NET Framework)
  • bibliotecas .NET Standard 2.0 (compatibilidade entre plataformas)
  • Aplicativos MSAL.NET não web

Observação

Para ASP.NET MVC/aplicativos de API Web, consulte OWIN Integration em vez disso.


Selecionar pacotes

Escolha o pacote que corresponde ao seu cenário.

Identificar pacotes principais para MSAL.NET

Package Propósito Dependências Destino do .NET
Microsoft. Identity.Web.TokenCache Serializadores de cache de token, ClaimsPrincipal extensões Mínimo .NET Standard 2.0
Microsoft. Identity.Web.Certificate Utilitários de carregamento de certificado Mínimo .NET Standard 2.0

Instalar pacotes

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

Gerenciador de Pacotes 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

Entender as limitações do pacote principal

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

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

Use pacotes direcionados para cenários .NET Framework e .NET Standard.


Configurar a serialização de cache de token

Entender os adaptadores de cache de token

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

Criar um cliente confidencial com cache de token

O exemplo a seguir cria um aplicativo cliente confidencial e anexa um cache de token na 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 token

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

Configurar o cache de token na memória

O exemplo a seguir adiciona um cache simples na memória:

using Microsoft.Identity.Web.TokenCacheProviders;

_app.AddInMemoryTokenCache();

In-memory cache 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
    });
});

Características:

  • Acesso rápido
  • Nenhuma dependência externa
  • Não compartilhado entre processos
  • Perda na reinicialização do aplicativo

Caso de uso: Aplicativos de console com instância única, aplicativos da área de trabalho


Configurar o cache de token distribuído na memória

Use o seguinte código para adicionar um cache distribuído na memória para ambientes de várias instâncias:

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

Características:

  • Compartilhado entre instâncias de aplicativo
  • Melhor para cenários com balanceamento de carga
  • Requer um pacote NuGet adicional
  • Ainda perdido na reinicialização do aplicativo

Caso de uso: Serviços de várias instâncias com reobtenção de token aceitável


Configurar o cache de token do SQL Server

Use o seguinte código para adicionar um cache de SQL Server persistente e distribuído:

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

Características:

  • Persistente mesmo após reinicializações
  • Compartilhado em várias instâncias
  • Confiável e escalonável
  • Requer configurar o SQL Server

Caso de uso: Serviços de processos em segundo plano de produção, tarefas agendadas, processos de múltiplas instâncias


Configurar o cache de token Redis

Use o seguinte código para adicionar um cache distribuído 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 a seguir mostra uma configuração do 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
    };
});

Características:

  • Extremamente rápido
  • Compartilhado entre instâncias
  • Persistente (com a persistência do Redis habilitada)
  • Requer o servidor Redis

Caso de uso: Aplicativos daemon de alto volume, sistemas distribuídos, microsserviços


Configurar o cache de token do Cosmos DB

Use o seguinte código para adicionar um cache do Cosmos DB distribuído 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;
    });
});

Características:

  • Distribuído globalmente
  • Altamente disponível
  • Dimensionamento automático
  • Latência maior que Redis
  • Custo mais alto

Caso de uso: Serviços daemon globais, aplicativos distribuídos geograficamente


Configurar o cache de token PostgreSQL

Use o seguinte código para adicionar um cache PostgreSQL distribuído:

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

Características:

  • Persistente mesmo após reinicializações
  • Compartilhado em várias instâncias
  • Semântica de SQL familiar
  • Funciona com Banco de Dados do Azure para PostgreSQL
  • Requer um servidor PostgreSQL

Use case: Aplicações já usando o PostgreSQL como banco de dados primário ou serviços hospedados na Azure usando o Banco de Dados do Azure para PostgreSQL


Criar um aplicativo daemon completo

O exemplo a seguir mostra um aplicativo daemon completo que adquire tokens usando credenciais de cliente e um cache de 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
        }
    }
}

Gerenciar certificados

Entender o carregamento de certificado

Microsoft. O Identity.Web simplifica o carregamento de certificados de várias fontes para fluxos de credenciais do cliente.

Carregar certificados com DefaultCertificateLoader

O exemplo a seguir demonstra como carregar um certificado de Azure Key Vault e criar um aplicativo 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;
    }
}

Escolher fontes de certificado

Carregar de Azure Key Vault

Carregue um certificado armazenado em 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 Gerenciada ou Entidade de Serviço com acesso ao Key Vault
  • Azure.Identity pacote NuGet
  • permissão Key Vault: Get em certificados

Carregar do repositório de certificados

Carregue um certificado do repositório de certificados Windows pelo nome diferenciado.

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

Você também pode encontrar um certificado por impressão digital:

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

Carregar do sistema de arquivos

Carregue um certificado de um arquivo PFX no sistema de arquivos 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();

Observação de segurança: Nunca codifique senhas diretamente no código. Use a configuração segura.


Carregar da string codificada em Base64

Carregue um certificado de uma cadeia de caracteres codificada em Base64 armazenada na 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 a carga de certificados a partir do App.config

Defina as configurações de certificado no arquivo App.config e carregue-as em runtime.

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

Explorar aplicativos de exemplo

Examine esses exemplos para ver implementações de trabalho.

Examinar exemplos oficiais de Microsoft

A tabela a seguir lista exemplos oficiais que demonstram o cache de token e o carregamento de certificados.

Amostra Plataforma Descrição
ConfidentialClientTokenCache Console (.NET Framework) Padrões de serialização de cache de token
active-directory-dotnetcore-daemon-v2 Console (.NET Core) Carregamento de certificado de Key Vault

Seguir as práticas recomendadas

Aplique esses padrões para criar aplicativos confiáveis e seguros.

1. Use o padrão singleton para IConfidentialClientApplication:

Crie uma única instância e reutilize-a em seu aplicativo.

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 do cache de token apropriada:

Configure a expiração deslizante acima do tempo de vida do token para evitar a nova aquisição desnecessária.

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

3. Use o armazenamento seguro de certificados:

Armazene certificados em Azure Key Vault ou em um repositório de certificados protegido corretamente.

// 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 de erros adequado:

Capturar exceções MSAL e registrar o ID de correlação para soluçã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. Use um cache distribuído para produção:

Um cache distribuído compartilha tokens entre instâncias e persiste entre reinicializações.

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

Evitar 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 codificar segredos:

// Wrong
.WithClientSecret("supersecretvalue123")

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

3. Não use cache na memória para serviços de várias instâncias:

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

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

4. Não ignore a validação do certificado:

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

// Correct - validate certificates properly

Migrar da ADAL.NET

Examine as principais diferenças e atualize seu código para usar MSAL.NET com Microsoft. Identity.Web.

Entender as principais diferenças

Aspecto ADAL.NET (preterida) MSAL.NET + Microsoft. Identity.Web
Escopos Baseado em recursos (https://graph.microsoft.com) Baseado em escopo (https://graph.microsoft.com/.default)
Token Cache Serialização manual necessária Adaptadores integrados via métodos de extensão
Certificados Carregamento manual do X509Certificate2 DefaultCertificateLoader com várias fontes
Autoridade Corrigido na construção Pode ser substituído por solicitação

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

Use esses recursos para saber mais sobre cenários relacionados.