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:
Shane Weaver 2021-10-25 13:56:19 -07:00 коммит произвёл GitHub
Родитель 69197dd945
Коммит 33f781a577
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
27 изменённых файлов: 554 добавлений и 77 удалений

Просмотреть файл

@ -1,8 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup> <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> <Title>Windows Community Toolkit .NET Standard Auth Services</Title>
<Description> <Description>
This library provides an authentication provider based on the native Windows dialogues. It is part of the Windows Community Toolkit. 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" /> <PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.19.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.Identity.Client.Desktop" Version="4.37.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" /> <ProjectReference Include="..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
</ItemGroup> </ItemGroup>

Просмотреть файл

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@ -11,6 +12,16 @@ using System.Threading.Tasks;
using Microsoft.Graph; using Microsoft.Graph;
using Microsoft.Identity.Client; 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 namespace CommunityToolkit.Authentication
{ {
/// <summary> /// <summary>
@ -18,52 +29,73 @@ namespace CommunityToolkit.Authentication
/// </summary> /// </summary>
public class MsalProvider : BaseProvider 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); private static readonly SemaphoreSlim SemaphoreSlim = new (1);
/// <summary>
/// Gets or sets the currently authenticated user account.
/// </summary>
public IAccount Account { get; protected set; }
/// <inheritdoc /> /// <inheritdoc />
public override string CurrentAccountId => _account?.HomeAccountId?.Identifier; public override string CurrentAccountId => Account?.HomeAccountId?.Identifier;
/// <summary> /// <summary>
/// Gets the MSAL.NET Client used to authenticate the user. /// Gets or sets the MSAL.NET Client used to authenticate the user.
/// </summary> /// </summary>
protected IPublicClientApplication Client { get; private set; } public IPublicClientApplication Client { get; protected set; }
/// <summary> /// <summary>
/// Gets an array of scopes to use for accessing Graph resources. /// Gets an array of scopes to use for accessing Graph resources.
/// </summary> /// </summary>
protected string[] Scopes { get; private set; } protected string[] Scopes { get; }
private IAccount _account;
/// <summary> /// <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> /// </summary>
/// <param name="clientId">Registered ClientId.</param> /// <param name="client">Registered ClientId 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="scopes">List of Scopes to initially request.</param>
/// <param name="autoSignIn">Determines whether the provider attempts to silently log in upon instantionation.</param> /// <param name="autoSignIn">Determines whether the provider attempts to silently log in upon creation.</param>
public MsalProvider(string clientId, string[] scopes = null, string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient", bool autoSignIn = true) 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; Client = client;
Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty };
if (autoSignIn) 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/> /// <inheritdoc/>
public override async Task AuthenticateRequestAsync(HttpRequestMessage request) public override async Task AuthenticateRequestAsync(HttpRequestMessage request)
{ {
AddSdkVersion(request);
string token; string token;
// Check if any specific scopes are being requested. // Check if any specific scopes are being requested.
@ -87,7 +119,7 @@ namespace CommunityToolkit.Authentication
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<bool> TrySilentSignInAsync() public override async Task<bool> TrySilentSignInAsync()
{ {
if (_account != null && State == ProviderState.SignedIn) if (Account != null && State == ProviderState.SignedIn)
{ {
return true; return true;
} }
@ -108,7 +140,7 @@ namespace CommunityToolkit.Authentication
/// <inheritdoc/> /// <inheritdoc/>
public override async Task SignInAsync() public override async Task SignInAsync()
{ {
if (_account != null || State != ProviderState.SignedOut) if (Account != null || State != ProviderState.SignedOut)
{ {
return; return;
} }
@ -129,10 +161,10 @@ namespace CommunityToolkit.Authentication
/// <inheritdoc /> /// <inheritdoc />
public override async Task SignOutAsync() public override async Task SignOutAsync()
{ {
if (_account != null) if (Account != null)
{ {
await Client.RemoveAsync(_account); await Client.RemoveAsync(Account);
_account = null; Account = null;
} }
State = ProviderState.SignedOut; State = ProviderState.SignedOut;
@ -144,7 +176,48 @@ namespace CommunityToolkit.Authentication
return this.GetTokenWithScopesAsync(Scopes, silentOnly); 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(); await SemaphoreSlim.WaitAsync();
@ -153,7 +226,7 @@ namespace CommunityToolkit.Authentication
AuthenticationResult authResult = null; AuthenticationResult authResult = null;
try try
{ {
var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault(); var account = Account ?? (await Client.GetAccountsAsync()).FirstOrDefault();
if (account != null) if (account != null)
{ {
authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync(); authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync();
@ -172,14 +245,26 @@ namespace CommunityToolkit.Authentication
{ {
try try
{ {
if (_account != null) var paramBuilder = Client.AcquireTokenInteractive(scopes);
if (Account != null)
{ {
authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).WithAccount(_account).ExecuteAsync(); paramBuilder = paramBuilder.WithAccount(Account);
}
else
{
authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).ExecuteAsync();
} }
#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 catch
{ {
@ -188,7 +273,7 @@ namespace CommunityToolkit.Authentication
} }
} }
_account = authResult?.Account; Account = authResult?.Account;
return authResult?.AccessToken; 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 /> /// <inheritdoc />
public override async Task AuthenticateRequestAsync(HttpRequestMessage request) public override async Task AuthenticateRequestAsync(HttpRequestMessage request)
{ {
AddSdkVersion(request);
string token = await GetTokenAsync(); string token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderScheme, token); request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderScheme, token);
} }

Просмотреть файл

@ -71,7 +71,7 @@ namespace CommunityToolkit.Graph.Extensions
.Photo .Photo
.Content .Content
.Request() .Request()
.WithScopes(new string[] { "user.readbasic.all" }) .WithScopes(new string[] { "user.read" })
.GetAsync(); .GetAsync();
} }

Просмотреть файл

@ -155,7 +155,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Assets\FileIcon.png" /> <Content Include="Assets\FileIcon.png" />
<None Include="Package.StoreAssociation.xml" />
<Content Include="Properties\Default.rd.xml" /> <Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" /> <Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.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 CommunityToolkit.Graph.Extensions;
using System.Windows; using System.Windows;
namespace WpfMsalProviderSample namespace WpfNet5WindowsMsalProviderSample
{ {
/// <summary> /// <summary>
/// Interaction logic for MainWindow.xaml /// 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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMsalProviderSample" xmlns:local="clr-namespace:WpfNetCoreMsalProviderSample"
StartupUri="MainWindow.xaml"> StartupUri="MainWindow.xaml">
<Application.Resources> <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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfMsalProviderSample" xmlns:local="clr-namespace:WpfNetCoreMsalProviderSample"
mc:Ignorable="d" mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"> DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid> <Grid>

Просмотреть файл

@ -2,12 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using CommunityToolkit.Authentication;
using System; using System;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using CommunityToolkit.Authentication;
namespace WpfMsalProviderSample namespace WpfNetCoreMsalProviderSample
{ {
/// <summary> /// <summary>
/// A simple button for triggering the globally configured IProvider to sign in and out. /// 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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfMsalProviderSample" xmlns:local="clr-namespace:WpfNetCoreMsalProviderSample"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"> Title="MainWindow" Height="450" Width="800">
<StackPanel> <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> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<DeterministicSourcePaths Condition="'$(EnableSourceLink)' == ''">false</DeterministicSourcePaths>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
<ProjectReference Include="..\..\CommunityToolkit.Authentication.Msal\CommunityToolkit.Authentication.Msal.csproj" /> <ProjectReference Include="..\..\CommunityToolkit.Authentication.Msal\CommunityToolkit.Authentication.Msal.csproj" />
<ProjectReference Include="..\..\CommunityToolkit.Authentication\CommunityToolkit.Authentication.csproj" />
<ProjectReference Include="..\..\CommunityToolkit.Graph\CommunityToolkit.Graph.csproj" /> <ProjectReference Include="..\..\CommunityToolkit.Graph\CommunityToolkit.Graph.csproj" />
</ItemGroup> </ItemGroup>

Просмотреть файл

@ -43,12 +43,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{022B
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UwpMsalProviderSample", "Samples\UwpMsalProviderSample\UwpMsalProviderSample.csproj", "{D0F6A1EB-806E-424A-BDCA-9F749F12774F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UwpMsalProviderSample", "Samples\UwpMsalProviderSample\UwpMsalProviderSample.csproj", "{D0F6A1EB-806E-424A-BDCA-9F749F12774F}"
EndProject 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UwpWindowsProviderSample", "Samples\UwpWindowsProviderSample\UwpWindowsProviderSample.csproj", "{C60C02DF-F44C-4449-A1D4-C2DC3A7959B9}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UwpWindowsProviderSample", "Samples\UwpWindowsProviderSample\UwpWindowsProviderSample.csproj", "{C60C02DF-F44C-4449-A1D4-C2DC3A7959B9}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManualGraphRequestSample", "Samples\ManualGraphRequestSample\ManualGraphRequestSample.csproj", "{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManualGraphRequestSample", "Samples\ManualGraphRequestSample\ManualGraphRequestSample.csproj", "{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfNetCoreMsalProviderSample", "Samples\WpfNetCoreMsalProviderSample\WpfNetCoreMsalProviderSample.csproj", "{86AD7D3C-F03F-4FD1-8D69-AB0520805A65}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
CI|Any CPU = CI|Any CPU 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.ActiveCfg = Release|x86
{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}.Release|x86.Build.0 = Release|x86 {192CC7FD-408D-4B0B-9032-AD06C7BE46C6}.Release|x86.Build.0 = Release|x86
{192CC7FD-408D-4B0B-9032-AD06C7BE46C6}.Release|x86.Deploy.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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -550,6 +592,7 @@ Global
{EDAD72A8-498B-4645-AD1A-E5CDBDB610F7} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA} {EDAD72A8-498B-4645-AD1A-E5CDBDB610F7} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
{C60C02DF-F44C-4449-A1D4-C2DC3A7959B9} = {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} {192CC7FD-408D-4B0B-9032-AD06C7BE46C6} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
{86AD7D3C-F03F-4FD1-8D69-AB0520805A65} = {022BF202-8D0D-4A6B-8A5B-92376D2EB5DA}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {20641689-5BDE-4F6F-8889-CCDD2CC2685E} SolutionGuid = {20641689-5BDE-4F6F-8889-CCDD2CC2685E}