@@ -53,17 +53,11 @@ public static IServiceCollection AddProtectedWebApi(
53
53
configuration . Bind ( configSectionName , options ) ;
54
54
55
55
// This is an Microsoft identity platform Web API
56
- var authority = options . Authority . Trim ( ) . TrimEnd ( '/' ) ;
57
- if ( ! authority . EndsWith ( "v2.0" ) )
58
- authority += "/v2.0" ;
59
- options . Authority = authority ;
56
+ EnsureAuthorityIsV2_0 ( options ) ;
60
57
61
- // The valid audience could be given as Client Id or as Uri. If it does not start with 'api://', this variant is added to the list of valid audiences.
62
- var validAudiences = new List < string > { options . Audience } ;
63
- if ( ! options . Audience . StartsWith ( "api://" , StringComparison . OrdinalIgnoreCase ) )
64
- validAudiences . Add ( $ "api://{ options . Audience } ") ;
65
-
66
- options . TokenValidationParameters . ValidAudiences = validAudiences ;
58
+ // The valid audience could be given as Client Id or as Uri.
59
+ // If it does not start with 'api://', this variant is added to the list of valid audiences.
60
+ EnsureValidAudiencesContainsApiGuidIfGuidProvided ( options ) ;
67
61
68
62
// Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
69
63
// we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens)
@@ -80,17 +74,17 @@ public static IServiceCollection AddProtectedWebApi(
80
74
options . Events = new JwtBearerEvents ( ) ;
81
75
82
76
options . Events . OnTokenValidated = async context =>
83
- {
84
- // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
85
- if ( ! context . Principal . Claims . Any ( x => x . Type == ClaimConstants . Scope )
86
- && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Scp )
87
- && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Roles ) )
88
- {
89
- throw new UnauthorizedAccessException ( "Neither scope or roles claim was found in the bearer token." ) ;
90
- }
91
-
92
- await Task . FromResult ( 0 ) ;
93
- } ;
77
+ {
78
+ // This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
79
+ if ( ! context . Principal . Claims . Any ( x => x . Type == ClaimConstants . Scope )
80
+ && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Scp )
81
+ && ! context . Principal . Claims . Any ( y => y . Type == ClaimConstants . Roles ) )
82
+ {
83
+ throw new UnauthorizedAccessException ( "Neither scope or roles claim was found in the bearer token." ) ;
84
+ }
85
+
86
+ await Task . FromResult ( 0 ) ;
87
+ } ;
94
88
95
89
if ( subscribeToJwtBearerMiddlewareDiagnosticsEvents )
96
90
{
@@ -130,5 +124,37 @@ public static IServiceCollection AddProtectedApiCallsWebApis(
130
124
131
125
return services ;
132
126
}
127
+
128
+ /// <summary>
129
+ /// Ensures that the authority is a v2.0 authority
130
+ /// </summary>
131
+ /// <param name="options">Jwt bearer options read from the config file
132
+ /// or set by the developper, for which we want to ensure the authority
133
+ /// is a v2.0 authority</param>
134
+ internal static void EnsureAuthorityIsV2_0 ( JwtBearerOptions options )
135
+ {
136
+ var authority = options . Authority . Trim ( ) . TrimEnd ( '/' ) ;
137
+ if ( ! authority . EndsWith ( "v2.0" ) )
138
+ authority += "/v2.0" ;
139
+ options . Authority = authority ;
140
+ }
141
+
142
+
143
+ /// <summary>
144
+ /// Ensure that if the audience is a GUID, api://{audience} is also added
145
+ /// as a valid audience (this is the default App ID URL in the app registration
146
+ /// portal)
147
+ /// </summary>
148
+ /// <param name="options">Jwt bearer options for which to ensure that
149
+ /// api://GUID is a valid audience</param>
150
+ internal static void EnsureValidAudiencesContainsApiGuidIfGuidProvided ( JwtBearerOptions options )
151
+ {
152
+ var validAudiences = new List < string > { options . Audience } ;
153
+ if ( ! options . Audience . StartsWith ( "api://" , StringComparison . OrdinalIgnoreCase )
154
+ && Guid . TryParse ( options . Audience , out _ ) )
155
+ validAudiences . Add ( $ "api://{ options . Audience } ") ;
156
+
157
+ options . TokenValidationParameters . ValidAudiences = validAudiences ;
158
+ }
133
159
}
134
160
}
0 commit comments