1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
3
4
+ using Microsoft . AspNetCore . Authentication ;
4
5
using Microsoft . AspNetCore . Authentication . JwtBearer ;
5
6
using Microsoft . AspNetCore . Authentication . OpenIdConnect ;
6
7
using Microsoft . Extensions . Configuration ;
@@ -59,20 +60,109 @@ public static IServiceCollection AddProtectedWebApi(
59
60
bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false )
60
61
{
61
62
services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
62
- . AddJwtBearer ( ( options => configuration . Bind ( configSectionName , options ) ) ) ;
63
-
64
- services . Configure < MicrosoftIdentityOptions > ( options => configuration . Bind ( configSectionName , options ) ) ;
63
+ . AddProtectedWebApi (
64
+ configSectionName ,
65
+ configuration ,
66
+ options => configuration . Bind ( configSectionName , options ) ,
67
+ tokenDecryptionCertificate ,
68
+ subscribeToJwtBearerMiddlewareDiagnosticsEvents ) ;
65
69
66
- services . AddHttpContextAccessor ( ) ;
70
+ return services ;
71
+ }
72
+
73
+ /// <summary>
74
+ /// Protects the Web API with Microsoft identity platform (formerly Azure AD v2.0)
75
+ /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
76
+ /// </summary>
77
+ /// <param name="builder">AuthenticationBuilder to which to add this configuration</param>
78
+ /// <param name="configuration">The Configuration object</param>
79
+ /// <param name="configureOptions">An action to configure JwtBearerOptions</param>
80
+ /// <param name="tokenDecryptionCertificate">Token decryption certificate</param>
81
+ /// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
82
+ /// Set to true if you want to debug, or just understand the JwtBearer events.
83
+ /// </param>
84
+ /// <returns></returns>
85
+ public static AuthenticationBuilder AddProtectedWebApi (
86
+ this AuthenticationBuilder builder ,
87
+ IConfiguration configuration ,
88
+ Action < JwtBearerOptions > configureOptions ,
89
+ X509Certificate2 tokenDecryptionCertificate = null ,
90
+ bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false )
91
+ {
92
+ return AddProtectedWebApi (
93
+ builder ,
94
+ "AzureAd" ,
95
+ configuration ,
96
+ JwtBearerDefaults . AuthenticationScheme ,
97
+ configureOptions ,
98
+ tokenDecryptionCertificate ,
99
+ subscribeToJwtBearerMiddlewareDiagnosticsEvents ) ;
100
+ }
101
+
102
+ /// <summary>
103
+ /// Protects the Web API with Microsoft identity platform (formerly Azure AD v2.0)
104
+ /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
105
+ /// </summary>
106
+ /// <param name="builder">AuthenticationBuilder to which to add this configuration</param>
107
+ /// <param name="configSectionName">The configuration section with the necessary settings to initialize authentication options</param>
108
+ /// <param name="configuration">The Configuration object</param>
109
+ /// <param name="configureOptions">An action to configure JwtBearerOptions</param>
110
+ /// <param name="tokenDecryptionCertificate">Token decryption certificate</param>
111
+ /// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
112
+ /// Set to true if you want to debug, or just understand the JwtBearer events.
113
+ /// </param>
114
+ /// <returns></returns>
115
+ public static AuthenticationBuilder AddProtectedWebApi (
116
+ this AuthenticationBuilder builder ,
117
+ string configSectionName ,
118
+ IConfiguration configuration ,
119
+ Action < JwtBearerOptions > configureOptions ,
120
+ X509Certificate2 tokenDecryptionCertificate = null ,
121
+ bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false )
122
+ {
123
+ return AddProtectedWebApi (
124
+ builder ,
125
+ configSectionName ,
126
+ configuration ,
127
+ JwtBearerDefaults . AuthenticationScheme ,
128
+ configureOptions ,
129
+ tokenDecryptionCertificate ,
130
+ subscribeToJwtBearerMiddlewareDiagnosticsEvents ) ;
131
+ }
132
+
133
+ /// <summary>
134
+ /// Protects the Web API with Microsoft identity platform (formerly Azure AD v2.0)
135
+ /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options.
136
+ /// </summary>
137
+ /// <param name="builder">AuthenticationBuilder to which to add this configuration</param>
138
+ /// <param name="configSectionName">The configuration section with the necessary settings to initialize authentication options</param>
139
+ /// <param name="configuration">The Configuration object</param>
140
+ /// <param name="jwtBearerScheme">The JwtBearer scheme name to be used. By default it uses "Bearer"</param>
141
+ /// <param name="configureOptions">An action to configure JwtBearerOptions</param>
142
+ /// <param name="tokenDecryptionCertificate">Token decryption certificate</param>
143
+ /// <param name="subscribeToJwtBearerMiddlewareDiagnosticsEvents">
144
+ /// Set to true if you want to debug, or just understand the JwtBearer events.
145
+ /// </param>
146
+ /// <returns></returns>
147
+ public static AuthenticationBuilder AddProtectedWebApi (
148
+ this AuthenticationBuilder builder ,
149
+ string configSectionName ,
150
+ IConfiguration configuration ,
151
+ string jwtBearerScheme ,
152
+ Action < JwtBearerOptions > configureOptions ,
153
+ X509Certificate2 tokenDecryptionCertificate = null ,
154
+ bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false )
155
+ {
156
+ builder . Services . Configure ( jwtBearerScheme , configureOptions ) ;
157
+ builder . Services . Configure < MicrosoftIdentityOptions > ( options => configuration . Bind ( configSectionName , options ) ) ;
158
+
159
+ builder . Services . AddHttpContextAccessor ( ) ;
67
160
68
161
// Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0).
69
- services . Configure < JwtBearerOptions > ( JwtBearerDefaults . AuthenticationScheme , options =>
162
+ builder . AddJwtBearer ( jwtBearerScheme , options =>
70
163
{
71
164
var microsoftIdentityOptions = configuration . GetSection ( configSectionName ) . Get < MicrosoftIdentityOptions > ( ) ;
72
165
73
- // Reinitialize the options as this has changed to JwtBearerOptions to pick configuration values for attributes unique to JwtBearerOptions
74
- configuration . Bind ( configSectionName , options ) ;
75
-
76
166
if ( string . IsNullOrWhiteSpace ( options . Authority ) )
77
167
options . Authority = AuthorityHelpers . BuildAuthority ( microsoftIdentityOptions ) ;
78
168
@@ -82,48 +172,52 @@ public static IServiceCollection AddProtectedWebApi(
82
172
// The valid audiences are both the Client ID (options.Audience) and api://{ClientID}
83
173
options . TokenValidationParameters . ValidAudiences = new string [ ]
84
174
{
85
- // If the developer doesnt set the Audience on JwtBearerOptions, use ClientId from MicrosoftIdentityOptions
175
+ // If the developer doesn't set the Audience on JwtBearerOptions, use ClientId from MicrosoftIdentityOptions
86
176
options . Audience , $ "api://{ options . Audience ?? microsoftIdentityOptions . ClientId } "
87
177
} ;
88
178
89
- // Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
90
- // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
91
- options . TokenValidationParameters . IssuerValidator = AadIssuerValidator . GetIssuerValidator ( options . Authority ) . Validate ;
179
+ // If the developer registered an IssuerValidator, do not overwrite it
180
+ if ( options . TokenValidationParameters . IssuerValidator == null )
181
+ {
182
+ // Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
183
+ // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
184
+ options . TokenValidationParameters . IssuerValidator = AadIssuerValidator . GetIssuerValidator ( options . Authority ) . Validate ;
185
+ }
92
186
93
187
// If you provide a token decryption certificate, it will be used to decrypt the token
94
188
if ( tokenDecryptionCertificate != null )
95
189
{
96
190
options . TokenValidationParameters . TokenDecryptionKey = new X509SecurityKey ( tokenDecryptionCertificate ) ;
97
191
}
98
192
193
+ if ( options . Events == null )
194
+ options . Events = new JwtBearerEvents ( ) ;
195
+
99
196
// When an access token for our own Web API is validated, we add it to MSAL.NET's cache so that it can
100
197
// be used from the controllers.
101
- options . Events = new JwtBearerEvents ( ) ;
102
-
198
+ var tokenValidatedHandler = options . Events . OnTokenValidated ;
103
199
options . Events . OnTokenValidated = async context =>
104
- {
105
- // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
106
- if ( ! context . Principal . Claims . Any ( x => x . Type == ClaimConstants . Scope )
107
- && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Scp )
108
- && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Roles ) )
109
- {
110
- throw new UnauthorizedAccessException ( "Neither scope or roles claim was found in the bearer token." ) ;
111
- }
112
-
113
- await Task . FromResult ( 0 ) ;
114
- } ;
200
+ {
201
+ // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
202
+ if ( ! context . Principal . Claims . Any ( x => x . Type == ClaimConstants . Scope )
203
+ && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Scp )
204
+ && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Roles ) )
205
+ {
206
+ throw new UnauthorizedAccessException ( "Neither scope or roles claim was found in the bearer token." ) ;
207
+ }
208
+
209
+ await tokenValidatedHandler ( context ) . ConfigureAwait ( false ) ;
210
+ } ;
115
211
116
212
if ( subscribeToJwtBearerMiddlewareDiagnosticsEvents )
117
213
{
118
214
options . Events = JwtBearerMiddlewareDiagnostics . Subscribe ( options . Events ) ;
119
215
}
120
216
} ) ;
121
217
122
- return services ;
218
+ return builder ;
123
219
}
124
220
125
- // TODO: pass an option with a section name to bind the options ? or a delegate?
126
-
127
221
/// <summary>
128
222
/// Protects the Web API with Microsoft identity platform (formerly Azure AD v2.0)
129
223
/// This supposes that the configuration files have a section named configSectionName (typically "AzureAD")
@@ -141,8 +235,7 @@ public static IServiceCollection AddProtectedWebApiCallsProtectedWebApi(
141
235
services . AddHttpContextAccessor ( ) ;
142
236
services . Configure < ConfidentialClientApplicationOptions > ( options => configuration . Bind ( configSectionName , options ) ) ;
143
237
services . Configure < MicrosoftIdentityOptions > ( options => configuration . Bind ( configSectionName , options ) ) ;
144
-
145
- // TODO: Pass scheme as parameter?
238
+
146
239
services . Configure < JwtBearerOptions > ( jwtBearerScheme , options =>
147
240
{
148
241
options . Events . OnTokenValidated = async context =>
0 commit comments