Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add cache extensions #1278

Merged
merged 4 commits into from
Jun 21, 2021
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
7 changes: 6 additions & 1 deletion src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472' Or '$(TargetFramework)' == 'net462'">
Expand All @@ -115,6 +115,11 @@
<Compile Remove="TokenCacheProviders\Session\**" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
101 changes: 101 additions & 0 deletions src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web.TokenCacheProviders;
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;

namespace Microsoft.Identity.Web
{
/// <summary>
/// Extension methods to expose a simplified developer experience for
/// adding token caches to MSAL.NET confidential client applications
/// in ASP.NET, or .NET Core, or .NET FW.
/// </summary>
public static class TokenCacheExtensions
{
/// <summary>
/// Use a token cache and choose the serialization part by adding it to
/// the services collection and configuring its options.
/// </summary>
/// <returns>The confidential client application.</returns>
/// <param name="confidentialClientApp">Confidential client application.</param>
/// <param name="initializeCaches">Action that you'll use to add a cache serialization
/// to the service collection passed as an argument.</param>
/// <returns>The application for chaining.</returns>
/// <example>
///
/// The following code adds a distributed in-memory token cache.
///
/// <code>
/// app.AddTokenCaches(services =>
/// {
/// // In memory distributed token cache
/// // In net472, requires to reference Microsoft.Extensions.Caching.Memory
/// services.AddDistributedTokenCaches();
/// services.AddDistributedMemoryCache();
/// });
/// </code>
///
/// The following code adds a token cache based on REDIS and initializes
/// its configuration.
///
/// <code>
/// app.AddTokenCaches(services =>
/// {
/// services.AddDistributedTokenCaches();
/// // Redis token cache
/// // Requires to reference Microsoft.Extensions.Caching.StackExchangeRedis
/// services.AddStackExchangeRedisCache(options =>
/// {
/// options.Configuration = "localhost";
/// options.InstanceName = "Redis";
/// });
/// });
/// </code>
/// If using distributed token caches, use AddDistributedTokenCaches.
/// </example>
/// <remarks>Don't use this method in ASP.NET Core. Just add use the ConfigureServices method
Copy link
Collaborator

@jmprieur jmprieur Jun 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a 'see also' to recommend AddDistributedTokenCaches?
If you are using distributed token caches, is recommanded, or something like that? #Resolved

/// instead.</remarks>
internal static IConfidentialClientApplication AddTokenCaches(
this IConfidentialClientApplication confidentialClientApp,
Action<IServiceCollection> initializeCaches)
{
if (confidentialClientApp is null)
{
throw new ArgumentNullException(nameof(confidentialClientApp));
}

if (initializeCaches is null)
{
throw new ArgumentNullException(nameof(initializeCaches));
}

IHostBuilder hostBuilder = Host.CreateDefaultBuilder()
.ConfigureLogging(logger => { })
.ConfigureServices(services =>
{
initializeCaches(services);
});

IServiceProvider serviceProvider = hostBuilder.Build().Services;
IMsalTokenCacheProvider msalTokenCacheProvider = serviceProvider.GetRequiredService<IMsalTokenCacheProvider>();
msalTokenCacheProvider.Initialize(confidentialClientApp.UserTokenCache);
msalTokenCacheProvider.Initialize(confidentialClientApp.AppTokenCache);
return confidentialClientApp;
}

/// <summary>
/// Add an in-memory well partitioned token cache to MSAL.NET confidential client
/// application. Don't use this method in ASP.NET Core: rather use:
/// <code>services.AddInMemoryTokenCaches()</code> in ConfigureServices.
/// </summary>
/// <param name="confidentialClientApp">Confidential client application.</param>
/// <returns>The application for chaining.</returns>
/// <example>
///
/// The following code adds an in-memory token cache.
///
/// <code>
/// app.AddInMemoryTokenCaches();
/// </code>
///
/// </example>
/// <remarks>Don't use this method in ASP.NET Core. Just add use the ConfigureServices method
/// instead.</remarks>
public static IConfidentialClientApplication AddInMemoryTokenCaches(
this IConfidentialClientApplication confidentialClientApp)
{
if (confidentialClientApp is null)
{
throw new ArgumentNullException(nameof(confidentialClientApp));
}

confidentialClientApp.AddTokenCaches(services =>
{
services.AddInMemoryTokenCaches();
});
return confidentialClientApp;
}

/// <summary>
/// Add a distributed token cache.
/// </summary>
/// <param name="confidentialClientApp">Confidential client application.</param>
/// <param name="initializeDistributedCache">Action taking a <see cref="IServiceCollection"/>
/// and by which you initialize your distributed cache.</param>
/// <returns>The application for chaining.</returns>
/// <example>
/// The following code adds a token cache based on REDIS and initializes
/// its configuration.
///
/// <code>
/// app.AddDistributedTokenCaches(services =>
/// {
/// // Redis token cache
/// // Requires to reference Microsoft.Extensions.Caching.StackExchangeRedis
/// services.AddStackExchangeRedisCache(options =>
/// {
/// options.Configuration = "localhost";
/// options.InstanceName = "Redis";
/// });
/// });
/// </code>
///
/// </example>
/// <remarks>Don't use this method in ASP.NET Core. Just add use the ConfigureServices method
/// instead.</remarks>
public static IConfidentialClientApplication AddDistributedTokenCaches(
this IConfidentialClientApplication confidentialClientApp,
Action<IServiceCollection> initializeDistributedCache)
{
if (confidentialClientApp is null)
{
throw new ArgumentNullException(nameof(confidentialClientApp));
}

if (initializeDistributedCache is null)
{
throw new ArgumentNullException(nameof(initializeDistributedCache));
}

confidentialClientApp.AddTokenCaches(services =>
{
services.AddDistributedTokenCaches();
initializeDistributedCache(services);
});
return confidentialClientApp;
}
}
}
80 changes: 80 additions & 0 deletions tests/Microsoft.Identity.Web.Test/CacheExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web.Test.Common;
using Xunit;

