Skip to content

Commit a33332d

Browse files
fix token request race condition
1 parent bf4c07b commit a33332d

File tree

4 files changed

+282
-83
lines changed

4 files changed

+282
-83
lines changed

access-token-management/src/AccessTokenManagement/ClientCredentialsTokenManagementService.cs

+21-52
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,19 @@
22
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
33

44
using Microsoft.Extensions.Logging;
5+
using System.Threading;
56

67
namespace Duende.AccessTokenManagement;
78

89
/// <summary>
910
/// Implements token management logic
1011
/// </summary>
11-
public class ClientCredentialsTokenManagementService : IClientCredentialsTokenManagementService
12+
public class ClientCredentialsTokenManagementService(
13+
IClientCredentialsTokenEndpointService clientCredentialsTokenEndpointService,
14+
IClientCredentialsTokenCache tokenCache,
15+
ILogger<ClientCredentialsTokenManagementService> logger
16+
) : IClientCredentialsTokenManagementService
1217
{
13-
private readonly ITokenRequestSynchronization _sync;
14-
private readonly IClientCredentialsTokenEndpointService _clientCredentialsTokenEndpointService;
15-
private readonly IClientCredentialsTokenCache _tokenCache;
16-
private readonly ILogger<ClientCredentialsTokenManagementService> _logger;
17-
18-
/// <summary>
19-
/// ctor
20-
/// </summary>
21-
/// <param name="sync"></param>
22-
/// <param name="clientCredentialsTokenEndpointService"></param>
23-
/// <param name="tokenCache"></param>
24-
/// <param name="logger"></param>
25-
public ClientCredentialsTokenManagementService(
26-
ITokenRequestSynchronization sync,
27-
IClientCredentialsTokenEndpointService clientCredentialsTokenEndpointService,
28-
IClientCredentialsTokenCache tokenCache,
29-
ILogger<ClientCredentialsTokenManagementService> logger)
30-
{
31-
_sync = sync;
32-
_clientCredentialsTokenEndpointService = clientCredentialsTokenEndpointService;
33-
_tokenCache = tokenCache;
34-
_logger = logger;
35-
}
3618

3719
/// <inheritdoc/>
3820
public async Task<ClientCredentialsToken> GetAccessTokenAsync(
@@ -46,47 +28,34 @@ public async Task<ClientCredentialsToken> GetAccessTokenAsync(
4628
{
4729
try
4830
{
49-
var item = await _tokenCache.GetAsync(clientName, parameters, cancellationToken).ConfigureAwait(false);
31+
var item = await tokenCache.GetAsync(
32+
clientName: clientName,
33+
requestParameters: parameters,
34+
cancellationToken: cancellationToken).ConfigureAwait(false);
5035
if (item != null)
5136
{
5237
return item;
5338
}
5439
}
5540
catch (Exception e)
5641
{
57-
_logger.LogError(e,
42+
logger.LogError(e,
5843
"Error trying to obtain token from cache for client {clientName}. Error = {error}. Will obtain new token.",
5944
clientName, e.Message);
6045
}
6146
}
6247

63-
return await _sync.SynchronizeAsync(clientName, async () =>
64-
{
65-
var token = await _clientCredentialsTokenEndpointService.RequestToken(clientName, parameters, cancellationToken).ConfigureAwait(false);
66-
if (token.IsError)
67-
{
68-
_logger.LogError(
69-
"Error requesting access token for client {clientName}. Error = {error}.",
70-
clientName, token.Error);
71-
72-
return token;
73-
}
74-
75-
try
76-
{
77-
await _tokenCache.SetAsync(clientName, token, parameters, cancellationToken).ConfigureAwait(false);
78-
}
79-
catch (Exception e)
80-
{
81-
_logger.LogError(e,
82-
"Error trying to set token in cache for client {clientName}. Error = {error}",
83-
clientName, e.Message);
84-
}
85-
86-
return token;
87-
}).ConfigureAwait(false);
48+
return await tokenCache.GetOrCreateAsync(
49+
clientName: clientName,
50+
requestParameters: parameters,
51+
factory: InvokeGetAccessToken,
52+
cancellationToken: cancellationToken).ConfigureAwait(false);
8853
}
8954

55+
private async Task<ClientCredentialsToken> InvokeGetAccessToken(string clientName, TokenRequestParameters parameters, CancellationToken cancellationToken)
56+
{
57+
return await clientCredentialsTokenEndpointService.RequestToken(clientName, parameters, cancellationToken).ConfigureAwait(false);
58+
}
9059

9160
/// <inheritdoc/>
9261
public Task DeleteAccessTokenAsync(
@@ -95,6 +64,6 @@ public Task DeleteAccessTokenAsync(
9564
CancellationToken cancellationToken = default)
9665
{
9766
parameters ??= new TokenRequestParameters();
98-
return _tokenCache.DeleteAsync(clientName, parameters, cancellationToken);
67+
return tokenCache.DeleteAsync(clientName, parameters, cancellationToken);
9968
}
10069
}

access-token-management/src/AccessTokenManagement/DistributedClientCredentialsTokenCache.cs

+49-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Duende Software. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
33

4+
using System.Collections.Concurrent;
45
using System.Text.Json;
56
using Microsoft.Extensions.Caching.Distributed;
67
using Microsoft.Extensions.Logging;
@@ -11,27 +12,19 @@ namespace Duende.AccessTokenManagement;
1112
/// <summary>
1213
/// Client access token cache using IDistributedCache
1314
/// </summary>
14-
public class DistributedClientCredentialsTokenCache : IClientCredentialsTokenCache
15+
public class DistributedClientCredentialsTokenCache(
16+
IDistributedCache cache,
17+
ITokenRequestSynchronization synchronization,
18+
IOptions<ClientCredentialsTokenManagementOptions> options,
19+
ILogger<DistributedClientCredentialsTokenCache> logger
20+
)
21+
: IClientCredentialsTokenCache
1522
{
16-
private readonly IDistributedCache _cache;
17-
private readonly ILogger<DistributedClientCredentialsTokenCache> _logger;
18-
private readonly ClientCredentialsTokenManagementOptions _options;
23+
private readonly IDistributedCache _cache = cache;
24+
private readonly ITokenRequestSynchronization _synchronization = synchronization;
25+
private readonly ILogger<DistributedClientCredentialsTokenCache> _logger = logger;
26+
private readonly ClientCredentialsTokenManagementOptions _options = options.Value;
1927

20-
/// <summary>
21-
/// ctor
22-
/// </summary>
23-
/// <param name="cache"></param>
24-
/// <param name="options"></param>
25-
/// <param name="logger"></param>
26-
public DistributedClientCredentialsTokenCache(
27-
IDistributedCache cache,
28-
IOptions<ClientCredentialsTokenManagementOptions> options,
29-
ILogger<DistributedClientCredentialsTokenCache> logger)
30-
{
31-
_cache = cache;
32-
_logger = logger;
33-
_options = options.Value;
34-
}
3528

3629
/// <inheritdoc/>
3730
public async Task SetAsync(
@@ -56,6 +49,43 @@ public async Task SetAsync(
5649
await _cache.SetStringAsync(cacheKey, data, entryOptions, token: cancellationToken).ConfigureAwait(false);
5750
}
5851

52+
public async Task<ClientCredentialsToken> GetOrCreateAsync(
53+
string clientName, TokenRequestParameters requestParameters,
54+
Func<string, TokenRequestParameters, CancellationToken, Task<ClientCredentialsToken>> factory,
55+
CancellationToken cancellationToken = default)
56+
{
57+
ArgumentNullException.ThrowIfNull(clientName);
58+
59+
var cacheKey = GenerateCacheKey(_options, clientName, requestParameters);
60+
61+
62+
return await _synchronization.SynchronizeAsync(cacheKey, async () =>
63+
{
64+
var token = await factory(clientName, requestParameters, cancellationToken).ConfigureAwait(false);
65+
if (token.IsError)
66+
{
67+
_logger.LogError(
68+
"Error requesting access token for client {clientName}. Error = {error}.",
69+
clientName, token.Error);
70+
71+
return token;
72+
}
73+
74+
try
75+
{
76+
await SetAsync(clientName, token, requestParameters, cancellationToken).ConfigureAwait(false);
77+
}
78+
catch (Exception e)
79+
{
80+
_logger.LogError(e,
81+
"Error trying to set token in cache for client {clientName}. Error = {error}",
82+
clientName, e.Message);
83+
}
84+
85+
return token;
86+
}).ConfigureAwait(false);
87+
}
88+
5989
/// <inheritdoc/>
6090
public async Task<ClientCredentialsToken?> GetAsync(
6191
string clientName,

access-token-management/src/AccessTokenManagement/Interfaces/IClientCredentialsTokenCache.cs

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ Task SetAsync(
2222
TokenRequestParameters requestParameters,
2323
CancellationToken cancellationToken = default);
2424

25+
Task<ClientCredentialsToken> GetOrCreateAsync(
26+
string clientName,
27+
TokenRequestParameters requestParameters,
28+
Func<string, TokenRequestParameters, CancellationToken, Task<ClientCredentialsToken>> factory,
29+
CancellationToken cancellationToken = default);
30+
2531
/// <summary>
2632
/// Retrieves a client access token from the cache
2733
/// </summary>

0 commit comments

Comments
 (0)