-
Notifications
You must be signed in to change notification settings - Fork 253
Expand file tree
/
Copy pathMicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs
More file actions
257 lines (230 loc) · 14.1 KB
/
MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs
File metadata and controls
257 lines (230 loc) · 14.1 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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Web.Resource;
using Microsoft.IdentityModel.Tokens;
namespace Microsoft.Identity.Web
{
/// <summary>
/// Extensions for <see cref="AuthenticationBuilder"/> for startup initialization of web APIs.
/// </summary>
public static class MicrosoftIdentityWebApiAuthenticationBuilderExtensions
{
/// <summary>
/// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0).
/// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/> to which to add this configuration.</param>
/// <param name="configuration">The configuration instance.</param>
/// <param name="configSectionName">The configuration section with the necessary settings to initialize authentication options.</param>
/// <param name="jwtBearerScheme">The JWT bearer scheme name to be used. By default it uses "Bearer".</param>
/// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
/// Set to true if you want to debug, or just understand the JWT bearer events.
/// </param>
/// <returns>The authentication builder to chain.</returns>
public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApi(
this AuthenticationBuilder builder,
IConfiguration configuration,
string configSectionName = Constants.AzureAd,
string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme,
bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false)
{
_ = Throws.IfNull(configuration);
_ = Throws.IfNull(configSectionName);
IConfigurationSection configurationSection = configuration.GetSection(configSectionName);
return builder.AddMicrosoftIdentityWebApi(
configurationSection,
jwtBearerScheme,
subscribeToJwtBearerMiddlewareDiagnosticsEvents);
}
/// <summary>
/// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0).
/// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/> to which to add this configuration.</param>
/// <param name="configurationSection">The configuration second from which to fill-in the options.</param>
/// <param name="jwtBearerScheme">The JWT bearer scheme name to be used. By default it uses "Bearer".</param>
/// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
/// Set to true if you want to debug, or just understand the JWT bearer events.
/// </param>
/// <returns>The authentication builder to chain.</returns>
public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApi(
this AuthenticationBuilder builder,
IConfigurationSection configurationSection,
string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme,
bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false)
{
_ = Throws.IfNull(configurationSection);
_ = Throws.IfNull(builder);
AddMicrosoftIdentityWebApiImplementation(
builder,
options => configurationSection.Bind(options),
options => configurationSection.Bind(options),
jwtBearerScheme,
subscribeToJwtBearerMiddlewareDiagnosticsEvents);
return new MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration(
builder.Services,
jwtBearerScheme,
options => configurationSection.Bind(options),
options => configurationSection.Bind(options),
configurationSection);
}
/// <summary>
/// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0).
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/> to which to add this configuration.</param>
/// <param name="configureJwtBearerOptions">The action to configure <see cref="JwtBearerOptions"/>.</param>
/// <param name="configureMicrosoftIdentityOptions">The action to configure the <see cref="MicrosoftIdentityOptions"/>.</param>
/// <param name="jwtBearerScheme">The JWT bearer scheme name to be used. By default it uses "Bearer".</param>
/// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
/// Set to true if you want to debug, or just understand the JWT bearer events.</param>
/// <returns>The authentication builder to chain.</returns>
public static MicrosoftIdentityWebApiAuthenticationBuilder AddMicrosoftIdentityWebApi(
this AuthenticationBuilder builder,
Action<JwtBearerOptions> configureJwtBearerOptions,
Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme,
bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false)
{
_ = Throws.IfNull(builder);
_ = Throws.IfNull(configureJwtBearerOptions);
_ = Throws.IfNull(configureMicrosoftIdentityOptions);
AddMicrosoftIdentityWebApiImplementation(
builder,
configureJwtBearerOptions,
configureMicrosoftIdentityOptions,
jwtBearerScheme,
subscribeToJwtBearerMiddlewareDiagnosticsEvents);
return new MicrosoftIdentityWebApiAuthenticationBuilder(
builder.Services,
jwtBearerScheme,
configureJwtBearerOptions,
configureMicrosoftIdentityOptions,
null);
}
private static void AddMicrosoftIdentityWebApiImplementation(
AuthenticationBuilder builder,
Action<JwtBearerOptions> configureJwtBearerOptions,
Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
string jwtBearerScheme,
bool subscribeToJwtBearerMiddlewareDiagnosticsEvents)
{
builder.AddJwtBearer(jwtBearerScheme, configureJwtBearerOptions);
builder.Services.Configure(jwtBearerScheme, configureMicrosoftIdentityOptions);
builder.Services.AddSingleton<IMergedOptionsStore, MergedOptionsStore>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddHttpClient();
builder.Services.TryAddSingleton<MicrosoftIdentityIssuerValidatorFactory>();
builder.Services.AddDenyGuestsAuthorization();
builder.Services.AddRequiredScopeAuthorization();
builder.Services.AddRequiredScopeOrAppPermissionAuthorization();
builder.Services.AddOptions<AadIssuerValidatorOptions>();
if (builder.Services.FirstOrDefault(s => s.ImplementationType == typeof(MicrosoftIdentityOptionsMerger)) == null)
{
builder.Services.TryAddSingleton<IPostConfigureOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsMerger>();
}
if (builder.Services.FirstOrDefault(s => s.ImplementationType == typeof(JwtBearerOptionsMerger)) == null)
{
builder.Services.TryAddSingleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerOptionsMerger>();
}
if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
{
builder.Services.AddTransient<IJwtBearerMiddlewareDiagnostics, JwtBearerMiddlewareDiagnostics>();
}
// Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0).
builder.Services.AddOptions<JwtBearerOptions>(jwtBearerScheme)
.Configure<IServiceProvider, IMergedOptionsStore, IOptionsMonitor<MicrosoftIdentityOptions>>((
options,
serviceProvider,
mergedOptionsMonitor,
msIdOptionsMonitor) =>
{
MicrosoftIdentityBaseAuthenticationBuilder.SetIdentityModelLogger(serviceProvider);
msIdOptionsMonitor.Get(jwtBearerScheme); // needed for firing the PostConfigure.
MergedOptions mergedOptions = mergedOptionsMonitor.Get(jwtBearerScheme);
MergedOptionsValidation.Validate(mergedOptions);
if (string.IsNullOrWhiteSpace(options.Authority))
{
options.Authority = AuthorityHelpers.BuildAuthority(mergedOptions);
}
// This is a Microsoft identity platform web API
options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority);
if (options.TokenValidationParameters.AudienceValidator == null
&& options.TokenValidationParameters.ValidAudience == null
&& options.TokenValidationParameters.ValidAudiences == null)
{
RegisterValidAudience registerAudience = new RegisterValidAudience();
registerAudience.RegisterAudienceValidation(
options.TokenValidationParameters,
mergedOptions);
}
// If the developer registered an IssuerValidator, do not overwrite it
if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null)
{
// Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
// we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory =
serviceProvider.GetRequiredService<MicrosoftIdentityIssuerValidatorFactory>();
options.TokenValidationParameters.IssuerValidator =
microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate;
}
// If you provide a token decryption certificate, it will be used to decrypt the token
// TODO use the credential loader
if (mergedOptions.TokenDecryptionCredentials != null)
{
DefaultCertificateLoader.UserAssignedManagedIdentityClientId = mergedOptions.UserAssignedManagedIdentityClientId;
IEnumerable<X509Certificate2?> certificates = DefaultCertificateLoader.LoadAllCertificates(mergedOptions.TokenDecryptionCredentials.OfType<CertificateDescription>());
IEnumerable<X509SecurityKey> keys = certificates.Select(c => new X509SecurityKey(c));
options.TokenValidationParameters.TokenDecryptionKeys = keys;
}
if (options.Events == null)
{
options.Events = new JwtBearerEvents();
}
// When an access token for our own web API is validated, we add it to MSAL.NET's cache so that it can
// be used from the controllers.
if (!mergedOptions.AllowWebApiToBeAuthorizedByACL)
{
ChainOnTokenValidatedEventForClaimsValidation(options.Events, jwtBearerScheme);
}
if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
{
var diagnostics = serviceProvider.GetRequiredService<IJwtBearerMiddlewareDiagnostics>();
diagnostics.Subscribe(options.Events);
}
});
}
/// <summary>
/// In order to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned, a token that
/// has neither Roles nor Scopes claims should be rejected. To enforce that rule, add an event handler to the beginning of the
/// <see cref="JwtBearerEvents.OnTokenValidated"/> handler chain that rejects tokens that don't meet the rules.
/// </summary>
/// <param name="events">The <see cref="JwtBearerEvents"/> object to modify.</param>
/// <param name="jwtBearerScheme">The JWT bearer scheme name to be used. By default it uses "Bearer".</param>
internal static void ChainOnTokenValidatedEventForClaimsValidation(JwtBearerEvents events, string jwtBearerScheme)
{
var tokenValidatedHandler = events.OnTokenValidated;
events.OnTokenValidated = async context =>
{
if (!context!.Principal!.Claims.Any(x => x.Type == ClaimConstants.Scope
|| x.Type == ClaimConstants.Scp
|| x.Type == ClaimConstants.Roles
|| x.Type == ClaimConstants.Role))
{
context.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.NeitherScopeOrRolesClaimFoundInToken, jwtBearerScheme));
}
await tokenValidatedHandler(context).ConfigureAwait(false);
};
}
}
}