-
Notifications
You must be signed in to change notification settings - Fork 218
/
MsalAbstractTokenCacheProvider.cs
151 lines (134 loc) · 6.34 KB
/
MsalAbstractTokenCacheProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Copyright (c) Microsoft Corporation. All rights reserved.
// 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
{
/// <summary></summary>
/// <seealso cref="Microsoft.Identity.Web.TokenCacheProviders.IMsalTokenCacheProvider" />
public abstract class MsalAbstractTokenCacheProvider : IMsalTokenCacheProvider
{
/// <summary>
/// Azure AD options.
/// </summary>
protected readonly IOptions<MicrosoftIdentityOptions> _microsoftIdentityOptions;
/// <summary>
/// HTTP accessor.
/// </summary>
protected readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Constructor of the abstract token cache provider.
/// </summary>
/// <param name="microsoftIdentityOptions">Configuration options.</param>
/// <param name="httpContextAccessor">Accessor for the HttpContext.</param>
protected MsalAbstractTokenCacheProvider(IOptions<MicrosoftIdentityOptions> microsoftIdentityOptions, IHttpContextAccessor httpContextAccessor)
{
_microsoftIdentityOptions = microsoftIdentityOptions;
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Initializes the token cache serialization.
/// </summary>
/// <param name="tokenCache">Token cache to serialize/deserialize.</param>
/// <returns></returns>
public Task InitializeAsync(ITokenCache tokenCache)
{
if (tokenCache == null)
{
throw new ArgumentNullException(nameof(tokenCache));
}
tokenCache.SetBeforeAccessAsync(OnBeforeAccessAsync);
tokenCache.SetAfterAccessAsync(OnAfterAccessAsync);
tokenCache.SetBeforeWriteAsync(OnBeforeWriteAsync);
return Task.CompletedTask;
}
/// <summary>
/// Cache key.
/// </summary>
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();
}
}
/// <summary>
/// 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:
/// If MSAL's current operation resulted in a cache change, the property TokenCacheNotificationArgs.HasStateChanged will be set to true.
/// If that is the case, we call the TokenCache.SerializeMsalV3() to get a binary blob representing the latest cache content – and persist it.
/// </summary>
/// <param name="args">Contains parameters used by the MSAL call accessing the cache.</param>
private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
string cacheKey = GetCacheKey(args.IsApplicationCache);
if (!string.IsNullOrWhiteSpace(cacheKey))
{
await WriteCacheBytesAsync(cacheKey, args.TokenCache.SerializeMsalV3()).ConfigureAwait(false);
}
}
}
private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args)
{
string cacheKey = GetCacheKey(args.IsApplicationCache);
if (!string.IsNullOrEmpty(cacheKey))
{
byte[] tokenCacheBytes = await ReadCacheBytesAsync(cacheKey).ConfigureAwait(false);
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
}
}
/// <summary>
/// if you want to ensure that no concurrent write takes place, use this notification to place a lock on the entry.
/// </summary>
/// <param name="args">Token cache notification arguments.</param>
/// <returns></returns>
protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args)
{
return Task.CompletedTask;
}
/// <summary>
/// Clear the cache.
/// </summary>
public async Task ClearAsync()
{
// This is a user token cache
await RemoveKeyAsync(GetCacheKey(false)).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
}
/// <summary>
/// Method to be implemented by concrete cache serializers to write the cache bytes.
/// </summary>
/// <param name="cacheKey">Cache key.</param>
/// <param name="bytes">Bytes to write.</param>
/// <returns></returns>
protected abstract Task WriteCacheBytesAsync(string cacheKey, byte[] bytes);
/// <summary>
/// Method to be implemented by concrete cache serializers to Read the cache bytes.
/// </summary>
/// <param name="cacheKey">Cache key.</param>
/// <returns>Read bytes.</returns>
protected abstract Task<byte[]> ReadCacheBytesAsync(string cacheKey);
/// <summary>
/// Method to be implemented by concrete cache serializers to remove an entry from the cache.
/// </summary>
/// <param name="cacheKey">Cache key.</param>
protected abstract Task RemoveKeyAsync(string cacheKey);
}
}