Skip to content

Commit ece3af0

Browse files
Add support for incremental consent via MsalProvider (#147)
* Add support for incremental consent via MsalProvider * Reverting changes to IProvider * Improved safety when checking for alternative scopes * Added SemaphoreSlim to WindowsProvider GetTokenAsync
1 parent 2c58710 commit ece3af0

File tree

4 files changed

+91
-34
lines changed

4 files changed

+91
-34
lines changed

CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17+
<PackageReference Include="Microsoft.Graph.Core" Version="2.0.3" />
1718
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.18.8" />
1819
</ItemGroup>
1920

CommunityToolkit.Authentication.Msal/MsalProvider.cs

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using System.Net.Http;
77
using System.Net.Http.Headers;
88
using System.Reflection;
9+
using System.Threading;
910
using System.Threading.Tasks;
11+
using Microsoft.Graph;
1012
using Microsoft.Identity.Client;
1113

1214
namespace CommunityToolkit.Authentication
@@ -16,6 +18,8 @@ namespace CommunityToolkit.Authentication
1618
/// </summary>
1719
public class MsalProvider : BaseProvider
1820
{
21+
private static readonly SemaphoreSlim SemaphoreSlim = new (1);
22+
1923
/// <inheritdoc />
2024
public override string CurrentAccountId => _account?.HomeAccountId?.Identifier;
2125

@@ -47,7 +51,7 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri
4751
.WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString())
4852
.Build();
4953

50-
Scopes = scopes ?? new string[] { string.Empty };
54+
Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty };
5155

5256
Client = client;
5357

@@ -60,7 +64,23 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri
6064
/// <inheritdoc/>
6165
public override async Task AuthenticateRequestAsync(HttpRequestMessage request)
6266
{
63-
string token = await GetTokenAsync();
67+
string token;
68+
69+
// Check if any specific scopes are being requested.
70+
if (request.Properties.TryGetValue(nameof(GraphRequestContext), out object requestContextObj) &&
71+
requestContextObj is GraphRequestContext requestContext &&
72+
requestContext.MiddlewareOptions.TryGetValue(nameof(AuthenticationHandlerOption), out IMiddlewareOption optionsMiddleware) &&
73+
optionsMiddleware is AuthenticationHandlerOption options &&
74+
options.AuthenticationProviderOption?.Scopes != null && options.AuthenticationProviderOption.Scopes.Length > 0)
75+
{
76+
var withScopes = options.AuthenticationProviderOption.Scopes;
77+
token = await this.GetTokenWithScopesAsync(withScopes);
78+
}
79+
else
80+
{
81+
token = await this.GetTokenAsync();
82+
}
83+
6484
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
6585
}
6686

@@ -119,42 +139,63 @@ public override async Task SignOutAsync()
119139
}
120140

121141
/// <inheritdoc/>
122-
public override async Task<string> GetTokenAsync(bool silentOnly = false)
142+
public override Task<string> GetTokenAsync(bool silentOnly = false)
123143
{
124-
AuthenticationResult authResult = null;
144+
return this.GetTokenWithScopesAsync(Scopes, silentOnly);
145+
}
146+
147+
private async Task<string> GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false)
148+
{
149+
await SemaphoreSlim.WaitAsync();
150+
125151
try
126152
{
127-
var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault();
128-
if (account != null)
153+
AuthenticationResult authResult = null;
154+
try
129155
{
130-
authResult = await Client.AcquireTokenSilent(Scopes, account).ExecuteAsync();
156+
var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault();
157+
if (account != null)
158+
{
159+
authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync();
160+
}
131161
}
132-
}
133-
catch (MsalUiRequiredException)
134-
{
135-
}
136-
catch
137-
{
138-
// Unexpected exception
139-
// TODO: Send exception to a logger.
140-
}
141-
142-
if (authResult == null && !silentOnly)
143-
{
144-
try
162+
catch (MsalUiRequiredException)
145163
{
146-
authResult = await Client.AcquireTokenInteractive(Scopes).WithPrompt(Prompt.SelectAccount).ExecuteAsync();
147164
}
148165
catch
149166
{
150167
// Unexpected exception
151168
// TODO: Send exception to a logger.
152169
}
153-
}
154170

155-
_account = authResult?.Account;
171+
if (authResult == null && !silentOnly)
172+
{
173+
try
174+
{
175+
if (_account != null)
176+
{
177+
authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).WithAccount(_account).ExecuteAsync();
178+
}
179+
else
180+
{
181+
authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).ExecuteAsync();
182+
}
183+
}
184+
catch
185+
{
186+
// Unexpected exception
187+
// TODO: Send exception to a logger.
188+
}
189+
}
190+
191+
_account = authResult?.Account;
156192

