Skip to content

Commit e6c7c61

Browse files
authored
Adding a new DistributedTokenCacheProvider (#173)
* Adding a new DistributedTokenCacheProvider This enables to decouple the serialization itself (done by a.NET Core IDistrributedCache implementation), from the token cache logic (done by the DistributedTokenCacheProvider) See https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-2.2#distributed-memory-cache. ```CSharp // or use a distributed Token Cache by adding .AddDistributedTokenCaches(); // and then choose your implementation. // See https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-2.2#distributed-memory-cache // For instance the distributed in memory cache (not cleaned when you stop the app) services.AddDistributedMemoryCache() // Or a Redis cache services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost"; options.InstanceName = "SampleInstance"; }); // Or even a SQL Server token cache services.AddDistributedSqlServerCache(options => { options.ConnectionString = _config["DistCache_ConnectionString"]; options.SchemaName = "dbo"; options.TableName = "TestCache"; }); ``` * processing PR feedback * Add more comments * Improving the identation * Updating the README.md with new pictures, and details about the Distributed token caches * updating the diagrams * Renaming DistributedTokenCacheProvider to DistributedTokenCacheAdapter as this is an adapter in this particular case cc: @bgavrilMS
1 parent 12e5980 commit e6c7c61

File tree

8 files changed

+289
-11
lines changed

8 files changed

+289
-11
lines changed

2-WebApp-graph-user/2-1-Call-MSGraph/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,37 @@ The two new lines of code:
104104
> - replace `using Microsoft.Identity.Web.TokenCacheProviders.InMemory` by `using Microsoft.Identity.Web.TokenCacheProviders.Session`
105105
> - Replace `.AddInMemoryTokenCaches()` by `.AddSessionTokenCaches()`
106106
> add `app.UseSession();` in the `Configure(IApplicationBuilder app, IHostingEnvironment env)` method, for instance after `app.UseCookiePolicy();`
107+
>
108+
>
109+
> You can also use a distributed token cache, and choose the serialization implementation. For this, in **Startup.cs**:
110+
> - replace `using Microsoft.Identity.Web.TokenCacheProviders.InMemory` by `using Microsoft.Identity.Web.TokenCacheProviders.Distributed`
111+
> - Replace `.AddInMemoryTokenCaches()` by `.AddDistributedTokenCaches()`
112+
> - Then choose the distributed cache implementation. For details, see https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-2.2#distributed-memory-cache
113+
>
114+
> ```CSharp
115+
> // use a distributed Token Cache by adding
116+
> .AddDistributedTokenCaches();
117+
>
118+
> // and then choose your implementation.
119+
>
120+
> // For instance the distributed in memory cache (not cleaned when you stop the app)
121+
> services.AddDistributedMemoryCache()
122+
>
123+
> // Or a Redis cache
124+
> services.AddStackExchangeRedisCache(options =>
125+
> {
126+
> options.Configuration = "localhost";
127+
> options.InstanceName = "SampleInstance";
128+
> });
129+
>
130+
> // Or even a SQL Server token cache
131+
> services.AddDistributedSqlServerCache(options =>
132+
> {
133+
> options.ConnectionString =_config["DistCache_ConnectionString"];
134+
> options.SchemaName = "dbo";
135+
> options.TableName = "TestCache";
136+
> });
137+
> ```
107138

108139
### Add additional files to call Microsoft Graph
109140

2-WebApp-graph-user/2-1-Call-MSGraph/Startup.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Extensions.Configuration;
88
using Microsoft.Extensions.DependencyInjection;
99
using Microsoft.Identity.Web;
10+
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;
1011
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
1112
using WebApp_OpenIDConnect_DotNet.Infrastructure;
1213
using WebApp_OpenIDConnect_DotNet.Services;
@@ -40,6 +41,32 @@ public void ConfigureServices(IServiceCollection services)
4041
.AddMsal(Configuration, new string[] { Constants.ScopeUserRead })
4142
.AddInMemoryTokenCaches();
4243

44+
/*
45+
// or use a distributed Token Cache by adding
46+
.AddDistributedTokenCaches();
47+
48+
// and then choose your implementation.
49+
// See https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-2.2#distributed-memory-cache
50+
51+
// For instance the distributed in memory cache (not cleared when you stop the app)
52+
services.AddDistributedMemoryCache()
53+
54+
// Or a Redis cache
55+
services.AddStackExchangeRedisCache(options =>
56+
{
57+
options.Configuration = "localhost";
58+
options.InstanceName = "SampleInstance";
59+
});
60+
61+
// Or even a SQL Server token cache
62+
services.AddDistributedSqlServerCache(options =>
63+
{
64+
options.ConnectionString =
65+
_config["DistCache_ConnectionString"];
66+
options.SchemaName = "dbo";
67+
options.TableName = "TestCache";
68+
});
69+
*/
4370
// Add Graph
4471
services.AddGraphService(Configuration);
4572

Microsoft.Identity.Web/Diagrams.cd

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,28 @@
4646
</TypeIdentifier>
4747
</Class>
4848
<Class Name="Microsoft.Identity.Web.WebAppServiceCollectionExtensions">
49-
<Position X="0.5" Y="0.5" Width="11" />
49+
<Position X="0.5" Y="0.5" Width="13.25" />
5050
<TypeIdentifier>
51-
<HashCode>AAAAAAAAAAAAAgAAAAAAAAAAAAAAAACAAAAAAAAAAAA=</HashCode>
51+
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAI=</HashCode>
5252
<FileName>WebAppServiceCollectionExtensions.cs</FileName>
5353
</TypeIdentifier>
5454
</Class>
5555
<Class Name="Microsoft.Identity.Web.WebApiServiceCollectionExtensions">
56-
<Position X="0.5" Y="2" Width="15" />
56+
<Position X="0.5" Y="2" Width="14.5" />
5757
<TypeIdentifier>
58-
<HashCode>AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAA=</HashCode>
58+
<HashCode>AAAAAACAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
5959
<FileName>WebApiServiceCollectionExtensions.cs</FileName>
6060
</TypeIdentifier>
6161
</Class>
62-
<Class Name="Microsoft.Identity.Web.MsalUiRequiredExceptionFilterAttribute">
62+
<Class Name="Microsoft.Identity.Web.AuthorizeForScopesAttribute">
6363
<Position X="0.5" Y="5.75" Width="3.5" />
6464
<Members>
6565
<Method Name="BuildAuthenticationPropertiesForIncrementalConsent" Hidden="true" />
6666
<Method Name="CanBeSolvedByReSignInUser" Hidden="true" />
6767
</Members>
6868
<TypeIdentifier>
69-
<HashCode>AAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAgAIAIA=</HashCode>
70-
<FileName>MsalUiRequiredExceptionFilterAttribute.cs</FileName>
69+
<HashCode>AAAAAAAAAAAAACAAAAAAIAAAAAAAAAAAAAAAAgAIAIA=</HashCode>
70+
<FileName>AuthorizeForScopesAttribute.cs</FileName>
7171
</TypeIdentifier>
7272
</Class>
7373
<Class Name="Microsoft.Identity.Web.AccountExtensions">

Microsoft.Identity.Web/README.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ to enable them to work with the Microsoft identity platform (formerly named Azur
1313

1414
As of today, ASP.NET Core web apps templates (`dot net new mvc -auth`) create web apps that sign in users with the Azure AD v1.0 endpoint (allowing to sign in users with their organizational accounts, also named *Work or school accounts*). This library brings `ServiceCollection` extension methods to be used in the ASP.NET Core web app **Startup.cs** file to enable the web app to sign in users with the Microsoft identity platform (formerly Azure AD v2.0 endpoint), and, optionally enable the web app to call APIs on behalf of the signed-in user.
1515

16-
![WebAppServiceCollectionExtensions](https://user-images.githubusercontent.com/13203188/62526924-0a563780-b7ef-11e9-8ce0-db284db3f02c.png)
16+
![WebAppServiceCollectionExtensions](https://user-images.githubusercontent.com/13203188/64252959-82ae3680-cf1c-11e9-8a01-0a0be728a78e.png)
1717

1818
### Web apps that sign in users - Startup.cs
1919

@@ -84,6 +84,11 @@ public class Startup
8484
}
8585
```
8686

87+
Note that by default, `AddMicrosoftIdentityPlatformAuthentication` gets the configuration from the "AzureAD" section of the configuration files. It has
88+
several parameters that you can change.
89+
90+
Also the proposed token cache serialization is in memory. you can also use the session cache, or various distributed caches
91+
8792
### Web app controller
8893

8994
For your web app to call web APIs on behalf of the signed-in user, you'll need to add a parameter of type `ITokenAcquisition` to the constructor of your controller (the `ITokenAcquisition` service will be injected by dependency injection by ASP.NET Core)
@@ -125,7 +130,7 @@ public class HomeController : Controller
125130

126131
The controller action is decorated by an attribute `AuthorizeForScopesAttribute` which enables to process the `MsalUiRequiredException` that could be thrown by the service implementing `ITokenAcquisition.GetAccessTokenOnBehalfOfUserAsync` so that the web app interacts with the user, and ask them to consent to the scopes, or re-sign-in if needed.
127132

128-
<img alt="AuthorizeForScopesAttribute" src="https://user-images.githubusercontent.com/13203188/62526956-18a45380-b7ef-11e9-99f3-c75085d61ce5.png" width="50%"/>
133+
<img alt="AuthorizeForScopesAttribute" src="https://user-images.githubusercontent.com/13203188/64253212-0bc56d80-cf1d-11e9-9666-2e72b78886ed.png" width="50%"/>
129134

130135
### Samples and documentation
131136

@@ -139,7 +144,7 @@ You can see in details how the library is used in the following samples:
139144

140145
The library also enables web APIs to work with the Microsoft identity platform, enabling them to process access tokens for both work and school and Microsoft personal accounts.
141146

142-
![image](https://user-images.githubusercontent.com/13203188/62526937-10e4af00-b7ef-11e9-9fee-c205c97653c5.png)
147+
![image](https://user-images.githubusercontent.com/13203188/64253058-ba1ce300-cf1c-11e9-8f01-88180fc0faed.png)
143148
144149
### Protected web APIS - Startup.cs
145150

@@ -209,6 +214,8 @@ public class Startup
209214
}
210215
```
211216

217+
Like for Web Apps, you can choose various token cache implementations.
218+
212219
If you're certain that your web API will need some specific scopes, you can optionally pass them as arguments to `AddProtectedApiCallsWebApis`.
213220

214221
### Web API controller
@@ -218,7 +225,7 @@ For your web API to call downstream APIs, you'll need to:
218225
- add (like in web apps), a parameter of type `ITokenAcquisition` to the constructor of your controller (the `ITokenAcquisition` service will be injected by dependency injection by ASP.NET Core)
219226
- verify, in your controller actions, that the token contains the scopes expected by the action. For this, you'll call the `VerifyUserHasAnyAcceptedScope` extension method on the `HttpContext`
220227

221-
<img alt="ScopesRequiredHttpContextExtensions" src="https://user-images.githubusercontent.com/13203188/62527104-60c37600-b7ef-11e9-8dcb-66bb982fe147.png" width="80%"/>
228+
<img alt="ScopesRequiredHttpContextExtensions" src="https://user-images.githubusercontent.com/13203188/64253176-f9e3ca80-cf1c-11e9-8fe9-df06cee11c25.png" width="80%"/>
222229

223230
- in your controller actions, to call: `ITokenAcquisition.GetAccessTokenOnBehalfOfUserAsync` passing the scopes for which to request a token.
224231

@@ -262,6 +269,36 @@ For web apps that calls web apis, and web APIs that call downstream APIs, the co
262269
| `AddInMemoryTokenCaches` | `TokenCacheProviders.InMemory` | In memory token cache serialization. This implementation is great in samples. It's also good in production applications provided you don't mind if the token cache is lost when the web app is restarted. `AddInMemoryTokenCaches` takes an optional parameter of type `MsalMemoryTokenCacheOptions` that enables you to specify the duration after which the cache entry will expire unless it's used.
263270
| `AddSqlTokenCaches` | `TokenCacheProviders.Sql` | The token cache maintained in a SQL database. This implementation is ideal for production applications that need to keep their token caches. AddSqlTokenCaches takes a parameter of type `MsalSqlTokenCacheOptions` that let you specify the SQL connection string
264271
| `AddSessionTokenCaches` | `TokenCacheProviders.Session` | The token cache is bound to the user session. This option isn't ideal if the ID token is too large because it contains too many claims as the cookie would be too large.
272+
| `AddDistributedTokenCaches` | `TokenCacheProviders.Distributed` | The token cache is an adapter against the ASP.NET Core `IDistributedCache` implementation, therefore enabling you to choose between a distributed memory cache, a Redis cache, or a SQL Server cache. For details about the IDistributedCache` implementations, see https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-2.2#distributed-memory-cache.
273+
274+
Examples of possible distributed cache:
275+
276+
```CSharp
277+
// or use a distributed Token Cache by adding
278+
services.AddMicrosoftIdentityPlatformAuthentication(Configuration)
279+
.AddMsal(new string[] { scopesToRequest })
280+
.AddDistributedTokenCaches();
281+
282+
// and then choose your implementation
283+
284+
// For instance the distributed in memory cache (not cleared when you stop the app)
285+
services.AddDistributedMemoryCache()
286+
287+
// Or a Redis cache
288+
services.AddStackExchangeRedisCache(options =>
289+
{
290+
options.Configuration = "localhost";
291+
options.InstanceName = "SampleInstance";
292+
});
293+
294+
// Or even a SQL Server token cache
295+
services.AddDistributedSqlServerCache(options =>
296+
{
297+
options.ConnectionString = _config["DistCache_ConnectionString"];
298+
options.SchemaName = "dbo";
299+
options.TableName = "TestCache";
300+
});
301+
```
265302

266303
## Other utility classes
267304

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed
7+
{
8+
/// <summary>
9+
/// Extension class used to add an in-memory token cache serializer to MSAL
10+
/// </summary>
11+
public static class DistributedTokenCacheAdapterExtension
12+
{
13+
/// <summary>Adds both the app and per-user in-memory token caches.</summary>
14+
/// <param name="services">The services collection to add to.</param>
15+
/// <param name="cacheOptions">The MSALMemoryTokenCacheOptions allows the caller to set the token cache expiration</param>
16+
/// <returns></returns>
17+
public static IServiceCollection AddDistributedTokenCaches(
18+
this IServiceCollection services)
19+
{
20+
AddDistributedAppTokenCache(services);
21+
AddDistributedUserTokenCache(services);
22+
return services;
23+
}
24+
25+
/// <summary>Adds the in-memory based application token cache to the service collection.</summary>
26+
/// <param name="services">The services collection to add to.</param>
27+
/// <param name="cacheOptions">The MSALMemoryTokenCacheOptions allows the caller to set the token cache expiration</param>
28+
public static IServiceCollection AddDistributedAppTokenCache(
29+
this IServiceCollection services)
30+
{
31+
services.AddDistributedMemoryCache();
32+
services.AddSingleton<IMsalAppTokenCacheProvider, MsalAppDistributedTokenCacheProvider>();
33+
return services;
34+
}
35+
36+
/// <summary>Adds the in-memory based per user token cache to the service collection.</summary>
37+
/// <param name="services">The services collection to add to.</param>
38+
/// <param name="cacheOptions">The MSALMemoryTokenCacheOptions allows the caller to set the token cache expiration</param>
39+
/// <returns></returns>
40+
public static IServiceCollection AddDistributedUserTokenCache(
41+
this IServiceCollection services)
42+
{
43+
services.AddDistributedMemoryCache();
44+
services.AddHttpContextAccessor();
45+
services.AddSingleton<IMsalUserTokenCacheProvider, MsalPerUserDistributedTokenCacheProvider>();
46+
return services;
47+
}
48+
}
49+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Security.Principal;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.Caching.Distributed;
9+
using Microsoft.Extensions.Caching.Memory;
10+
using Microsoft.Extensions.Options;
11+
using Microsoft.Identity.Client;
12+
13+
namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed
14+
{
15+
/// <summary>
16+
/// An implementation of token cache for Confidential clients backed by MemoryCache.
17+
/// MemoryCache is useful in Api scenarios where there is no HttpContext to cache data.
18+
/// </summary>
19+
/// <seealso cref="https://aka.ms/msal-net-token-cache-serialization"/>
20+
public class MsalAppDistributedTokenCacheProvider : MsalDistributedTokenCacheAdapter, IMsalAppTokenCacheProvider
21+
{
22+
public MsalAppDistributedTokenCacheProvider(IOptions<AzureADOptions> azureAdOptions,
23+
IHttpContextAccessor httpContextAccessor,
24+
IDistributedCache memoryCache,
25+
IOptions<DistributedCacheEntryOptions> cacheOptions) :
26+
base(azureAdOptions, httpContextAccessor, memoryCache, cacheOptions)
27+
{
28+
29+
}
30+
31+
public async Task InitializeAsync(ITokenCache tokenCache)
32+
{
33+
await InitializeAsync(tokenCache, true).ConfigureAwait(false);
34+
}
35+
}
36+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Extensions.Caching.Distributed;
8+
using Microsoft.Extensions.Caching.Memory;
9+
using Microsoft.Extensions.Options;
10+
11+
namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed
12+
{
13+
/// <summary>
14+
/// An implementation of token cache for both Confidential and Public clients backed by MemoryCache.
15+
/// </summary>
16+
/// <seealso cref="https://aka.ms/msal-net-token-cache-serialization"/>
17+
public class MsalDistributedTokenCacheAdapter : MsalAbstractTokenCacheProvider
18+
{
19+
/// <summary>
20+
/// .NET Core Memory cache
21+
/// </summary>
22+
private readonly IDistributedCache _distributedCache;
23+
24+
/// <summary>
25+
/// Msal memory token cache options
26+
/// </summary>
27+
private readonly DistributedCacheEntryOptions _cacheOptions;
28+
29+
/// <summary>
30+
/// Constructor
31+
/// </summary>
32+
/// <param name="azureAdOptions"></param>
33+
/// <param name="httpContextAccessor"></param>
34+
/// <param name="memoryCache"></param>
35+
/// <param name="cacheOptions"></param>
36+
public MsalDistributedTokenCacheAdapter(IOptions<AzureADOptions> azureAdOptions,
37+
IHttpContextAccessor httpContextAccessor,
38+
IDistributedCache memoryCache,
39+
IOptions<DistributedCacheEntryOptions> cacheOptions) :
40+
base(azureAdOptions, httpContextAccessor)
41+
{
42+
_distributedCache = memoryCache;
43+
_cacheOptions = cacheOptions.Value;
44+
}
45+
46+
protected override async Task RemoveKeyAsync(string cacheKey)
47+
{
48+
await _distributedCache.RemoveAsync(cacheKey).ConfigureAwait(false);
49+
}
50+
51+
protected override async Task<byte[]> ReadCacheBytesAsync(string cacheKey)
52+
{
53+
return await _distributedCache.GetAsync(cacheKey).ConfigureAwait(false);
54+
}
55+
56+
protected override async Task WriteCacheBytesAsync(string cacheKey, byte[] bytes)
57+
{
58+
await _distributedCache.SetAsync(cacheKey, bytes, _cacheOptions).ConfigureAwait(false) ;
59+
}
60+
}
61+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Extensions.Caching.Memory;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.Options;
9+
using Microsoft.Identity.Client;
10+
using System.Threading.Tasks;
11+
using Microsoft.Extensions.Caching.Distributed;
12+
13+
14+
namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed
15+
{
16+
/// <summary>
17+
/// An implementation of token cache for both Confidential and Public clients backed by MemoryCache.
18+
/// MemoryCache is useful in Api scenarios where there is no HttpContext.Session to cache data.
19+
/// </summary>
20+
/// <seealso cref="https://aka.ms/msal-net-token-cache-serialization"/>
21+
public class MsalPerUserDistributedTokenCacheProvider : MsalDistributedTokenCacheAdapter, IMsalUserTokenCacheProvider
22+
{
23+
public MsalPerUserDistributedTokenCacheProvider(IOptions<AzureADOptions> azureAdOptions,
24+
IHttpContextAccessor httpContextAccessor,
25+
IDistributedCache memoryCache,
26+
IOptions<DistributedCacheEntryOptions> cacheOptions) :
27+
base(azureAdOptions, httpContextAccessor, memoryCache, cacheOptions)
28+
{
29+
30+
}
31+
32+
public async Task InitializeAsync(ITokenCache tokenCache)
33+
{
34+
await InitializeAsync(tokenCache, false).ConfigureAwait(false);
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)