Skip to content

Commit a0bd598

Browse files
authored
Merge pull request #75 from Azure-Samples/jmprieur/syncMicrosoftIdentityWeb
Syncing Microsoft.Identity.Web content with ASP.NET Core web app tutorial
2 parents 1781201 + ab477ba commit a0bd598

14 files changed

+131
-89
lines changed

Microsoft.Identity.Web/ClaimConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,7 @@ public static class ClaimConstants
3535
public const string PreferredUserName = "preferred_username";
3636
public const string TenantId = "http://schemas.microsoft.com/identity/claims/tenantid";
3737
public const string Tid = "tid";
38+
public const string Scope = "http://schemas.microsoft.com/identity/claims/scope";
39+
public const string Roles = "roles";
3840
}
3941
}

Microsoft.Identity.Web/ClaimPrincipalExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static string GetLoginHint(this ClaimsPrincipal claimsPrincipal)
8787
/// <summary>
8888
/// Gets the domain-hint associated with an identity
8989
/// </summary>
90-
/// <param name="claimsPrincipal">Identity for which to compte the domain-hint</param>
90+
/// <param name="claimsPrincipal">Identity for which to compute the domain-hint</param>
9191
/// <returns>domain-hint for the identity, or <c>null</c> if it cannot be found</returns>
9292
public static string GetDomainHint(this ClaimsPrincipal claimsPrincipal)
9393
{

Microsoft.Identity.Web/Client/ITokenAcquisition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public interface ITokenAcquisition
1919
/// From the configuration of the Authentication of the ASP.NET Core Web API:
2020
/// <code>OpenIdConnectOptions options;</code>
2121
///
22-
/// Subscribe to the authorization code recieved event:
22+
/// Subscribe to the authorization code received event:
2323
/// <code>
2424
/// options.Events = new OpenIdConnectEvents();
2525
/// options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived;

Microsoft.Identity.Web/Client/MsalUiRequiredExceptionFilterAttribute.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
31
using Microsoft.AspNetCore.Authentication;
42
using Microsoft.AspNetCore.Http;
53
using Microsoft.AspNetCore.Mvc;
64
using Microsoft.AspNetCore.Mvc.Filters;
75
using Microsoft.Identity.Client;
86
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
7+
using System.Collections.Generic;
8+
using System.Linq;
99

1010
namespace Microsoft.Identity.Web.Client
1111
{
1212
/// <summary>
1313
/// Filter used on a controller action to trigger an incremental consent.
1414
/// </summary>
1515
/// <example>
16-
/// The following controller action will trigger
16+
/// The following controller action will trigger
1717
/// <code>
1818
/// [MsalUiRequiredExceptionFilter(Scopes = new[] {"Mail.Send"})]
1919
/// public async Task<IActionResult> SendEmail()
@@ -24,7 +24,7 @@ namespace Microsoft.Identity.Web.Client
2424
public class MsalUiRequiredExceptionFilterAttribute : ExceptionFilterAttribute
2525
{
2626
public string[] Scopes { get; set; }
27-
27+
2828
public override void OnException(ExceptionContext context)
2929
{
3030
MsalUiRequiredException msalUiRequiredException = context.Exception as MsalUiRequiredException;
@@ -33,7 +33,7 @@ public override void OnException(ExceptionContext context)
3333
msalUiRequiredException = context.Exception?.InnerException as MsalUiRequiredException;
3434
}
3535

36-
if (msalUiRequiredException!=null)
36+
if (msalUiRequiredException != null)
3737
{
3838
if (CanBeSolvedByReSignInUser(msalUiRequiredException))
3939
{
@@ -42,10 +42,10 @@ public override void OnException(ExceptionContext context)
4242
context.Result = new ChallengeResult(properties);
4343
}
4444
}
45-
45+
4646
base.OnException(context);
4747
}
48-
48+
4949
private bool CanBeSolvedByReSignInUser(MsalUiRequiredException ex)
5050
{
5151
// ex.ErrorCode != MsalUiRequiredException.UserNullError indicates a cache problem.
@@ -61,7 +61,7 @@ private bool CanBeSolvedByReSignInUser(MsalUiRequiredException ex)
6161
/// Build Authentication properties needed for an incremental consent.
6262
/// </summary>
6363
/// <param name="scopes">Scopes to request</param>
64-
/// <param name="ex">ui is present</param>
64+
/// <param name="ex">MsalUiRequiredException instance</param>
6565
/// <param name="context">current http context in the pipeline</param>
6666
/// <returns>AuthenticationProperties</returns>
6767
private AuthenticationProperties BuildAuthenticationPropertiesForIncrementalConsent(
@@ -94,4 +94,4 @@ private AuthenticationProperties BuildAuthenticationPropertiesForIncrementalCons
9494
return properties;
9595
}
9696
}
97-
}
97+
}

