This repository has been archived by the owner on Nov 18, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 85
/
AccessTokenManagementService.cs
171 lines (150 loc) · 6.81 KB
/
AccessTokenManagementService.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace IdentityModel.AspNetCore.AccessTokenManagement
{
/// <summary>
/// Implements basic token management logic
/// </summary>
public class AccessTokenManagementService : IAccessTokenManagementService
{
static readonly ConcurrentDictionary<string, Lazy<Task<string>>> _userRefreshDictionary =
new ConcurrentDictionary<string, Lazy<Task<string>>>();
static readonly ConcurrentDictionary<string, Lazy<Task<string>>> _clientTokenRequestDictionary =
new ConcurrentDictionary<string, Lazy<Task<string>>>();
private readonly IUserTokenStore _userTokenStore;
private readonly ISystemClock _clock;
private readonly AccessTokenManagementOptions _options;
private readonly ITokenEndpointService _tokenEndpointService;
private readonly IClientAccessTokenCache _clientAccessTokenCache;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<AccessTokenManagementService> _logger;
/// <summary>
/// ctor
/// </summary>
/// <param name="userTokenStore"></param>
/// <param name="clock"></param>
/// <param name="options"></param>
/// <param name="tokenEndpointService"></param>
/// <param name="clientAccessTokenCache"></param>
/// <param name="httpContextAccessor"></param>
/// <param name="logger"></param>
public AccessTokenManagementService(
IUserTokenStore userTokenStore,
ISystemClock clock,
IOptions<AccessTokenManagementOptions> options,
ITokenEndpointService tokenEndpointService,
IClientAccessTokenCache clientAccessTokenCache,
IHttpContextAccessor httpContextAccessor,
ILogger<AccessTokenManagementService> logger)
{
_userTokenStore = userTokenStore;
_clock = clock;
_options = options.Value;
_tokenEndpointService = tokenEndpointService;
_clientAccessTokenCache = clientAccessTokenCache;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
/// <inheritdoc/>
public async Task<string> GetClientAccessTokenAsync(string clientName = AccessTokenManagementDefaults.DefaultTokenClientName, bool forceRenewal = false)
{
if (forceRenewal == false)
{
var item = await _clientAccessTokenCache.GetAsync(clientName);
if (item != null)
{
return item.AccessToken;
}
}
try
{
return await _clientTokenRequestDictionary.GetOrAdd(clientName, (string _) =>
{
return new Lazy<Task<string>>(async () =>
{
var response = await _tokenEndpointService.RequestClientAccessToken(clientName);
if (response.IsError)
{
_logger.LogError("Error requesting access token for client {clientName}. Error = {error}", clientName, response.Error);
return null;
}
await _clientAccessTokenCache.SetAsync(clientName, response.AccessToken, response.ExpiresIn);
return response.AccessToken;
});
}).Value;
}
finally
{
_clientTokenRequestDictionary.TryRemove(clientName, out _);
}
}
/// <inheritdoc/>
public Task DeleteClientAccessTokenAsync(string clientName = AccessTokenManagementDefaults.DefaultTokenClientName)
{
return _clientAccessTokenCache.DeleteAsync(clientName);
}
/// <inheritdoc/>
public async Task<string> GetUserAccessTokenAsync(bool forceRenewal = false)
{
var user = _httpContextAccessor.HttpContext.User;
var userName = user.FindFirst(JwtClaimTypes.Name)?.Value ?? user.FindFirst(JwtClaimTypes.Subject)?.Value ?? "unknown";
var userToken = await _userTokenStore.GetTokenAsync(_httpContextAccessor.HttpContext.User);
var dtRefresh = userToken.Expiration.Subtract(_options.User.RefreshBeforeExpiration);
if ((dtRefresh < _clock.UtcNow) || forceRenewal == true)
{
_logger.LogDebug("Token for user {user} needs refreshing.", userName);
try
{
return await _userRefreshDictionary.GetOrAdd(userToken.RefreshToken, (string refreshToken) =>
{
return new Lazy<Task<string>>(async () =>
{
var refreshed = await RefreshUserAccessTokenAsync();
return refreshed.AccessToken;
});
}).Value;
}
finally
{
_userRefreshDictionary.TryRemove(userToken.RefreshToken, out _);
}
}
return userToken.AccessToken;
}
/// <inheritdoc/>
public async Task RevokeRefreshTokenAsync()
{
var userToken = await _userTokenStore.GetTokenAsync(_httpContextAccessor.HttpContext.User);
if (!string.IsNullOrEmpty(userToken.RefreshToken))
{
var response = await _tokenEndpointService.RevokeRefreshTokenAsync(userToken.RefreshToken);
if (response.IsError)
{
_logger.LogError("Error revoking refresh token. Error = {error}", response.Error);
}
}
}
internal async Task<TokenResponse> RefreshUserAccessTokenAsync()
{
var userToken = await _userTokenStore.GetTokenAsync(_httpContextAccessor.HttpContext.User);
var response = await _tokenEndpointService.RefreshUserAccessTokenAsync(userToken.RefreshToken);
if (!response.IsError)
{
await _userTokenStore.StoreTokenAsync(_httpContextAccessor.HttpContext.User, response.AccessToken, response.ExpiresIn, response.RefreshToken);
}
else
{
_logger.LogError("Error refreshing access token. Error = {error}", response.Error);
}
return response;
}
}
}