diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 622dff1e1..76ff6f7b9 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -88,7 +88,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index f8b51763a..10bf18386 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -1455,12 +1455,10 @@ MSAL memory token cache options. - + Initializes a new instance of the class. - - @@ -1506,11 +1504,11 @@ Token cache for which to initialize the serialization. A that represents a completed initialization operation. - + Clear the cache. - A that represents a completed clear operation. + HomeAccountId for a user account in the cache. @@ -1557,12 +1555,10 @@ MSAL memory token cache options. - + Constructor. - Configuration options. - Accessor to the HttpContext. serialization cache. Memory cache options. @@ -1593,23 +1589,6 @@ - - - Azure AD options. - - - - - HTTP accessor. - - - - - Constructor of the abstract token cache provider. - - Configuration options. - Accessor for the HttpContext. - Initializes the token cache serialization. @@ -1617,12 +1596,6 @@ Token cache to serialize/deserialize. A that represents a completed initialization operation. - - - Cache key. - - The cache key. - Raised AFTER MSAL added the new token in its in-memory copy of the cache. @@ -1639,11 +1612,11 @@ Token cache notification arguments. A that represents a completed operation. - + Clear the cache. - A that represents a completed clear operation. + HomeAccountId for a user account in the cache. @@ -1689,12 +1662,11 @@ https://aka.ms/msal-net-token-cache-serialization - + MSAL Token cache provider constructor. - Configuration options. - Accessor for an HttpContext. + Session for the current user. Logger. diff --git a/src/Microsoft.Identity.Web/TokenAcquisition.cs b/src/Microsoft.Identity.Web/TokenAcquisition.cs index d2063ea3e..19a0329d6 100644 --- a/src/Microsoft.Identity.Web/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/TokenAcquisition.cs @@ -283,29 +283,34 @@ public async Task GetAccessTokenForAppAsync(IEnumerable scopes) /// A that represents a completed account removal operation. public async Task RemoveAccountAsync(RedirectContext context) { - IConfidentialClientApplication app = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - - // For B2C, we should remove all accounts of the user regardless the user flow - if (_microsoftIdentityOptions.IsB2C) + ClaimsPrincipal user = context.HttpContext.User; + string? userId = user.GetMsalAccountId(); + if (!string.IsNullOrEmpty(userId)) { - var b2cAccounts = await app.GetAccountsAsync().ConfigureAwait(false); + IConfidentialClientApplication app = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - foreach (var b2cAccount in b2cAccounts) + // For B2C, we should remove all accounts of the user regardless the user flow + if (_microsoftIdentityOptions.IsB2C) { - await app.RemoveAsync(b2cAccount).ConfigureAwait(false); - } + var b2cAccounts = await app.GetAccountsAsync().ConfigureAwait(false); - _tokenCacheProvider?.ClearAsync().ConfigureAwait(false); - } - else - { - string? identifier = context.HttpContext.User.GetMsalAccountId(); - IAccount account = await app.GetAccountAsync(identifier).ConfigureAwait(false); + foreach (var b2cAccount in b2cAccounts) + { + await app.RemoveAsync(b2cAccount).ConfigureAwait(false); + } - if (account != null) + await _tokenCacheProvider.ClearAsync(userId).ConfigureAwait(false); + } + else { - await app.RemoveAsync(account).ConfigureAwait(false); - _tokenCacheProvider?.ClearAsync().ConfigureAwait(false); + string? identifier = context.HttpContext.User.GetMsalAccountId(); + IAccount account = await app.GetAccountAsync(identifier).ConfigureAwait(false); + + if (account != null) + { + await app.RemoveAsync(account).ConfigureAwait(false); + await _tokenCacheProvider.ClearAsync(userId).ConfigureAwait(false); + } } } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs index 520847c24..866c46cad 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; @@ -28,16 +27,11 @@ public class MsalDistributedTokenCacheAdapter : MsalAbstractTokenCacheProvider /// /// Initializes a new instance of the class. /// - /// - /// /// /// public MsalDistributedTokenCacheAdapter( - IOptions microsoftIdentityOptions, - IHttpContextAccessor httpContextAccessor, IDistributedCache memoryCache, IOptions cacheOptions) - : base(microsoftIdentityOptions, httpContextAccessor) { if (cacheOptions == null) { diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs index d78208b46..bf85a894a 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs @@ -19,9 +19,10 @@ public interface IMsalTokenCacheProvider Task InitializeAsync(ITokenCache tokenCache); /// - /// Clear the cache. + /// Clear the user token cache. /// + /// HomeAccountId for a user account in the cache. /// A that represents a completed clear operation. - Task ClearAsync(); + Task ClearAsync(string homeAccountId); } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs index 061a19654..1f4f7c7aa 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs @@ -28,16 +28,11 @@ public class MsalMemoryTokenCacheProvider : MsalAbstractTokenCacheProvider /// /// Constructor. /// - /// Configuration options. - /// Accessor to the HttpContext. /// serialization cache. /// Memory cache options. public MsalMemoryTokenCacheProvider( - IOptions microsoftIdentityOptions, - IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, IOptions cacheOptions) - : base(microsoftIdentityOptions, httpContextAccessor) { if (cacheOptions == null) { diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs index 24c094aef..69bb28e6b 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs @@ -2,10 +2,7 @@ // Licensed under the MIT License. using System; -using System.IdentityModel.Tokens.Jwt; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; using Microsoft.Identity.Client; namespace Microsoft.Identity.Web.TokenCacheProviders @@ -16,27 +13,6 @@ namespace Microsoft.Identity.Web.TokenCacheProviders /// public abstract class MsalAbstractTokenCacheProvider : IMsalTokenCacheProvider { - /// - /// Azure AD options. - /// - protected readonly IOptions _microsoftIdentityOptions; - - /// - /// HTTP accessor. - /// - protected readonly IHttpContextAccessor _httpContextAccessor; - - /// - /// Constructor of the abstract token cache provider. - /// - /// Configuration options. - /// Accessor for the HttpContext. - protected MsalAbstractTokenCacheProvider(IOptions microsoftIdentityOptions, IHttpContextAccessor httpContextAccessor) - { - _microsoftIdentityOptions = microsoftIdentityOptions; - _httpContextAccessor = httpContextAccessor; - } - /// /// Initializes the token cache serialization. /// @@ -56,27 +32,6 @@ public Task InitializeAsync(ITokenCache tokenCache) return Task.CompletedTask; } - /// - /// Cache key. - /// - /// The cache key. - private string? GetCacheKey(bool isAppTokenCache) - { - if (isAppTokenCache) - { - return $"{_microsoftIdentityOptions.Value.ClientId}_AppTokenCache"; - } - else - { - // In the case of Web Apps, the cache key is the user account Id, and the expectation is that AcquireTokenSilent - // should return a token otherwise this might require a challenge. - // In the case Web APIs, the token cache key is a hash of the access token used to call the Web API - JwtSecurityToken? jwtSecurityToken = _httpContextAccessor.HttpContext.GetTokenUsedToCallWebAPI(); - return (jwtSecurityToken != null) ? jwtSecurityToken.RawSignature - : _httpContextAccessor.HttpContext.User.GetMsalAccountId(); - } - } - /// /// Raised AFTER MSAL added the new token in its in-memory copy of the cache. /// This notification is called every time MSAL accesses the cache, not just when a write takes place: @@ -86,24 +41,26 @@ public Task InitializeAsync(ITokenCache tokenCache) /// Contains parameters used by the MSAL call accessing the cache. private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args) { - // if the access operation resulted in a cache update + // The access operation resulted in a cache update. if (args.HasStateChanged) { - string? cacheKey = GetCacheKey(args.IsApplicationCache); - if (!string.IsNullOrWhiteSpace(cacheKey)) + if (args.HasTokens) { - await WriteCacheBytesAsync(cacheKey, args.TokenCache.SerializeMsalV3()).ConfigureAwait(false); + await WriteCacheBytesAsync(args.SuggestedCacheKey, args.TokenCache.SerializeMsalV3()).ConfigureAwait(false); + } + else + { + // No token in the cache. we can remove the cache entry + await RemoveKeyAsync(args.SuggestedCacheKey).ConfigureAwait(false); } } } private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args) { - string? cacheKey = GetCacheKey(args.IsApplicationCache); - - if (!string.IsNullOrEmpty(cacheKey)) + if (!string.IsNullOrEmpty(args.SuggestedCacheKey)) { - byte[] tokenCacheBytes = await ReadCacheBytesAsync(cacheKey).ConfigureAwait(false); + byte[] tokenCacheBytes = await ReadCacheBytesAsync(args.SuggestedCacheKey).ConfigureAwait(false); args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true); } } @@ -121,16 +78,11 @@ protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args) /// /// Clear the cache. /// - /// A that represents a completed clear operation. - public async Task ClearAsync() + /// HomeAccountId for a user account in the cache. + public async Task ClearAsync(string homeAccountId) { - string? cacheKey = GetCacheKey(false); - - if (!string.IsNullOrEmpty(cacheKey)) - { - // This is a user token cache - await RemoveKeyAsync(cacheKey).ConfigureAwait(false); - } + // This is a user token cache + await RemoveKeyAsync(homeAccountId).ConfigureAwait(false); // TODO: Clear the cookie session if any. Get inspiration from // https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/240 diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs index 0120ec746..c0fee3b0f 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Microsoft.Identity.Web.TokenCacheProviders.Session { @@ -31,21 +30,19 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Session /// https://aka.ms/msal-net-token-cache-serialization public class MsalSessionTokenCacheProvider : MsalAbstractTokenCacheProvider, IMsalTokenCacheProvider { - private HttpContext CurrentHttpContext => _httpContextAccessor.HttpContext; - private readonly ILogger _logger; + private ILogger _logger; + private ISession _session; /// /// MSAL Token cache provider constructor. /// - /// Configuration options. - /// Accessor for an HttpContext. + /// Session for the current user. /// Logger. public MsalSessionTokenCacheProvider( - IOptions microsoftIdentityOptions, - IHttpContextAccessor httpContextAccessor, + ISession session, ILogger logger) - : base(microsoftIdentityOptions, httpContextAccessor) { + _session = session; _logger = logger; } @@ -57,18 +54,18 @@ public MsalSessionTokenCacheProvider( /// Read blob. protected override async Task ReadCacheBytesAsync(string cacheKey) { - await CurrentHttpContext.Session.LoadAsync().ConfigureAwait(false); + await _session.LoadAsync().ConfigureAwait(false); _sessionLock.EnterReadLock(); try { - if (CurrentHttpContext.Session.TryGetValue(cacheKey, out byte[] blob)) + if (_session.TryGetValue(cacheKey, out byte[] blob)) { - _logger.LogInformation($"Deserializing session {CurrentHttpContext.Session.Id}, cacheId {cacheKey}"); + _logger.LogInformation($"Deserializing session {_session.Id}, cacheId {cacheKey}"); } else { - _logger.LogInformation($"CacheId {cacheKey} not found in session {CurrentHttpContext.Session.Id}"); + _logger.LogInformation($"CacheId {cacheKey} not found in session {_session.Id}"); } return blob; @@ -89,10 +86,10 @@ protected override async Task WriteCacheBytesAsync(string cacheKey, byte[] bytes _sessionLock.EnterWriteLock(); try { - _logger.LogInformation($"Serializing session {CurrentHttpContext.Session.Id}, cacheId {cacheKey}"); + _logger.LogInformation($"Serializing session {_session.Id}, cacheId {cacheKey}"); // Reflect changes in the persistent store - CurrentHttpContext.Session.Set(cacheKey, bytes); + _session.Set(cacheKey, bytes); await Task.CompletedTask.ConfigureAwait(false); } finally @@ -110,10 +107,10 @@ protected override async Task RemoveKeyAsync(string cacheKey) _sessionLock.EnterWriteLock(); try { - _logger.LogInformation($"Clearing session {CurrentHttpContext.Session.Id}, cacheId {cacheKey}"); + _logger.LogInformation($"Clearing session {_session.Id}, cacheId {cacheKey}"); // Reflect changes in the persistent store - CurrentHttpContext.Session.Remove(cacheKey); + _session.Remove(cacheKey); await Task.CompletedTask.ConfigureAwait(false); } finally diff --git a/tests/B2CWebAppCallsWebApi/Client/appsettings.json b/tests/B2CWebAppCallsWebApi/Client/appsettings.json index 093940003..12e5052a8 100644 --- a/tests/B2CWebAppCallsWebApi/Client/appsettings.json +++ b/tests/B2CWebAppCallsWebApi/Client/appsettings.json @@ -1,15 +1,15 @@ { - "AzureAdB2C": { - "Instance": "https://fabrikamb2c.b2clogin.com", - "ClientId": "fdb91ff5-5ce6-41f3-bdbd-8267c817015d", - "Domain": "fabrikamb2c.onmicrosoft.com", - "SignedOutCallbackPath": "/signout/B2C_1_susi", - "SignUpSignInPolicyId": "b2c_1_susi", - "ResetPasswordPolicyId": "b2c_1_reset", - "EditProfilePolicyId": "b2c_1_edit_profile", // Optional profile editing policy - "ClientSecret": "[Copy the client secret added to the app from the Azure portal]" - //"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc - }, + "AzureAdB2C": { + "Instance": "https://fabrikamb2c.b2clogin.com", + "ClientId": "fdb91ff5-5ce6-41f3-bdbd-8267c817015d", + "Domain": "fabrikamb2c.onmicrosoft.com", + "SignedOutCallbackPath": "/signout/B2C_1_susi", + "SignUpSignInPolicyId": "b2c_1_susi", + "ResetPasswordPolicyId": "b2c_1_reset", + "EditProfilePolicyId": "b2c_1_edit_profile", // Optional profile editing policy + "ClientSecret": "[Copy the client secret added to the app from the Azure portal]" + //"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc + }, "TodoList": { /* TodoListScope is the scope of the Web API you want to call. This can be: "api://8f085429-c424-45c4-beb3-75f6f0a7924f/user_impersonation", diff --git a/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/MsalTestTokenCacheProvider.cs b/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/MsalTestTokenCacheProvider.cs index babe901db..aed2e2e47 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/MsalTestTokenCacheProvider.cs +++ b/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/MsalTestTokenCacheProvider.cs @@ -13,11 +13,8 @@ namespace Microsoft.Identity.Web.Test.Common.TestHelpers public class MsalTestTokenCacheProvider : MsalAbstractTokenCacheProvider { public MsalTestTokenCacheProvider( - IOptions microsoftIdentityOptions, - IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, IOptions cacheOptions) - : base(microsoftIdentityOptions, httpContextAccessor) { MemoryCache = memoryCache; _cacheOptions = cacheOptions?.Value; diff --git a/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs b/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs index 83ecb22b7..024a18c88 100644 --- a/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs +++ b/tests/Microsoft.Identity.Web.Test.Integration/AcquireTokenForAppIntegrationTests.cs @@ -98,8 +98,6 @@ private void InitializeTokenAcquisitionObjects() IHttpContextAccessor httpContextAccessor = CreateMockHttpContextAccessor(); _msalTestTokenCacheProvider = new MsalTestTokenCacheProvider( - microsoftIdentityOptions, - httpContextAccessor, _provider.GetService(), tokenOptions); diff --git a/tests/Microsoft.Identity.Web.Test.LabInfrastructure/Microsoft.Identity.Web.Test.LabInfrastructure.csproj b/tests/Microsoft.Identity.Web.Test.LabInfrastructure/Microsoft.Identity.Web.Test.LabInfrastructure.csproj index aa1d077b8..9bc8af74d 100644 --- a/tests/Microsoft.Identity.Web.Test.LabInfrastructure/Microsoft.Identity.Web.Test.LabInfrastructure.csproj +++ b/tests/Microsoft.Identity.Web.Test.LabInfrastructure/Microsoft.Identity.Web.Test.LabInfrastructure.csproj @@ -17,7 +17,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive