Update MsalProvider to use native account broker (#169)
* Updated MsalProvider to use local account broker when possible. * Updated MsalProvider configuration to support toggling org account login. * Re-adding SDK version header logic * Added token cache example to wpf sample app. * Renamed WpfMsalProviderSample to WpfNet5WindowsMsalProviderSample and added WpfNetCoreMsalProviderSample
This commit is contained in:
Родитель
69197dd945
Коммит
33f781a577
|
@ -1,8 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="MSBuild.Sdk.Extras">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
||||
<TargetFrameworks>netstandard2.0;uap10.0;net5.0-windows10.0.17763.0;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>7</SupportedOSPlatformVersion>
|
||||
|
||||
<Title>Windows Community Toolkit .NET Standard Auth Services</Title>
|
||||
<Description>
|
||||
This library provides an authentication provider based on the native Windows dialogues. It is part of the Windows Community Toolkit.
|
||||
|
@ -18,6 +20,10 @@
|
|||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.19.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<PackageReference Include="Microsoft.Identity.Client.Desktop" Version="4.37.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
@ -11,6 +12,16 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Graph;
|
||||
using Microsoft.Identity.Client;
|
||||
|
||||
#if WINDOWS_UWP
|
||||
using Windows.Security.Authentication.Web;
|
||||
#else
|
||||
using System.Diagnostics;
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
using Microsoft.Identity.Client.Desktop;
|
||||
#endif
|
||||
|
||||
namespace CommunityToolkit.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -18,52 +29,73 @@ namespace CommunityToolkit.Authentication
|
|||
/// </summary>
|
||||
public class MsalProvider : BaseProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// A prefix value used to create the redirect URI value for use in AAD.
|
||||
/// </summary>
|
||||
public static readonly string MSAccountBrokerRedirectUriPrefix = "ms-appx-web://microsoft.aad.brokerplugin/";
|
||||
|
||||
private static readonly SemaphoreSlim SemaphoreSlim = new (1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently authenticated user account.
|
||||
/// </summary>
|
||||
public IAccount Account { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string CurrentAccountId => _account?.HomeAccountId?.Identifier;
|
||||
public override string CurrentAccountId => Account?.HomeAccountId?.Identifier;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MSAL.NET Client used to authenticate the user.
|
||||
/// Gets or sets the MSAL.NET Client used to authenticate the user.
|
||||
/// </summary>
|
||||
protected IPublicClientApplication Client { get; private set; }
|
||||
public IPublicClientApplication Client { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of scopes to use for accessing Graph resources.
|
||||
/// </summary>
|
||||
protected string[] Scopes { get; private set; }
|
||||
|
||||
private IAccount _account;
|
||||
protected string[] Scopes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MsalProvider"/> class.
|
||||
/// Initializes a new instance of the <see cref="MsalProvider"/> class using a configuration object.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Registered ClientId.</param>
|
||||
/// <param name="redirectUri">RedirectUri for auth response.</param>
|
||||
/// <param name="client">Registered ClientId in Azure Acitve Directory.</param>
|
||||
/// <param name="scopes">List of Scopes to initially request.</param>
|
||||
/// <param name="autoSignIn">Determines whether the provider attempts to silently log in upon instantionation.</param>
|
||||
public MsalProvider(string clientId, string[] scopes = null, string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient", bool autoSignIn = true)
|
||||
/// <param name="autoSignIn">Determines whether the provider attempts to silently log in upon creation.</param>
|
||||
public MsalProvider(IPublicClientApplication client, string[] scopes = null, bool autoSignIn = true)
|
||||
{
|
||||
var client = PublicClientApplicationBuilder.Create(clientId)
|
||||
.WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount)
|
||||
.WithRedirectUri(redirectUri)
|
||||
.WithClientName(ProviderManager.ClientName)
|
||||
.WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString())
|
||||
.Build();
|
||||
|
||||
Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty };
|
||||
|
||||
Client = client;
|
||||
Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty };
|
||||
|
||||
if (autoSignIn)
|
||||
{
|
||||
_ = TrySilentSignInAsync();
|
||||
TrySilentSignInAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MsalProvider"/> class with default configuration values.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Registered client id in Azure Acitve Directory.</param>
|
||||
/// <param name="redirectUri">RedirectUri for auth response.</param>
|
||||
/// <param name="scopes">List of Scopes to initially request.</param>
|
||||
/// <param name="autoSignIn">Determines whether the provider attempts to silently log in upon creation.</param>
|
||||
/// <param name="listWindowsWorkAndSchoolAccounts">Determines if organizational accounts should be enabled/disabled.</param>
|
||||
/// <param name="tenantId">Registered tenant id in Azure Active Directory.</param>
|
||||
public MsalProvider(string clientId, string[] scopes = null, string redirectUri = null, bool autoSignIn = true, bool listWindowsWorkAndSchoolAccounts = true, string tenantId = null)
|
||||
{
|
||||
Client = CreatePublicClientApplication(clientId, tenantId, redirectUri, listWindowsWorkAndSchoolAccounts);
|
||||
Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty };
|
||||
|
||||
if (autoSignIn)
|
||||
{
|
||||
TrySilentSignInAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task AuthenticateRequestAsync(HttpRequestMessage request)
|
||||
{
|
||||
AddSdkVersion(request);
|
||||
|
||||
string token;
|
||||
|
||||
// Check if any specific scopes are being requested.
|
||||
|
@ -87,7 +119,7 @@ namespace CommunityToolkit.Authentication
|
|||
/// <inheritdoc/>
|
||||
public override async Task<bool> TrySilentSignInAsync()
|
||||
{
|
||||
if (_account != null && State == ProviderState.SignedIn)
|
||||
if (Account != null && State == ProviderState.SignedIn)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -108,7 +140,7 @@ namespace CommunityToolkit.Authentication
|
|||
/// <inheritdoc/>
|
||||
public override async Task SignInAsync()
|
||||
{
|
||||
if (_account != null || State != ProviderState.SignedOut)
|
||||
if (Account != null || State != ProviderState.SignedOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -129,10 +161,10 @@ namespace CommunityToolkit.Authentication
|
|||
/// <inheritdoc />
|
||||
public override async Task SignOutAsync()
|
||||
{
|
||||
if (_account != null)
|
||||
if (Account != null)
|
||||
{
|
||||
await Client.RemoveAsync(_account);
|
||||
_account = null;
|
||||
await Client.RemoveAsync(Account);
|
||||
Account = null;
|
||||
}
|
||||
|
||||
State = ProviderState.SignedOut;
|
||||
|
@ -144,7 +176,48 @@ namespace CommunityToolkit.Authentication
|
|||
return this.GetTokenWithScopesAsync(Scopes, silentOnly);
|
||||
}
|
||||
|
||||
private async Task<string> GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false)
|
||||
/// <summary>
|
||||
/// Create an instance of <see cref="PublicClientApplication"/> using the provided config and some default values.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Registered ClientId.</param>
|
||||
/// <param name="tenantId">An optional tenant id.</param>
|
||||
/// <param name="redirectUri">Redirect uri for auth response.</param>
|
||||
/// <param name="listWindowsWorkAndSchoolAccounts">Determines if organizational accounts should be supported.</param>
|
||||
/// <returns>A new instance of <see cref="PublicClientApplication"/>.</returns>
|
||||
protected IPublicClientApplication CreatePublicClientApplication(string clientId, string tenantId, string redirectUri, bool listWindowsWorkAndSchoolAccounts)
|
||||
{
|
||||
var authority = listWindowsWorkAndSchoolAccounts ? AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount : AadAuthorityAudience.PersonalMicrosoftAccount;
|
||||
|
||||
var clientBuilder = PublicClientApplicationBuilder.Create(clientId)
|
||||
.WithAuthority(AzureCloudInstance.AzurePublic, authority)
|
||||
.WithClientName(ProviderManager.ClientName)
|
||||
.WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
|
||||
if (tenantId != null)
|
||||
{
|
||||
clientBuilder = clientBuilder.WithTenantId(tenantId);
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP || NET5_0_WINDOWS10_0_17763_0
|
||||
clientBuilder = clientBuilder.WithBroker();
|
||||
#elif NETCOREAPP3_1
|
||||
clientBuilder = clientBuilder.WithWindowsBroker();
|
||||
#endif
|
||||
|
||||
clientBuilder = (redirectUri != null)
|
||||
? clientBuilder.WithRedirectUri(redirectUri)
|
||||
: clientBuilder.WithDefaultRedirectUri();
|
||||
|
||||
return clientBuilder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve an authorization token using the provided scopes.
|
||||
/// </summary>
|
||||
/// <param name="scopes">An array of scopes to pass along with the Graph request.</param>
|
||||
/// <param name="silentOnly">A value to determine whether account broker UI should be shown, if required by MSAL.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
|
||||
protected async Task<string> GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false)
|
||||
{
|
||||
await SemaphoreSlim.WaitAsync();
|
||||
|
||||
|
@ -153,7 +226,7 @@ namespace CommunityToolkit.Authentication
|
|||
AuthenticationResult authResult = null;
|
||||
try
|
||||
{
|
||||
var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault();
|
||||
var account = Account ?? (await Client.GetAccountsAsync()).FirstOrDefault();
|
||||
if (account != null)
|
||||
{
|
||||
authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync();
|
||||
|
@ -172,14 +245,26 @@ namespace CommunityToolkit.Authentication
|
|||
{
|
||||
try
|
||||
{
|
||||
if (_account != null)
|
||||
var paramBuilder = Client.AcquireTokenInteractive(scopes);
|
||||
|
||||
if (Account != null)
|
||||
{
|
||||
authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).WithAccount(_account).ExecuteAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).ExecuteAsync();
|
||||
paramBuilder = paramBuilder.WithAccount(Account);
|
||||
}
|
||||
|
||||
#if WINDOWS_UWP
|
||||
// For UWP, specify NoPrompt for the least intrusive user experience.
|
||||
paramBuilder = paramBuilder.WithPrompt(Prompt.NoPrompt);
|
||||
#else
|
||||
// Otherwise, get the process by FriendlyName from Application Domain
|
||||
var friendlyName = AppDomain.CurrentDomain.FriendlyName;
|
||||
var proc = Process.GetProcessesByName(friendlyName).First();
|
||||
|
||||
var windowHandle = proc.MainWindowHandle;
|
||||
paramBuilder = paramBuilder.WithParentActivityOrWindow(windowHandle);
|
||||
#endif
|
||||
|
||||
authResult = await paramBuilder.ExecuteAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -188,7 +273,7 @@ namespace CommunityToolkit.Authentication
|
|||
}
|
||||
}
|
||||
|
||||
_account = authResult?.Account;
|
||||
Account = authResult?.Account;
|
||||
|
||||
return authResult?.AccessToken;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
|
||||
namespace CommunityToolkit.Authentication.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the MsalProvider.
|
||||
/// </summary>
|
||||
public static class MsalProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper function to initialize the token cache for non-UWP apps. MSAL handles this automatically on UWP.
|
||||
/// </summary>
|
||||
/// <param name="provider">The instance of <see cref="MsalProvider"/> to init the cache for.</param>
|
||||
/// <param name="storageProperties">Properties for configuring the storage cache.</param>
|
||||
/// <param name="logger">Passing null uses the default TraceSource logger.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
|
||||
public static async Task InitTokenCacheAsync(
|
||||
this MsalProvider provider,
|
||||
StorageCreationProperties storageProperties,
|
||||
TraceSource logger = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
// Token cache persistence (not required on UWP as MSAL does it for you)
|
||||
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties, logger);
|
||||
cacheHelper.RegisterCache(provider.Client.UserTokenCache);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -110,6 +110,8 @@ namespace CommunityToolkit.Authentication
|
|||
/// <inheritdoc />
|
||||
public override async Task AuthenticateRequestAsync(HttpRequestMessage request)
|
||||
{
|
||||
AddSdkVersion(request);
|
||||
|
||||
string token = await GetTokenAsync();
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderScheme, token);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace CommunityToolkit.Graph.Extensions
|
|||
.Photo
|
||||
.Content
|
||||
.Request()
|
||||
.WithScopes(new string[] { "user.readbasic.all" })
|
||||
.WithScopes(new string[] { "user.read" })
|
||||
.GetAsync();
|
||||
}
|
||||
|
||||
|
|
|
@ -155,7 +155,6 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\FileIcon.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
<Content Include="Properties\Default.rd.xml" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Authentication;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace WpfMsalProviderSample
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
protected override void OnActivated(EventArgs e)
|
||||
{
|
||||
if (ProviderManager.Instance.GlobalProvider == null)
|
||||
{
|
||||
string clientId = "YOUR-CLIENT-ID-HERE";
|
||||
string[] scopes = new string[] { "User.Read" };
|
||||
string redirectUri = "http://localhost";
|
||||
ProviderManager.Instance.GlobalProvider = new MsalProvider(clientId, scopes, redirectUri);
|
||||
}
|
||||
|
||||
base.OnActivated(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<Application x:Class="WpfNet5WindowsMsalProviderSample.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WpfNet5WindowsMsalProviderSample"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -0,0 +1,51 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Authentication;
|
||||
using CommunityToolkit.Authentication.Extensions;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
|
||||
namespace WpfNet5WindowsMsalProviderSample
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
static readonly string ClientId = "YOUR-CLIENT-ID-HERE";
|
||||
static readonly string[] Scopes = new string[] { "User.Read" };
|
||||
|
||||
protected override void OnActivated(EventArgs e)
|
||||
{
|
||||
InitializeGlobalProviderAsync();
|
||||
base.OnActivated(e);
|
||||
}
|
||||
|
||||
private async Task InitializeGlobalProviderAsync()
|
||||
{
|
||||
if (ProviderManager.Instance.GlobalProvider == null)
|
||||
{
|
||||
var provider = new MsalProvider(ClientId, Scopes, null, false, true);
|
||||
|
||||
// Configure the token cache storage for non-UWP applications.
|
||||
var storageProperties = new StorageCreationPropertiesBuilder(CacheConfig.CacheFileName, CacheConfig.CacheDir)
|
||||
.WithLinuxKeyring(
|
||||
CacheConfig.LinuxKeyRingSchema,
|
||||
CacheConfig.LinuxKeyRingCollection,
|
||||
CacheConfig.LinuxKeyRingLabel,
|
||||
CacheConfig.LinuxKeyRingAttr1,
|
||||
CacheConfig.LinuxKeyRingAttr2)
|
||||
.WithMacKeyChain(
|
||||
CacheConfig.KeyChainServiceName,
|
||||
CacheConfig.KeyChainAccountName)
|
||||
.Build();
|
||||
await provider.InitTokenCacheAsync(storageProperties);
|
||||
|
||||
ProviderManager.Instance.GlobalProvider = provider;
|
||||
|
||||
await provider.TrySilentSignInAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
|
||||
namespace WpfNet5WindowsMsalProviderSample
|
||||
{
|
||||
/// <summary>
|
||||
/// https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache
|
||||
/// </summary>
|
||||
class CacheConfig
|
||||
{
|
||||
public const string CacheFileName = "msal_cache.dat";
|
||||
public const string CacheDir = "MSAL_CACHE";
|
||||
|
||||
public const string KeyChainServiceName = "msal_service";
|
||||
public const string KeyChainAccountName = "msal_account";
|
||||
|
||||
public const string LinuxKeyRingSchema = "com.contoso.devtools.tokencache";
|
||||
public const string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection;
|
||||
public const string LinuxKeyRingLabel = "MSAL token cache for all Contoso dev tool apps.";
|
||||
public static readonly KeyValuePair<string, string> LinuxKeyRingAttr1 = new KeyValuePair<string, string>("Version", "1");
|
||||
public static readonly KeyValuePair<string, string> LinuxKeyRingAttr2 = new KeyValuePair<string, string>("ProductGroup", "MyApps");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<UserControl x:Class="WpfNet5WindowsMsalProviderSample.LoginButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WpfNet5WindowsMsalProviderSample"
|
||||
mc:Ignorable="d"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||
<Grid>
|
||||
<Button x:Name="MyButton"
|
||||
Click="MyButton_Click"
|
||||
IsEnabled="{Binding IsEnabled}" />
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,70 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Authentication;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace WpfNet5WindowsMsalProviderSample
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple button for triggering the globally configured IProvider to sign in and out.
|
||||
/// </summary>
|
||||
public partial class LoginButton : UserControl
|
||||
{
|
||||
public LoginButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState();
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
private void UpdateState()
|
||||
{
|
||||
var provider = ProviderManager.Instance.GlobalProvider;
|
||||
if (provider == null || provider.State == ProviderState.Loading)
|
||||
{
|
||||
MyButton.Content = "Sign in";
|
||||
IsEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (provider.State)
|
||||
{
|
||||
case ProviderState.SignedIn:
|
||||
MyButton.Content = "Sign out";
|
||||
break;
|
||||
|
||||
case ProviderState.SignedOut:
|
||||
MyButton.Content = "Sign in";
|
||||
break;
|
||||
}
|
||||
|
||||
IsEnabled = true;
|
||||
}
|
||||
|
||||
private void MyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
var provider = ProviderManager.Instance.GlobalProvider;
|
||||
if (provider != null)
|
||||
{
|
||||
switch (provider.State)
|
||||
{
|
||||
case ProviderState.SignedOut:
|
||||
provider.SignInAsync();
|
||||
break;
|
||||
|
||||
case ProviderState.SignedIn:
|
||||
provider.SignOutAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<Window x:Class="WpfNet5WindowsMsalProviderSample.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfNet5WindowsMsalProviderSample"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<StackPanel>
|
||||
<local:LoginButton />
|
||||
<TextBlock x:Name="SignedInUserTextBlock" />
|
||||
</StackPanel>
|
||||
</Window>
|
|
@ -6,7 +6,7 @@ using CommunityToolkit.Authentication;
|
|||
using CommunityToolkit.Graph.Extensions;
|
||||
using System.Windows;
|
||||
|
||||
namespace WpfMsalProviderSample
|
||||
namespace WpfNet5WindowsMsalProviderSample
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.19.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Authentication.Msal\CommunityToolkit.Authentication.Msal.csproj" />
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Graph\CommunityToolkit.Graph.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,7 +1,7 @@
|
|||
<Application x:Class="WpfMsalProviderSample.App"
|
||||
<Application x:Class="WpfNetCoreMsalProviderSample.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WpfMsalProviderSample"
|
||||
xmlns:local="clr-namespace:WpfNetCoreMsalProviderSample"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Authentication;
|
||||
using CommunityToolkit.Authentication.Extensions;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
|
||||
namespace WpfNetCoreMsalProviderSample
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
static readonly string ClientId = "YOUR-CLIENT-ID-HERE";
|
||||
static readonly string[] Scopes = new string[] { "User.Read" };
|
||||
|
||||
protected override void OnActivated(EventArgs e)
|
||||
{
|
||||
InitializeGlobalProviderAsync();
|
||||
base.OnActivated(e);
|
||||
}
|
||||
|
||||
private async Task InitializeGlobalProviderAsync()
|
||||
{
|
||||
if (ProviderManager.Instance.GlobalProvider == null)
|
||||
{
|
||||
var provider = new MsalProvider(ClientId, Scopes, null, false, true);
|
||||
|
||||
// Configure the token cache storage for non-UWP applications.
|
||||
var storageProperties = new StorageCreationPropertiesBuilder(CacheConfig.CacheFileName, CacheConfig.CacheDir)
|
||||
.WithLinuxKeyring(
|
||||
CacheConfig.LinuxKeyRingSchema,
|
||||
CacheConfig.LinuxKeyRingCollection,
|
||||
CacheConfig.LinuxKeyRingLabel,
|
||||
CacheConfig.LinuxKeyRingAttr1,
|
||||
CacheConfig.LinuxKeyRingAttr2)
|
||||
.WithMacKeyChain(
|
||||
CacheConfig.KeyChainServiceName,
|
||||
CacheConfig.KeyChainAccountName)
|
||||
.Build();
|
||||
await provider.InitTokenCacheAsync(storageProperties);
|
||||
|
||||
ProviderManager.Instance.GlobalProvider = provider;
|
||||
|
||||
await provider.TrySilentSignInAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
|
@ -0,0 +1,27 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
|
||||
namespace WpfNetCoreMsalProviderSample
|
||||
{
|
||||
/// <summary>
|
||||
/// https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache
|
||||
/// </summary>
|
||||
class CacheConfig
|
||||
{
|
||||
public const string CacheFileName = "msal_cache.dat";
|
||||
public const string CacheDir = "MSAL_CACHE";
|
||||
|
||||
public const string KeyChainServiceName = "msal_service";
|
||||
public const string KeyChainAccountName = "msal_account";
|
||||
|
||||
public const string LinuxKeyRingSchema = "com.contoso.devtools.tokencache";
|
||||
public const string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection;
|
||||
public const string LinuxKeyRingLabel = "MSAL token cache for all Contoso dev tool apps.";
|
||||
public static readonly KeyValuePair<string, string> LinuxKeyRingAttr1 = new KeyValuePair<string, string>("Version", "1");
|
||||
public static readonly KeyValuePair<string, string> LinuxKeyRingAttr2 = new KeyValuePair<string, string>("ProductGroup", "MyApps");
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
<UserControl x:Class="WpfMsalProviderSample.LoginButton"
|
||||
<UserControl x:Class="WpfNetCoreMsalProviderSample.LoginButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WpfMsalProviderSample"
|
||||
xmlns:local="clr-namespace:WpfNetCoreMsalProviderSample"
|
||||
mc:Ignorable="d"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||
<Grid>
|
|
@ -2,12 +2,12 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Authentication;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using CommunityToolkit.Authentication;
|
||||
|
||||
namespace WpfMsalProviderSample
|
||||
namespace WpfNetCoreMsalProviderSample
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple button for triggering the globally configured IProvider to sign in and out.
|
|
@ -1,9 +1,9 @@
|
|||
<Window x:Class="WpfMsalProviderSample.MainWindow"
|
||||
<Window x:Class="WpfNetCoreMsalProviderSample.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfMsalProviderSample"
|
||||
xmlns:local="clr-namespace:WpfNetCoreMsalProviderSample"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<StackPanel>
|
|
@ -0,0 +1,37 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Authentication;
|
||||
using CommunityToolkit.Graph.Extensions;
|
||||
|
||||
namespace WpfNetCoreMsalProviderSample
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged;
|
||||
}
|
||||
|
||||
private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
|
||||
{
|
||||
if (e.NewState == ProviderState.SignedIn)
|
||||
{
|
||||
SignedInUserTextBlock.Text = "Signed in as...";
|
||||
|
||||
var graphClient = ProviderManager.Instance.GlobalProvider.GetClient();
|
||||
var me = await graphClient.Me.Request().GetAsync();
|
||||
|
||||
SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
SignedInUserTextBlock.Text = "Please sign in.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,11 +4,12 @@
|
|||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<DeterministicSourcePaths Condition="'$(EnableSourceLink)' == ''">false</DeterministicSourcePaths>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Authentication.Msal\CommunityToolkit.Authentication.Msal.csproj" />
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
|
||||
<ProjectReference Include="..\..\CommunityToolkit.Graph\CommunityToolkit.Graph.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
@ -43,12 +43,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{022B
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UwpMsalProviderSample", "Samples\UwpMsalProviderSample\UwpMsalProviderSample.csproj", "{D0F6A1EB-806E-424A-BDCA-9F749F12774F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfMsalProviderSample", "Samples\WpfMsalProviderSample\WpfMsalProviderSample.csproj", "{EDAD72A8-498B-4645-AD1A-E5CDBDB610F7}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfNet5WindowsMsalProviderSample", "Samples\WpfNet5WindowsMsalProviderSample\WpfNet5WindowsMsalProviderSample.csproj", "{EDAD72A8-498B-4645-AD1A-E5CDBDB610F7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UwpWindowsProviderSample", "Samples\UwpWindowsProviderSample\UwpWindowsProviderSample.csproj", "{C60C02DF-F44C-4449-A1D4-C2DC3A7959B9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManualGraphRequestSample", "Samples\ManualGraphRequestSample\ManualGraphRequestSample.csproj", "{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfNetCoreMsalProviderSample", "Samples\WpfNetCoreMsalProviderSample\WpfNetCoreMsalProviderSample.csproj", "{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
CI|Any CPU = CI|Any CPU
|
||||
|
@ -540,6 +542,46 @@ Global
|
|||
{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}.Release|x86.ActiveCfg = Release|x86
|
||||
{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}.Release|x86.Build.0 = Release|x86
|
||||
{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}.Release|x86.Deploy.0 = Release|x86
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|Any CPU.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|ARM.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|ARM.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|ARM64.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|x64.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|x64.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|x86.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.CI|x86.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|Any CPU.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|ARM.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|ARM.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|ARM64.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|x64.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|x64.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|x86.ActiveCfg = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Native|x86.Build.0 = Debug|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|x64.Build.0 = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -550,6 +592,7 @@ Global
|
|||
{EDAD72A8-498B-4645-AD1A-E5CDBDB610F7} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
|
||||
{C60C02DF-F44C-4449-A1D4-C2DC3A7959B9} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
|
||||
{192CC7FD-408D-4B0B-9032-AD06C7BE46C6} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
|
||||
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {20641689-5BDE-4F6F-8889-CCDD2CC2685E}
|
||||
|
|
Загрузка…
Ссылка в новой задаче