Microsoft.Identity.Web/Client/TokenAcquisition.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public TokenAcquisition(IConfiguration configuration, IMSALAppTokenCacheProvider
9898
/// From the configuration of the Authentication of the ASP.NET Core Web API:
9999
/// <code>OpenIdConnectOptions options;</code>
100100
///
101-
/// Subscribe to the authorization code recieved event:
101+
/// Subscribe to the authorization code received event:
102102
/// <code>
103103
/// options.Events = new OpenIdConnectEvents();
104104
/// options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived;
@@ -125,9 +125,17 @@ public async Task AddAccountToCacheFromAuthorizationCode(AuthorizationCodeReceiv
125125
try
126126
{
127127
// As AcquireTokenByAuthorizationCodeAsync is asynchronous we want to tell ASP.NET core that we are handing the code
128-
// even if it's not done yet, so that it does not concurrently call the Token endpoint.
128+
// even if it's not done yet, so that it does not concurrently call the Token endpoint. (otherwise there will be a
129+
// race condition ending-up in an error from Azure AD telling "code already redeemed")
129130
context.HandleCodeRedemption();
130131

132+
// The cache will need the claims from the ID token. In the case of guest scenarios
133+
// If they are not yet in the HttpContext.User's claims, adding them.
134+
if (!context.HttpContext.User.Claims.Any())
135+
{
136+
(context.HttpContext.User.Identity as ClaimsIdentity).AddClaims(context.Principal.Claims);
137+
}
138+
131139
var application = GetOrBuildConfidentialClientApplication(context.HttpContext, context.Principal);
132140

133141
// Do not share the access token with ASP.NET Core otherwise ASP.NET will cache it and will not send the OAuth 2.0 request in
@@ -272,15 +280,15 @@ public async Task RemoveAccount(RedirectContext context)
272280
account = accounts.FirstOrDefault(a => a.Username == user.GetLoginHint());
273281
}
274282

275-
if (account!=null)
283+
if (account != null)
276284
{
277285
this.UserTokenCacheProvider?.Clear(account.HomeAccountId.Identifier);
278286

279287
await app.RemoveAsync(account);
280288
}
281289
}
282290

283-
IConfidentialClientApplication application;
291+
private IConfidentialClientApplication application;
284292

285293
/// <summary>
286294
/// Creates an MSAL Confidential client application if needed
@@ -359,14 +367,15 @@ private async Task<string> GetAccessTokenOnBehalfOfUser(IConfidentialClientAppli
359367
// Get the account
360368
IAccount account = await application.GetAccountAsync(accountIdentifier);
361369

362-
// Special case for guest users as the Guest iod / tenant id are not surfaced.
370+
// Special case for guest users as the Guest id / tenant id are not surfaced.
363371
if (account == null)
364372
{
365373
var accounts = await application.GetAccountsAsync();
366374
account = accounts.FirstOrDefault(a => a.Username == loginHint);
367375
}
368376

369-
AuthenticationResult result;
377+
AuthenticationResult result = null;
378+
370379
if (string.IsNullOrWhiteSpace(tenant))
371380
{
372381
result = await application.AcquireTokenSilent(scopes.Except(scopesRequestedByMsalNet), account)
@@ -379,6 +388,7 @@ private async Task<string> GetAccessTokenOnBehalfOfUser(IConfidentialClientAppli
379388
.WithAuthority(authority)
380389
.ExecuteAsync();
381390
}
391+
382392
return result.AccessToken;
383393
}
384394

@@ -417,9 +427,8 @@ private void AddAccountToCacheFromJwt(IEnumerable<string> scopes, JwtSecurityTok
417427
}
418428
}
419429

420-
421430
/// <summary>
422-
/// Used in Web APIs (which therefore cannot have an interaction with the user).
431+
/// Used in Web APIs (which therefore cannot have an interaction with the user).
423432
/// Replies to the client through the HttpReponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that
424433
/// the client can trigger an iteraction with the user so that the user consents to more scopes
425434
/// </summary>
@@ -466,7 +475,7 @@ private static bool AcceptedTokenVersionIsNotTheSameAsTokenVersion(MsalUiRequire
466475
{
467476
// Normally app developers should not make decisions based on the internal AAD code
468477
// however until the STS sends sub-error codes for this error, this is the only
469-
// way to distinguish the case.
478+
// way to distinguish the case.
470479
// This is subject to change in the future
471480
return (msalSeviceException.Message.Contains("AADSTS50013"));
472481
}

Microsoft.Identity.Web/Client/TokenCacheProviders/InMemory/MSALPerUserMemoryTokenCacheProvider.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,13 @@ private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs ar
9494
// if the access operation resulted in a cache update
9595
if (args.HasStateChanged)
9696
{
97-
string cacheKey = args.Account?.HomeAccountId?.Identifier;
98-
if (string.IsNullOrEmpty(cacheKey))
99-
{
100-
cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
101-
}
97+
string cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
10298

10399
if (string.IsNullOrWhiteSpace(cacheKey))
104100
return;
105101

106102
// Ideally, methods that load and persist should be thread safe.MemoryCache.Get() is thread safe.
107103
this.memoryCache.Set(cacheKey, args.TokenCache.SerializeMsalV3(), this.CacheOptions.SlidingExpiration);
108-
109104
}
110105
}
111106

@@ -116,17 +111,13 @@ private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs ar
116111
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
117112
private void UserTokenCacheBeforeAccessNotification(TokenCacheNotificationArgs args)
118113
{
119-
string cacheKey = args.Account?.HomeAccountId?.Identifier;
120-
if (string.IsNullOrEmpty(cacheKey))
121-
{
122-
cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
123-
}
114+
string cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
124115

125116
if (string.IsNullOrWhiteSpace(cacheKey))
126117
return;
127118

128119
byte[] tokenCacheBytes = (byte[])this.memoryCache.Get(cacheKey);
129-
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache:true);
120+
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
130121
}
131122