157-
return authResult?.AccessToken;
193+
return authResult?.AccessToken;
194+
}
195+
finally
196+
{
197+
SemaphoreSlim.Release();
198+
}
158199
}
159200
}
160201
}

CommunityToolkit.Authentication.Uwp/WindowsProvider.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Net.Http;
88
using System.Net.Http.Headers;
9+
using System.Threading;
910
using System.Threading.Tasks;
1011
using Windows.Networking.Connectivity;
1112
using Windows.Security.Authentication.Web;
@@ -28,6 +29,8 @@ public class WindowsProvider : BaseProvider
2829
/// </summary>
2930
public static string RedirectUri => string.Format("ms-appx-web://Microsoft.AAD.BrokerPlugIn/{0}", WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper());
3031

32+
private static readonly SemaphoreSlim SemaphoreSlim = new(1);
33+
3134
private const string AuthenticationHeaderScheme = "Bearer";
3235
private const string GraphResourcePropertyKey = "resource";
3336
private const string GraphResourcePropertyValue = "https://graph.microsoft.com";
@@ -181,18 +184,22 @@ public override async Task SignOutAsync()
181184
/// <inheritdoc />
182185
public override async Task<string> GetTokenAsync(bool silentOnly = false)
183186
{
184-
var internetConnectionProfile = NetworkInformation.GetInternetConnectionProfile();
185-
if (internetConnectionProfile == null)
186-
{
187-
// We are not online, no token for you.
188-
// TODO: Is there anything special to do when we go offline?
189-
return null;
190-
}
187+
await SemaphoreSlim.WaitAsync();
191188

192189
try
193190
{
191+
var internetConnectionProfile = NetworkInformation.GetInternetConnectionProfile();
192+
if (internetConnectionProfile == null)
193+
{
194+
// We are not online, no token for you.
195+
// TODO: Is there anything special to do when we go offline?
196+
return null;
197+
}
198+
199+
var scopes = _scopes;
200+
194201
// Attempt to authenticate silently.
195-
var authResult = await AuthenticateSilentAsync(_scopes);
202+
var authResult = await AuthenticateSilentAsync(scopes);
196203

197204
// Authenticate with user interaction as appropriate.
198205
if (authResult?.ResponseStatus != WebTokenRequestStatus.Success)
@@ -204,7 +211,7 @@ public override async Task<string> GetTokenAsync(bool silentOnly = false)
204211
}
205212

206213
// Attempt to authenticate interactively.
207-
authResult = await AuthenticateInteractiveAsync(_scopes);
214+
authResult = await AuthenticateInteractiveAsync(scopes);
208215
}
209216

210217
if (authResult?.ResponseStatus == WebTokenRequestStatus.Success)
@@ -232,9 +239,12 @@ public override async Task<string> GetTokenAsync(bool silentOnly = false)
232239
catch (Exception e)
233240
{
234241
// TODO: Log failure
235-
System.Diagnostics.Debug.WriteLine(e.Message);
236242
throw e;
237243
}
244+
finally
245+
{
246+
SemaphoreSlim.Release();
247+
}
238248
}
239249

240250
/// <summary>

CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ private async void LoadData()
131131
else
132132
{
133133
LoadDefaultImage();
134+
135+
if (!string.IsNullOrWhiteSpace(UserId) || !string.IsNullOrWhiteSpace(PersonQuery))
136+
{
137+
PersonDetails = null;
138+
}
134139
}
135140
}
136141

0 commit comments

Comments
 (0)