-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathConfigureOpenIdConnectOptions.cs
More file actions
193 lines (160 loc) · 7.42 KB
/
ConfigureOpenIdConnectOptions.cs
File metadata and controls
193 lines (160 loc) · 7.42 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Copyright (c) Duende Software. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using Duende.AccessTokenManagement.DPoP;
using Duende.IdentityModel;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Duende.AccessTokenManagement.OpenIdConnect.Internal;
/// <summary>
/// Configures OpenIdConnectOptions for user token management
/// </summary>
internal class ConfigureOpenIdConnectOptions(
IDPoPNonceStore dPoPNonceStore,
IDPoPProofService dPoPProofService,
IHttpContextAccessor httpContextAccessor,
IOptions<UserTokenManagementOptions> userAccessTokenManagementOptions,
IAuthenticationSchemeProvider schemeProvider,
IClientAssertionService clientAssertionService,
ILoggerFactory loggerFactory) : IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly Scheme _configScheme = GetConfigScheme(userAccessTokenManagementOptions.Value, schemeProvider);
private ClientCredentialsClientName ClientName => _configScheme.ToClientName();
private static Scheme GetConfigScheme(UserTokenManagementOptions options, IAuthenticationSchemeProvider schemeProvider)
{
var scheme = options.ChallengeScheme;
if (scheme != null)
{
return scheme.Value;
}
var defaultScheme = schemeProvider.GetDefaultChallengeSchemeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
var schemeName = defaultScheme?.Name ?? throw new InvalidOperationException(
"No OpenID Connect authentication scheme configured for getting client configuration. Either set the scheme name explicitly or set the default challenge scheme");
return Scheme.Parse(schemeName);
}
/// <inheritdoc/>
public void Configure(OpenIdConnectOptions options)
{
}
/// <inheritdoc/>
public void Configure(string? name, OpenIdConnectOptions options)
{
if (_configScheme.ToString() == name)
{
// add the event handling to enable DPoP for this OIDC client
options.Events.OnRedirectToIdentityProvider = CreateCallback(options.Events.OnRedirectToIdentityProvider);
options.Events.OnAuthorizationCodeReceived = CreateCallback(options.Events.OnAuthorizationCodeReceived);
options.Events.OnTokenValidated = CreateCallback(options.Events.OnTokenValidated);
options.Events.OnPushAuthorization = CreateCallback(options.Events.OnPushAuthorization);
options.BackchannelHttpHandler = new AuthorizationServerDPoPHandler(dPoPProofService, dPoPNonceStore, httpContextAccessor, loggerFactory)
{
InnerHandler = options.BackchannelHttpHandler ?? new HttpClientHandler()
};
}
}
private Func<RedirectContext, Task> CreateCallback(Func<RedirectContext, Task> inner)
{
async Task Callback(RedirectContext context)
{
await inner.Invoke(context);
var dPoPKeyStore = context.HttpContext.RequestServices.GetRequiredService<IDPoPKeyStore>();
var key = await dPoPKeyStore.GetKeyAsync(ClientName);
if (key == null)
{
return;
}
var jkt = dPoPProofService.GetProofKeyThumbprint(key.Value);
// checking for null allows for opt-out from using DPoP
if (jkt == null)
{
return;
}
context.Properties.SetProofKey(key.Value);
// pass jkt to authorize endpoint
context.ProtocolMessage.Parameters[OidcConstants.AuthorizeRequest.DPoPKeyThumbprint] =
jkt.ToString();
// we store the proof key here to associate it with the
// authorization code that will be returned. Ultimately we
// use this to provide proof of possession during code
// exchange.
}
return Callback;
}
private Func<AuthorizationCodeReceivedContext, Task> CreateCallback(Func<AuthorizationCodeReceivedContext, Task> inner)
{
async Task Callback(AuthorizationCodeReceivedContext context)
{
await inner.Invoke(context);
// get key from storage
var jwk = context.Properties?.GetProofKey();
if (jwk != null)
{
// set it so the OIDC message handler can find it
context.HttpContext.SetCodeExchangeDPoPKey(jwk.Value);
}
// Automatically send client assertion during code exchange if a service is registered
var assertion = await clientAssertionService
.GetClientAssertionAsync(ClientName, ct: context.HttpContext.RequestAborted)
.ConfigureAwait(false);
if (assertion != null && context.TokenEndpointRequest != null)
{
context.TokenEndpointRequest.ClientAssertionType = assertion.Type;
context.TokenEndpointRequest.ClientAssertion = assertion.Value;
}
}
return Callback;
}
private Func<TokenValidatedContext, Task> CreateCallback(Func<TokenValidatedContext, Task> inner)
{
async Task Callback(TokenValidatedContext context)
{
await inner.Invoke(context);
// TODO: we don't have a good approach for this right now, since the IUserTokenStore
// just assumes that the session management has been populated with all the token values
//
// get key from storage
//var jwk = context.Properties?.GetProofKey();
//if (jwk != null)
//{
// // clear this so the properties are not bloated
// // and defer to the host and/or IUserTokenStore implementation to decide where the key is kept
// //context.Properties!.RemoveProofKey();
//}
}
return Callback;
}
private Func<PushedAuthorizationContext, Task> CreateCallback(Func<PushedAuthorizationContext, Task> inner)
{
async Task Callback(PushedAuthorizationContext context)
{
await inner.Invoke(context);
// --- DPoP thumbprint ---
var dPoPKeyStore = context.HttpContext.RequestServices.GetRequiredService<IDPoPKeyStore>();
var key = await dPoPKeyStore.GetKeyAsync(ClientName);
if (key != null)
{
var jkt = dPoPProofService.GetProofKeyThumbprint(key.Value);
if (jkt != null)
{
context.Properties.SetProofKey(key.Value);
context.ProtocolMessage.Parameters[OidcConstants.AuthorizeRequest.DPoPKeyThumbprint] =
jkt.ToString();
}
}
// --- Client assertion ---
var assertion = await clientAssertionService
.GetClientAssertionAsync(ClientName, ct: context.HttpContext.RequestAborted)
.ConfigureAwait(false);
if (assertion != null)
{
context.ProtocolMessage.ClientAssertionType = assertion.Type;
context.ProtocolMessage.ClientAssertion = assertion.Value;
context.HandleClientAuthentication();
}
}
return Callback;
}
}