132123
/// <summary>
@@ -137,4 +128,4 @@ private void UserTokenCacheBeforeWriteNotification(TokenCacheNotificationArgs ar
137128
{
138129
}
139130
}
140-
}
131+
}

Microsoft.Identity.Web/Client/TokenCacheProviders/Session/MSALPerUserSessionTokenCacheProvider.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,7 @@ private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs ar
107107
// if the access operation resulted in a cache update
108108
if (args.HasStateChanged)
109109
{
110-
string cacheKey = args.Account?.HomeAccountId?.Identifier;
111-
if (string.IsNullOrEmpty(cacheKey))
112-
{
113-
cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
114-
}
110+
string cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
115111

116112
if (string.IsNullOrWhiteSpace(cacheKey))
117113
return;
@@ -140,11 +136,7 @@ private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs ar
140136
private void UserTokenCacheBeforeAccessNotification(TokenCacheNotificationArgs args)
141137
{
142138
this.HttpContext.Session.LoadAsync().Wait();
143-
string cacheKey = args.Account?.HomeAccountId?.Identifier;
144-
if (string.IsNullOrEmpty(cacheKey))
145-
{
146-
cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
147-
}
139+
string cacheKey = httpContextAccessor.HttpContext.User.GetMsalAccountId();
148140
if (string.IsNullOrWhiteSpace(cacheKey))
149141
return;
150142

Microsoft.Identity.Web/Client/TokenCacheProviders/Sql/MSALPerUserSqlTokenCacheProvider.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,7 @@ private void UserTokenCacheBeforeAccessNotification(TokenCacheNotificationArgs a
121121
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
122122
private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs args)
123123
{
124-
string accountId = args.Account?.HomeAccountId?.Identifier;
125-
if (string.IsNullOrEmpty(accountId))
126-
{
127-
accountId = httpContextAccesssor.HttpContext.User.GetMsalAccountId();
128-
}
124+
string accountId = httpContextAccesssor.HttpContext.User.GetMsalAccountId();
129125

130126
// if state changed, i.e. new token obtained
131127
if (args.HasStateChanged && !string.IsNullOrWhiteSpace(accountId))
@@ -160,11 +156,7 @@ private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs ar
160156
/// </summary>
161157
private void ReadCacheForSignedInUser(TokenCacheNotificationArgs args)
162158
{
163-
string accountId = args.Account?.HomeAccountId?.Identifier;
164-
if (string.IsNullOrEmpty(accountId))
165-
{
166-
accountId = httpContextAccesssor.HttpContext.User.GetMsalAccountId();
167-
}
159+
string accountId = httpContextAccesssor.HttpContext.User.GetMsalAccountId();
168160
if (this.InMemoryCache == null) // first time access
169161
{
170162
this.InMemoryCache = GetLatestUserRecordQuery(accountId).FirstOrDefault();

Microsoft.Identity.Web/Microsoft.Identity.Web.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<ItemGroup>
88
<PackageReference Include="Microsoft.AspNetCore.App" />
99
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="2.2.0" />
10+
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="2.2.0" />
1011
<PackageReference Include="Microsoft.Identity.Client" Version="4.1.0" />
1112
</ItemGroup>
1213
</Project>

Microsoft.Identity.Web/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This library contains a set of reusable classes useful in Web Applications and W
55
The library contains helper classes to:
66

77
- **Bootstrap the web resource from the Startup.cs file** in your web application by just calling a few methods
8-
- `AddAzureAdV2Authentication` to add authentication with the Microsoft Identity platform (AAD v2.0), including managing the authority validation, and the sign-out.
8+
- `AddAzureAdV2Authentication` to add authentication with the Microsoft Identity platform (AAD v2.0), including managing the authority validation.
99

1010
```CSharp
1111
services.AddAzureAdV2Authentication();

0 commit comments

Comments
 (0)