Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Updating to MSAL.NET 4.16 #38

Merged
merged 3 commits into from
Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WebApp/App_Start/Startup.Auth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void ConfigureAuth(IAppBuilder app)
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
// Upon successful sign in, get the access token & cache it using MSAL
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(context.AuthenticationTicket.Identity));
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "Mail.Read" }, context.Code).ExecuteAsync();
}

Expand Down
14 changes: 7 additions & 7 deletions WebApp/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ public async Task<ActionResult> SendMail()
// Before we render the send email screen, we use the incremental consent to obtain and cache the access token with the correct scopes
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = null;
var accounts = await app.GetAccountsAsync();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
string[] scopes = { "Mail.Send" };

try
{
// try to get an already cached token
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().ConfigureAwait(false);
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException ex)
{
Expand Down Expand Up @@ -108,21 +108,21 @@ public async Task<ActionResult> SendMail(string recipient, string subject, strin
string message = String.Format(messagetemplate, subject, body, recipient);

HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/me/microsoft.graph.sendMail")
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/me/sendMail")
{
Content = new StringContent(message, Encoding.UTF8, "application/json")
};


IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = null;
var accounts = await app.GetAccountsAsync();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
string[] scopes = { "Mail.Send" };

try
{
// try to get an already cached token
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().ConfigureAwait(false);
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -158,13 +158,13 @@ public async Task<ActionResult> ReadMail()
{
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = null;
var accounts = await app.GetAccountsAsync();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
string[] scopes = { "Mail.Read" };

try
{
// try to get token silently
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().ConfigureAwait(false);
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
Expand Down
4 changes: 2 additions & 2 deletions WebApp/MailApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
<HintPath>..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Identity.Client, Version=4.7.1.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Identity.Client.4.7.1\lib\net45\Microsoft.Identity.Client.dll</HintPath>
<Reference Include="Microsoft.Identity.Client, Version=4.16.0.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Identity.Client.4.16.0\lib\net45\Microsoft.Identity.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.JsonWebTokens, Version=5.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.JsonWebTokens.5.4.0\lib\net461\Microsoft.IdentityModel.JsonWebTokens.dll</HintPath>
Expand Down
30 changes: 12 additions & 18 deletions WebApp/Utils/MSALAppMemoryTokenCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ namespace WebApp.Utils
/// <seealso cref="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization"/>
public class MSALAppMemoryTokenCache
{
/// <summary>
/// The application cache key
/// </summary>
internal readonly string AppCacheId;

/// <summary>
/// The backing MemoryCache instance
/// </summary>
Expand All @@ -55,11 +50,8 @@ public class MSALAppMemoryTokenCache
/// Initializes a new instance of the <see cref="MSALAppMemoryTokenCache"/> class.
/// </summary>
/// <param name="tokenCache">The client's instance of the token cache.</param>
/// <param name="clientId">The application's id (Client ID).</param>
public MSALAppMemoryTokenCache(ITokenCache tokenCache, string clientId)
public MSALAppMemoryTokenCache(ITokenCache tokenCache)
{
AppCacheId = clientId + "_AppTokenCache";

tokenCache.SetBeforeAccess(AppTokenCacheBeforeAccessNotification);
tokenCache.SetAfterAccess(AppTokenCacheAfterAccessNotification);
tokenCache.SetBeforeWrite(AppTokenCacheBeforeWriteNotification);
Expand All @@ -74,19 +66,14 @@ private void AppTokenCacheBeforeWriteNotification(TokenCacheNotificationArgs arg
// Since we are using a MemoryCache ,whose methods are threads safe, we need not to do anything in this handler.
}

public void Clear()
{
memoryCache.Remove(AppCacheId);
}

/// <summary>
/// Triggered right before MSAL needs to access the cache. Reload the cache from the persistence store in case it changed since the last access.
/// </summary>
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
private void AppTokenCacheBeforeAccessNotification(TokenCacheNotificationArgs args)
{
// Load the token cache from memory
byte[] tokenCacheBytes = (byte[])this.memoryCache.Get(AppCacheId);
byte[] tokenCacheBytes = (byte[])this.memoryCache.Get(args.SuggestedCacheKey);
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
}

Expand All @@ -99,9 +86,16 @@ private void AppTokenCacheAfterAccessNotification(TokenCacheNotificationArgs arg
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
// Reflect changes in the persistence store
this.memoryCache.Set(AppCacheId, args.TokenCache.SerializeMsalV3(), cacheDuration);

if (args.HasTokens)
{
// No tokens => remove the entry in the cache
this.memoryCache.Remove(args.SuggestedCacheKey);
}
else
{
// Reflect changes in the persistence store
this.memoryCache.Set(args.SuggestedCacheKey, args.TokenCache.SerializeMsalV3(), cacheDuration);
}
}
}
}
Expand Down
92 changes: 20 additions & 72 deletions WebApp/Utils/MSALPerUserMemoryTokenCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,67 +46,23 @@ public class MSALPerUserMemoryTokenCache
/// </summary>
private readonly DateTimeOffset cacheDuration = DateTimeOffset.Now.AddHours(48);

/// <summary>
/// Once the user signes in, this will not be null and can be obtained via a call to Thread.CurrentPrincipal
/// </summary>
internal ClaimsPrincipal SignedInUser;

/// <summary>
/// Initializes a new instance of the <see cref="MSALPerUserMemoryTokenCache"/> class.
/// </summary>
/// <param name="tokenCache">The client's instance of the token cache.</param>
public MSALPerUserMemoryTokenCache(ITokenCache tokenCache)
{
Initialize(tokenCache, ClaimsPrincipal.Current);
}

/// <summary>
/// Initializes a new instance of the <see cref="MSALPerUserMemoryTokenCache"/> class.
/// </summary>
/// <param name="tokenCache">The client's instance of the token cache.</param>
/// <param name="user">The signed-in user for whom the cache needs to be established.</param>
public MSALPerUserMemoryTokenCache(ITokenCache tokenCache, ClaimsPrincipal user)
{
Initialize(tokenCache, user);
Initialize(tokenCache);
}

/// <summary>Initializes the cache instance</summary>
/// <param name="tokenCache">The ITokenCache passed through the constructor</param>
/// <param name="user">The signed-in user for whom the cache needs to be established..</param>
private void Initialize(ITokenCache tokenCache, ClaimsPrincipal user)
private void Initialize(ITokenCache tokenCache)
{
SignedInUser = user;

tokenCache.SetBeforeAccess(UserTokenCacheBeforeAccessNotification);
tokenCache.SetAfterAccess(UserTokenCacheAfterAccessNotification);
tokenCache.SetBeforeWrite(UserTokenCacheBeforeWriteNotification);

if (SignedInUser == null)
{
// No users signed in yet, so we return
return;
}
}

/// <summary>
/// Explores the Claims of a signed-in user (if available) to populate the unique Id of this cache's instance.
/// </summary>
/// <returns>The signed in user's object.tenant Id , if available in the ClaimsPrincipal.Current instance</returns>
internal string GetMsalAccountId()
{
if (SignedInUser != null)
{
return SignedInUser.GetMsalAccountId();
}
return null;
}

/// <summary>
/// Clears the TokenCache's copy of this user's cache.
/// </summary>
public void Clear()
{
memoryCache.Remove(GetMsalAccountId());
}

/// <summary>
Expand All @@ -115,18 +71,22 @@ public void Clear()
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs args)
{
SetSignedInUserFromNotificationArgs(args);

// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
string cacheKey = GetMsalAccountId();

if (string.IsNullOrWhiteSpace(cacheKey))
return;

// Ideally, methods that load and persist should be thread safe.MemoryCache.Get() is thread safe.
this.memoryCache.Set(cacheKey, args.TokenCache.SerializeMsalV3(), cacheDuration);
string cacheKey = args.SuggestedCacheKey;
if (args.HasTokens)
{
if (string.IsNullOrWhiteSpace(cacheKey))
return;

// Ideally, methods that load and persist should be thread safe.MemoryCache.Get() is thread safe.
memoryCache.Set(cacheKey, args.TokenCache.SerializeMsalV3(), cacheDuration);
}
else
{
memoryCache.Remove(cacheKey);
}
}
}

Expand All @@ -136,12 +96,13 @@ private void UserTokenCacheAfterAccessNotification(TokenCacheNotificationArgs ar
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
private void UserTokenCacheBeforeAccessNotification(TokenCacheNotificationArgs args)
{
string cacheKey = GetMsalAccountId();

if (string.IsNullOrWhiteSpace(cacheKey))
string cacheKey = args.SuggestedCacheKey;
if (string.IsNullOrEmpty(cacheKey))
{
return;
}

byte[] tokenCacheBytes = (byte[])this.memoryCache.Get(cacheKey);
byte[] tokenCacheBytes = (byte[])memoryCache.Get(cacheKey);
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
}

Expand All @@ -153,18 +114,5 @@ private void UserTokenCacheBeforeWriteNotification(TokenCacheNotificationArgs ar
{
// Since we are using a MemoryCache ,whose methods are threads safe, we need not to do anything in this handler.
}

/// <summary>
/// To keep the cache, ClaimsPrincipal and Sql in sync, we ensure that the user's object Id we obtained by MSAL after
/// successful sign-in is set as the key for the cache.
/// </summary>
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
private void SetSignedInUserFromNotificationArgs(TokenCacheNotificationArgs args)
{
if (SignedInUser == null && args.Account != null)
{
SignedInUser = args.Account.ToClaimsPrincipal();
}
}
}
}
17 changes: 6 additions & 11 deletions WebApp/Utils/MsalAppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,15 @@ namespace WebApp.Utils
public static class MsalAppBuilder
{
public static IConfidentialClientApplication BuildConfidentialClientApplication()
{
return BuildConfidentialClientApplication(ClaimsPrincipal.Current);
}

public static IConfidentialClientApplication BuildConfidentialClientApplication(ClaimsPrincipal currentUser)
{
IConfidentialClientApplication clientapp = ConfidentialClientApplicationBuilder.Create(AuthenticationConfig.ClientId)
.WithClientSecret(AuthenticationConfig.ClientSecret)
.WithRedirectUri(AuthenticationConfig.RedirectUri)
.WithAuthority(new Uri(AuthenticationConfig.Authority))
.Build();

// After the ConfidentialClientApplication is created, we overwrite its default UserTokenCache with our implementation
MSALPerUserMemoryTokenCache userTokenCache = new MSALPerUserMemoryTokenCache(clientapp.UserTokenCache, currentUser ?? ClaimsPrincipal.Current);

// After the ConfidentialClientApplication is created, we overwrite its default UserTokenCache serialization with our implementation
MSALPerUserMemoryTokenCache userTokenCache = new MSALPerUserMemoryTokenCache(clientapp.UserTokenCache);
return clientapp;
}

Expand All @@ -61,9 +55,10 @@ public static async Task ClearUserTokenCache()
// We only clear the user's tokens.
MSALPerUserMemoryTokenCache userTokenCache = new MSALPerUserMemoryTokenCache(clientapp.UserTokenCache);
var userAccount = await clientapp.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());

await clientapp.RemoveAsync(userAccount);
userTokenCache.Clear();
if (userAccount != null)
{
await clientapp.RemoveAsync(userAccount);
}
}
}
}
2 changes: 1 addition & 1 deletion WebApp/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<package id="Microsoft.AspNet.Razor" version="3.2.4" targetFramework="net472" />
<package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net472" />
<package id="Microsoft.AspNet.WebPages" version="3.2.4" targetFramework="net472" />
<package id="Microsoft.Identity.Client" version="4.7.1" targetFramework="net472" />
<package id="Microsoft.Identity.Client" version="4.16.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.JsonWebTokens" version="5.4.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Logging" version="5.4.0" targetFramework="net472" />
<package id="Microsoft.IdentityModel.Protocols" version="5.4.0" targetFramework="net472" />
Expand Down