Tutorial: Autenticar usuários em seu aplicativo de desktop WPF
Este tutorial é a parte final de uma série que demonstra a criação de um aplicativo de área de trabalho WPF (Windows Presentation Form) e prepará-lo para autenticação usando o centro de administração do Microsoft Entra. Na Parte 1 desta série, você registrou um aplicativo e configurou fluxos de usuário em seu locatário externo. Este tutorial demonstra como criar seu aplicativo de área de trabalho .NET WPF e entrar e sair de um usuário usando o Microsoft Entra External ID.
Neste tutorial, você:
- Configure um aplicativo de desktop WPF para usar seus detalhes de registro do aplicativo.
- Crie um aplicativo de área de trabalho que conecte um usuário e adquira um token em nome do usuário.
Pré-requisitos
- Tutorial: Prepare seu locatário externo para entrar no usuário no aplicativo .NET WPF.
- SDK do .NET 7.0 ou posterior.
- Embora qualquer ambiente de desenvolvimento integrado (IDE) que ofereça suporte a aplicativos React possa ser usado, este tutorial usa o Visual Studio Code.
Criar um aplicativo de desktop WPF
Abra o terminal e navegue até a pasta onde você deseja que seu projeto viva.
Inicialize um aplicativo de desktop WPF e navegue até sua pasta raiz.
dotnet new wpf --language "C#" --name sign-in-dotnet-wpf cd sign-in-dotnet-wpf
Instalar pacotes
Instale provedores de configuração que ajudam nosso aplicativo a ler dados de configuração de pares chave-valor em nosso arquivo de configurações do aplicativo. Essas abstrações de configuração fornecem a capacidade de vincular valores de configuração a instâncias de objetos .NET.
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder
Instale a Biblioteca de Autenticação da Microsoft (MSAL) que contém todos os principais componentes necessários para adquirir um token. Você também instala a biblioteca do agente MSAL que lida com interações com agentes de autenticação da área de trabalho.
dotnet add package Microsoft.Identity.Client
dotnet add package Microsoft.Identity.Client.Broker
Criar appsettings.json arquivo e adicionar configurações de registro
Crie appsettings.json arquivo na pasta raiz do aplicativo.
Adicione detalhes de registro do aplicativo ao arquivo appsettings.json .
{ "AzureAd": { "Authority": "https://<Enter_the_Tenant_Subdomain_Here>.ciamlogin.com/", "ClientId": "<Enter_the_Application_Id_Here>" } }
- Substitua
Enter_the_Tenant_Subdomain_Here
pelo subdomínio Directory (locatário). - Substitua
Enter_the_Application_Id_Here
pelo ID do aplicativo (cliente) do aplicativo que você registrou anteriormente.
- Substitua
Depois de criar o arquivo de configurações do aplicativo, criaremos outro arquivo chamado AzureAdConfig.cs que ajudará você a ler as configurações do arquivo de configurações do aplicativo. Crie o arquivo AzureAdConfig.cs na pasta raiz do aplicativo.
No arquivo AzureAdConfig.js, defina os getters e setters para as
ClientId
propriedades eAuthority
. Adicione o seguinte código:namespace sign_in_dotnet_wpf { public class AzureAdConfig { public string Authority { get; set; } public string ClientId { get; set; } } }
Usar domínio de URL personalizado (opcional)
Use um domínio personalizado para marcar totalmente a URL de autenticação. Do ponto de vista do usuário, os usuários permanecem no seu domínio durante o processo de autenticação, em vez de serem redirecionados para ciamlogin.com nome de domínio.
Siga estas etapas para usar um domínio personalizado:
Use as etapas em Habilitar domínios de URL personalizados para aplicativos em locatários externos para habilitar o domínio de URL personalizado para seu locatário externo.
Abra appsettings.json arquivo:
- Atualize o
Authority
valor da propriedade para https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. SubstituaEnter_the_Custom_Domain_Here
pelo seu domínio de URL personalizado eEnter_the_Tenant_ID_Here
pelo seu ID de inquilino. Se não tiver o ID do inquilino, saiba como ler os detalhes do inquilino. - Adicionar
knownAuthorities
propriedade com um valor [Enter_the_Custom_Domain_Here].
- Atualize o
Depois de fazer as alterações no arquivo appsettings.json, se o domínio de URL personalizado estiver login.contoso.com e o ID do locatário for aaaabbbb-0000-cccc-1111-dddd2222eeee, o arquivo deverá ser semelhante ao seguinte trecho:
{
"AzureAd": {
"Authority": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
"ClientId": "Enter_the_Application_Id_Here",
"KnownAuthorities": ["login.contoso.com"]
}
}
Modificar o arquivo de projeto
Navegue até o arquivo sign-in-dotnet-wpf.csproj na pasta raiz do aplicativo.
Nesse arquivo, execute as duas etapas a seguir:
- Modifique o arquivo sign-in-dotnet-wpf.csproj para instruir seu aplicativo a copiar o arquivo appsettings.json para o diretório de saída quando o projeto for compilado. Adicione a seguinte parte do código ao arquivo sign-in-dotnet-wpf.csproj :
- Defina a estrutura de destino para a compilação windows10.0.19041.0 de destino para ajudar com a leitura do token armazenado em cache do cache de token, como você verá na classe auxiliar de cache de token.
<Project Sdk="Microsoft.NET.Sdk"> ... <!-- Set target framework to target windows10.0.19041.0 build --> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net7.0-windows10.0.19041.0</TargetFramework> <!-- target framework --> <RootNamespace>sign_in_dotnet_wpf</RootNamespace> <Nullable>enable</Nullable> <UseWPF>true</UseWPF> </PropertyGroup> <!-- Copy appsettings.json file to output folder. --> <ItemGroup> <None Remove="appsettings.json" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="appsettings.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </EmbeddedResource> </ItemGroup> </Project>
Criar uma classe auxiliar de cache de token
Crie uma classe auxiliar de cache de token que inicializa um cache de token. O aplicativo tenta ler o token do cache antes de tentar adquirir um novo token. Se o token não for encontrado no cache, o aplicativo adquirirá um novo token. Ao sair, o cache é limpo de todas as contas e todos os tokens de acesso correspondentes.
Crie um arquivo TokenCacheHelper.cs na pasta raiz do aplicativo.
Abra o arquivo TokenCacheHelper.cs . Adicione os pacotes e namespaces ao arquivo. Nas etapas a seguir, você preenche esse arquivo com a lógica de código adicionando a lógica relevante à
TokenCacheHelper
classe.using System.IO; using System.Security.Cryptography; using Microsoft.Identity.Client; namespace sign_in_dotnet_wpf { static class TokenCacheHelper{} }
Adicione o
TokenCacheHelper
construtor à classe que define o caminho do arquivo de cache. Para aplicativos de área de trabalho empacotados (pacotes MSIX, também chamados de ponte da área de trabalho), a pasta assembly em execução é somente leitura. Nesse caso, precisamos usarWindows.Storage.ApplicationData.Current.LocalCacheFolder.Path + "\msalcache.bin"
uma pasta de leitura/gravação por aplicativo para aplicativos empacotados.namespace sign_in_dotnet_wpf { static class TokenCacheHelper { static TokenCacheHelper() { try { CacheFilePath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path, ".msalcache.bin3"); } catch (System.InvalidOperationException) { CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3"; } } public static string CacheFilePath { get; private set; } private static readonly object FileLock = new object(); } }
Adicione código para manipular a serialização do cache de token. A
ITokenCache
interface implementa o acesso público às operações de cache.ITokenCache
interface contém os métodos para assinar os eventos de serialização de cache, enquanto a interfaceITokenCacheSerializer
expõe os métodos que você precisa usar nos eventos de serialização de cache, a fim de serializar/desserializar o cache.TokenCacheNotificationArgs
contém parâmetros usados pelaMicrosoft.Identity.Client
chamada (MSAL) que acessa o cache.ITokenCacheSerializer
A interface está disponível em retorno deTokenCacheNotificationArgs
chamada.Adicione o seguinte código à
TokenCacheHelper
classe:static class TokenCacheHelper { static TokenCacheHelper() {...} public static string CacheFilePath { get; private set; } private static readonly object FileLock = new object(); public static void BeforeAccessNotification(TokenCacheNotificationArgs args) { lock (FileLock) { args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.CurrentUser) : null); } } public static void AfterAccessNotification(TokenCacheNotificationArgs args) { if (args.HasStateChanged) { lock (FileLock) { File.WriteAllBytes(CacheFilePath, ProtectedData.Protect(args.TokenCache.SerializeMsalV3(), null, DataProtectionScope.CurrentUser) ); } } } } internal static void EnableSerialization(ITokenCache tokenCache) { tokenCache.SetBeforeAccess(BeforeAccessNotification); tokenCache.SetAfterAccess(AfterAccessNotification); }
BeforeAccessNotification
No método, você lê o cache do sistema de arquivos e, se o cache não estiver vazio, desserializa-o e carrega-o. OAfterAccessNotification
método é chamado depois queMicrosoft.Identity.Client
(MSAL) acessa o cache. Se o cache tiver sido alterado, serialize-o e persista as alterações no cache.O
EnableSerialization
contém oITokenCache.SetBeforeAccess()
eITokenCache.SetAfterAccess()
métodos:ITokenCache.SetBeforeAccess()
Define um delegado a ser notificado antes que qualquer método de biblioteca acesse o cache. Isso dá uma opção ao delegado para desserializar uma entrada de cache para o aplicativo e as contas especificadas noTokenCacheNotificationArgs
.ITokenCache.SetAfterAccess()
Define um delegado a ser notificado depois que qualquer método de biblioteca acessa o cache. Isso dá uma opção ao delegado para serializar uma entrada de cache para o aplicativo e as contas especificadas noTokenCacheNotificationArgs
.
Criar a interface do usuário do aplicativo de desktop WPF
Modifique o arquivo MainWindow.xaml para adicionar os elementos da interface do usuário para o aplicativo. Abra o arquivo MainWindow.xaml na pasta raiz do aplicativo e adicione a seguinte parte do código com a <Grid></Grid>
seção de controle.
<StackPanel Background="Azure">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="SignInButton" Content="Sign-In" HorizontalAlignment="Right" Padding="5" Click="SignInButton_Click" Margin="5" FontFamily="Segoe Ui"/>
<Button x:Name="SignOutButton" Content="Sign-Out" HorizontalAlignment="Right" Padding="5" Click="SignOutButton_Click" Margin="5" Visibility="Collapsed" FontFamily="Segoe Ui"/>
</StackPanel>
<Label Content="Authentication Result" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
<TextBox x:Name="ResultText" TextWrapping="Wrap" MinHeight="120" Margin="5" FontFamily="Segoe Ui"/>
<Label Content="Token Info" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
<TextBox x:Name="TokenInfoText" TextWrapping="Wrap" MinHeight="70" Margin="5" FontFamily="Segoe Ui"/>
</StackPanel>
Esse código adiciona elementos-chave da interface do usuário. Os métodos e objetos que manipulam a funcionalidade dos elementos da interface do usuário são definidos no arquivo de MainWindow.xaml.cs que criamos na próxima etapa.
- Um botão que inicia sessão no utilizador.
SignInButton_Click
método é chamado quando o usuário seleciona esse botão. - Um botão que desconecta o usuário.
SignOutButton_Click
método é chamado quando o usuário seleciona esse botão. - Uma caixa de texto que exibe os detalhes do resultado da autenticação depois que o usuário tenta entrar. As informações exibidas aqui são retornadas
ResultText
pelo objeto. - Uma caixa de texto que exibe os detalhes do token depois que o usuário entra com êxito. As informações exibidas aqui são retornadas
TokenInfoText
pelo objeto.
Adicionar código ao arquivo MainWindow.xaml.cs
O arquivo MainWindow.xaml.cs contém o código que fornece a lógica de tempo de execução para o comportamento dos elementos da interface do usuário no arquivo MainWindow.xaml .
Abra o arquivo MainWindow.xaml.cs na pasta raiz do aplicativo.
Adicione o seguinte código no arquivo para importar os pacotes e definir espaços reservados para os métodos que criamos.
using Microsoft.Identity.Client; using System; using System.Linq; using System.Windows; using System.Windows.Interop; namespace sign_in_dotnet_wpf { public partial class MainWindow : Window { string[] scopes = new string[] { }; public MainWindow() { InitializeComponent(); } private async void SignInButton_Click(object sender, RoutedEventArgs e){...} private async void SignOutButton_Click(object sender, RoutedEventArgs e){...} private void DisplayBasicTokenInfo(AuthenticationResult authResult){...} } }
Adicione o seguinte código ao método
SignInButton_Click
. Esse método é chamado quando o usuário seleciona o botão de entrada .private async void SignInButton_Click(object sender, RoutedEventArgs e) { AuthenticationResult authResult = null; var app = App.PublicClientApp; ResultText.Text = string.Empty; TokenInfoText.Text = string.Empty; IAccount firstAccount; var accounts = await app.GetAccountsAsync(); firstAccount = accounts.FirstOrDefault(); try { authResult = await app.AcquireTokenSilent(scopes, firstAccount) .ExecuteAsync(); } catch (MsalUiRequiredException ex) { try { authResult = await app.AcquireTokenInteractive(scopes) .WithAccount(firstAccount) .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) .WithPrompt(Prompt.SelectAccount) .ExecuteAsync(); } catch (MsalException msalex) { ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}"; } catch (Exception ex) { ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}"; return; } if (authResult != null) { ResultText.Text = "Sign in was successful."; DisplayBasicTokenInfo(authResult); this.SignInButton.Visibility = Visibility.Collapsed; this.SignOutButton.Visibility = Visibility.Visible; } } }
GetAccountsAsync()
Retorna todas as contas disponíveis no cache de token de usuário para o aplicativo. AIAccount
interface representa informações sobre uma única conta.Para adquirir tokens, o aplicativo tenta adquirir o token silenciosamente usando o
AcquireTokenSilent
método para verificar se um token aceitável está no cache. OAcquireTokenSilent
método pode falhar, por exemplo, porque o usuário saiu. Quando o MSAL deteta que o problema pode ser resolvido exigindo uma ação interativa, ele lança umaMsalUiRequiredException
exceção. Essa exceção faz com que o aplicativo adquira um token interativamente.Chamar o
AcquireTokenInteractive
método resulta em uma janela que solicita que os usuários entrem. Normalmente, as aplicações exigem que os utilizadores iniciem sessão interativamente na primeira vez que precisam de se autenticar. Eles também podem precisar entrar quando uma operação silenciosa para adquirir um token. DepoisAcquireTokenInteractive
é executado pela primeira vez,AcquireTokenSilent
torna-se o método usual a ser usado para obter tokensAdicione o seguinte código ao método
SignOutButton_Click
. Esse método é chamado quando o usuário seleciona o botão Sair .private async void SignOutButton_Click(object sender, RoutedEventArgs e) { var accounts = await App.PublicClientApp.GetAccountsAsync(); if (accounts.Any()) { try { await App.PublicClientApp.RemoveAsync(accounts.FirstOrDefault()); this.ResultText.Text = "User has signed-out"; this.TokenInfoText.Text = string.Empty; this.SignInButton.Visibility = Visibility.Visible; this.SignOutButton.Visibility = Visibility.Collapsed; } catch (MsalException ex) { ResultText.Text = $"Error signing-out user: {ex.Message}"; } } }
O
SignOutButton_Click
método limpa o cache de todas as contas e todos os tokens de acesso correspondentes. Da próxima vez que o utilizador tentar iniciar sessão, terá de o fazer de forma interativa.Adicione o seguinte código ao método
DisplayBasicTokenInfo
. Esse método exibe informações básicas sobre o token.private void DisplayBasicTokenInfo(AuthenticationResult authResult) { TokenInfoText.Text = ""; if (authResult != null) { TokenInfoText.Text += $"Username: {authResult.Account.Username}" + Environment.NewLine; TokenInfoText.Text += $"{authResult.Account.HomeAccountId}" + Environment.NewLine; } }
Adicionar código ao arquivo App.xaml.cs
App.xaml é onde você declara recursos que são usados em todo o aplicativo. É o ponto de entrada para o seu aplicativo. App.xaml.cs é o arquivo code-behind para App.xaml. App.xaml.cs também define a janela Iniciar para seu aplicativo.
Abra o arquivo App.xaml.cs na pasta raiz do aplicativo e adicione o seguinte código a ele.
using System.Windows;
using System.Reflection;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
namespace sign_in_dotnet_wpf
{
public partial class App : Application
{
static App()
{
CreateApplication();
}
public static void CreateApplication()
{
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream("sign_in_dotnet_wpf.appsettings.json");
AppConfiguration = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
AzureAdConfig azureADConfig = AppConfiguration.GetSection("AzureAd").Get<AzureAdConfig>();
var builder = PublicClientApplicationBuilder.Create(azureADConfig.ClientId)
.WithAuthority(azureADConfig.Authority)
.WithDefaultRedirectUri();
_clientApp = builder.Build();
TokenCacheHelper.EnableSerialization(_clientApp.UserTokenCache);
}
private static IPublicClientApplication _clientApp;
private static IConfiguration AppConfiguration;
public static IPublicClientApplication PublicClientApp { get { return _clientApp; } }
}
}
Nesta etapa, você carrega o arquivo appsettings.json . O construtor de configurações ajuda você a ler as configurações do aplicativo definidas no arquivo appsettings.json . Você também define o aplicativo WPF como um aplicativo cliente público, pois é um aplicativo de área de trabalho. O TokenCacheHelper.EnableSerialization
método habilita a serialização do cache de token.
Executar a aplicação
Execute seu aplicativo e entre para testar o aplicativo
No seu terminal, navegue até a pasta raiz do seu aplicativo WPF e execute o aplicativo executando o comando
dotnet run
no seu terminal.Depois de iniciar o exemplo, você verá uma janela com um botão de entrada . Selecione o botão Entrar .
Na página de início de sessão, introduza o endereço de e-mail da sua conta. Se você não tiver uma conta, selecione Sem conta? Crie um, que inicia o fluxo de inscrição. Siga esse fluxo para criar uma nova conta e entrar.
Depois de entrar, você verá uma tela exibindo o login bem-sucedido e informações básicas sobre sua conta de usuário armazenadas no token recuperado. As informações básicas são exibidas na seção Informações do token da tela de entrada
Consulte também
- Iniciar sessão de utilizadores numa aplicação de ambiente de trabalho Electron de exemplo utilizando o Microsoft Entra External ID
- Iniciar sessão de utilizadores numa aplicação de ambiente de trabalho .NET MAUI de exemplo utilizando o Microsoft Entra External ID
- Personalize a identidade visual para a sua experiência de início de sessão