namespace Microsoft.Identity.Web.Test
{
public class CacheExtensionsTests
{
private IConfidentialClientApplication _confidentialApp;

[Fact]
public void InMemoryCacheExtensionsTests()
{
CreateCca();
_confidentialApp.AddInMemoryTokenCaches();

Assert.NotNull(_confidentialApp.UserTokenCache);
Assert.NotNull(_confidentialApp.AppTokenCache);
}

[Fact]
public void InMemoryCacheExtensions_NoCca_ThrowsException_Tests()
{
var ex = Assert.Throws<ArgumentNullException>(() => _confidentialApp.AddInMemoryTokenCaches());

Assert.Equal("confidentialClientApp", ex.ParamName);
}

[Fact]
public void DistributedCacheExtensionsTests()
{
CreateCca();
_confidentialApp.AddDistributedTokenCaches(services =>
{
services.AddDistributedMemoryCache();
});

Assert.NotNull(_confidentialApp.UserTokenCache);
Assert.NotNull(_confidentialApp.AppTokenCache);
}

[Fact]
public void DistributedCacheExtensions_NoCca_ThrowsException_Tests()
{
var ex = Assert.Throws<ArgumentNullException>(() => _confidentialApp.AddDistributedTokenCaches(services =>
{
services.AddDistributedMemoryCache();
}));

Assert.Equal("confidentialClientApp", ex.ParamName);
}

[Fact]
public void DistributedCacheExtensions_NoService_ThrowsException_Tests()
{
CreateCca();

var ex = Assert.Throws<ArgumentNullException>(() => _confidentialApp.AddDistributedTokenCaches(null));

Assert.Equal("initializeDistributedCache", ex.ParamName);
}

private void CreateCca()
{
if (_confidentialApp == null)
{
_confidentialApp = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithAuthority(TestConstants.AuthorityCommonTenant)
.WithClientSecret(TestConstants.ClientSecret)
.Build();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<Compile Include="MsalTokenCacheProviderTests.cs" />
<Compile Include="Certificates\CertificateDescriptionTests.cs"/>
<Compile Include="Certificates\DefaultCertificateLoaderTests.cs"/>
<Compile Include="CacheExtensionsTests.cs"/>
</ItemGroup>

</Project>