@@ -24,119 +24,119 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
25
25
using Microsoft . Identity . Web . InstanceDiscovery ;
26
26
using Microsoft . IdentityModel . JsonWebTokens ;
27
- using Microsoft . IdentityModel . Protocols ;
28
- using Microsoft . IdentityModel . Tokens ;
27
+ using Microsoft . IdentityModel . Protocols ;
28
+ using Microsoft . IdentityModel . Tokens ;
29
29
using System ;
30
30
using System . Collections . Concurrent ;
31
- using System . Collections . Generic ;
32
- using System . IdentityModel . Tokens . Jwt ;
33
- using System . Linq ;
34
-
35
- namespace Microsoft . Identity . Web . Resource
36
- {
37
- /// <summary>
38
- /// Generic class that validates token issuer from the provided Azure AD authority. Use the <see cref="AadIssuerValidatorFactory"/> to create instaces of this class.
39
- /// </summary>
40
- public class AadIssuerValidator
41
- {
31
+ using System . Collections . Generic ;
32
+ using System . IdentityModel . Tokens . Jwt ;
33
+ using System . Linq ;
34
+
35
+ namespace Microsoft . Identity . Web . Resource
36
+ {
37
+ /// <summary>
38
+ /// Generic class that validates token issuer from the provided Azure AD authority. Use the <see cref="AadIssuerValidatorFactory"/> to create instaces of this class.
39
+ /// </summary>
40
+ public class AadIssuerValidator
41
+ {
42
42
private const string AzureADIssuerMetadataUrl = "https://login.microsoftonline.com/common/discovery/instance?authorization_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/authorize&api-version=1.1" ;
43
43
private const string FallbackAuthority = "https://login.microsoftonline.com/" ;
44
44
45
45
// TODO: separate AadIssuerValidator creation logic from the validation logic in order to unit test it
46
46
private static readonly IDictionary < string , AadIssuerValidator > s_issuerValidators = new ConcurrentDictionary < string , AadIssuerValidator > ( ) ;
47
47
48
- private static readonly ConfigurationManager < IssuerMetadata > s_configManager = new ConfigurationManager < IssuerMetadata > ( AzureADIssuerMetadataUrl , new IssuerConfigurationRetriever ( ) ) ;
49
-
50
- /// <summary>
51
- /// A list of all Issuers across the various Azure AD instances
52
- /// </summary>
48
+ private static readonly ConfigurationManager < IssuerMetadata > s_configManager = new ConfigurationManager < IssuerMetadata > ( AzureADIssuerMetadataUrl , new IssuerConfigurationRetriever ( ) ) ;
49
+
50
+ /// <summary>
51
+ /// A list of all Issuers across the various Azure AD instances
52
+ /// </summary>
53
53
private readonly ISet < string > _issuerAliases ;
54
-
55
- internal /* internal for test */ AadIssuerValidator ( IEnumerable < string > aliases )
56
- {
57
- _issuerAliases = new HashSet < string > ( aliases , StringComparer . OrdinalIgnoreCase ) ;
54
+
55
+ internal /* internal for test */ AadIssuerValidator ( IEnumerable < string > aliases )
56
+ {
57
+ _issuerAliases = new HashSet < string > ( aliases , StringComparer . OrdinalIgnoreCase ) ;
58
58
}
59
59
60
- /// <summary>
61
- /// Gets a <see cref="AadIssuerValidator"/> for an authority.
62
- /// </summary>
63
- /// <param name="aadAuthority">The authority to create the validator for, e.g. https://login.microsoftonline.com/ </param>
64
- /// <returns>A <see cref="AadIssuerValidator"/> for the aadAuthority.</returns>
65
- /// <exception cref="ArgumentNullException">if <paramref name="aadAuthority"/> is null or empty.</exception>
66
- public static AadIssuerValidator GetIssuerValidator ( string aadAuthority )
67
- {
68
- if ( string . IsNullOrEmpty ( aadAuthority ) )
69
- throw new ArgumentNullException ( nameof ( aadAuthority ) ) ;
70
-
71
- if ( s_issuerValidators . TryGetValue ( aadAuthority , out AadIssuerValidator aadIssuerValidator ) )
72
- {
73
- return aadIssuerValidator ;
74
- }
75
- else
76
- {
77
- // In the constructor, we hit the Azure AD issuer metadata endpoint and cache the aliases. The data is cached for 24 hrs.
60
+ /// <summary>
61
+ /// Gets a <see cref="AadIssuerValidator"/> for an authority.
62
+ /// </summary>
63
+ /// <param name="aadAuthority">The authority to create the validator for, e.g. https://login.microsoftonline.com/ </param>
64
+ /// <returns>A <see cref="AadIssuerValidator"/> for the aadAuthority.</returns>
65
+ /// <exception cref="ArgumentNullException">if <paramref name="aadAuthority"/> is null or empty.</exception>
66
+ public static AadIssuerValidator GetIssuerValidator ( string aadAuthority )
67
+ {
68
+ if ( string . IsNullOrEmpty ( aadAuthority ) )
69
+ throw new ArgumentNullException ( nameof ( aadAuthority ) ) ;
70
+
71
+ if ( s_issuerValidators . TryGetValue ( aadAuthority , out AadIssuerValidator aadIssuerValidator ) )
72
+ {
73
+ return aadIssuerValidator ;
74
+ }
75
+ else
76
+ {
77
+ // In the constructor, we hit the Azure AD issuer metadata endpoint and cache the aliases. The data is cached for 24 hrs.
78
78
var issuerMetadata = s_configManager . GetConfigurationAsync ( ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
79
79
string authorityHost ;
80
80
try
81
81
{
82
- authorityHost = new Uri ( aadAuthority ) . Authority ;
83
- }
84
- catch
82
+ authorityHost = new Uri ( aadAuthority ) . Authority ;
83
+ }
84
+ catch
85
85
{
86
86
authorityHost = null ;
87
- }
88
-
89
- // Add issuer aliases of the chosen authority
87
+ }
88
+
89
+ // Add issuer aliases of the chosen authority
90
90
string authority = authorityHost ?? new Uri ( FallbackAuthority ) . Host ;
91
91
var aliases = issuerMetadata . Metadata
92
92
. Where ( m => m . Aliases . Any ( a => string . Equals ( a , authority , StringComparison . OrdinalIgnoreCase ) ) )
93
93
. SelectMany ( m => m . Aliases )
94
- . Distinct ( ) ;
95
- s_issuerValidators [ authority ] = new AadIssuerValidator ( aliases ) ;
96
- return s_issuerValidators [ authority ] ;
97
- }
98
- }
99
-
100
- /// <summary>
101
- /// Validate the issuer for multi-tenant applications of various audience (Work and School account, or Work and School accounts +
102
- /// Personal accounts)
103
- /// </summary>
104
- /// <param name="actualIssuer">Issuer to validate (will be tenanted)</param>
105
- /// <param name="securityToken">Received Security Token</param>
106
- /// <param name="validationParameters">Token Validation parameters</param>
107
- /// <remarks>The issuer is considered as valid if it has the same http scheme and authority as the
108
- /// authority from the configuration file, has a tenant Id, and optionally v2.0 (this web api
109
- /// accepts both V1 and V2 tokens).
110
- /// Authority aliasing is also taken into account</remarks>
111
- /// <returns>The <c>issuer</c> if it's valid, or otherwise <c>SecurityTokenInvalidIssuerException</c> is thrown</returns>
112
- /// <exception cref="ArgumentNullException"> if <paramref name="securityToken"/> is null.</exception>
113
- /// <exception cref="ArgumentNullException"> if <paramref name="validationParameters"/> is null.</exception>
114
- /// <exception cref="SecurityTokenInvalidIssuerException">if the issuer </exception>
115
- public string Validate ( string actualIssuer , SecurityToken securityToken , TokenValidationParameters validationParameters )
94
+ . Distinct ( ) ;
95
+ s_issuerValidators [ authority ] = new AadIssuerValidator ( aliases ) ;
96
+ return s_issuerValidators [ authority ] ;
97
+ }
98
+ }
99
+
100
+ /// <summary>
101
+ /// Validate the issuer for multi-tenant applications of various audience (Work and School account, or Work and School accounts +
102
+ /// Personal accounts)
103
+ /// </summary>
104
+ /// <param name="actualIssuer">Issuer to validate (will be tenanted)</param>
105
+ /// <param name="securityToken">Received Security Token</param>
106
+ /// <param name="validationParameters">Token Validation parameters</param>
107
+ /// <remarks>The issuer is considered as valid if it has the same http scheme and authority as the
108
+ /// authority from the configuration file, has a tenant Id, and optionally v2.0 (this web api
109
+ /// accepts both V1 and V2 tokens).
110
+ /// Authority aliasing is also taken into account</remarks>
111
+ /// <returns>The <c>issuer</c> if it's valid, or otherwise <c>SecurityTokenInvalidIssuerException</c> is thrown</returns>
112
+ /// <exception cref="ArgumentNullException"> if <paramref name="securityToken"/> is null.</exception>
113
+ /// <exception cref="ArgumentNullException"> if <paramref name="validationParameters"/> is null.</exception>
114
+ /// <exception cref="SecurityTokenInvalidIssuerException">if the issuer </exception>
115
+ public string Validate ( string actualIssuer , SecurityToken securityToken , TokenValidationParameters validationParameters )
116
116
{
117
- if ( String . IsNullOrEmpty ( actualIssuer ) )
118
- throw new ArgumentNullException ( nameof ( actualIssuer ) ) ;
119
-
120
- if ( securityToken == null )
121
- throw new ArgumentNullException ( nameof ( securityToken ) ) ;
122
-
123
- if ( validationParameters == null )
124
- throw new ArgumentNullException ( nameof ( validationParameters ) ) ;
125
-
126
- string tenantId = GetTenantIdFromToken ( securityToken ) ;
127
- if ( string . IsNullOrWhiteSpace ( tenantId ) )
128
- throw new SecurityTokenInvalidIssuerException ( "Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft Identity Platform." ) ;
129
-
130
- if ( validationParameters . ValidIssuers != null )
131
- foreach ( var validIssuerTemplate in validationParameters . ValidIssuers )
132
- if ( IsValidIssuer ( validIssuerTemplate , tenantId , actualIssuer ) )
133
- return actualIssuer ;
134
-
117
+ if ( String . IsNullOrEmpty ( actualIssuer ) )
118
+ throw new ArgumentNullException ( nameof ( actualIssuer ) ) ;
119
+
120
+ if ( securityToken == null )
121
+ throw new ArgumentNullException ( nameof ( securityToken ) ) ;
122
+
123
+ if ( validationParameters == null )
124
+ throw new ArgumentNullException ( nameof ( validationParameters ) ) ;
125
+
126
+ string tenantId = GetTenantIdFromToken ( securityToken ) ;
127
+ if ( string . IsNullOrWhiteSpace ( tenantId ) )
128
+ throw new SecurityTokenInvalidIssuerException ( "Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft Identity Platform." ) ;
129
+
130
+ if ( validationParameters . ValidIssuers != null )
131
+ foreach ( var validIssuerTemplate in validationParameters . ValidIssuers )
132
+ if ( IsValidIssuer ( validIssuerTemplate , tenantId , actualIssuer ) )
133
+ return actualIssuer ;
134
+
135
135
if ( IsValidIssuer ( validationParameters . ValidIssuer , tenantId , actualIssuer ) )
136
136
return actualIssuer ;
137
137
138
- // If a valid issuer is not found, throw
139
- // brentsch - todo, create a list of all the possible valid issuers in TokenValidationParameters
138
+ // If a valid issuer is not found, throw
139
+ // brentsch - todo, create a list of all the possible valid issuers in TokenValidationParameters
140
140
throw new SecurityTokenInvalidIssuerException ( $ "Issuer: '{ actualIssuer } ', does not match any of the valid issuers provided for this application.") ;
141
141
}
142
142
@@ -165,21 +165,21 @@ private bool IsValidIssuer(string validIssuerTemplate, string tenantId, string a
165
165
}
166
166
167
167
return false ;
168
- }
169
-
168
+ }
169
+
170
170
private static bool IsValidTidInLocalPath ( string tenantId , Uri uri )
171
171
{
172
172
string trimmedLocalPath = uri . LocalPath . Trim ( '/' ) ;
173
173
return trimmedLocalPath == tenantId || trimmedLocalPath == $ "{ tenantId } /v2.0";
174
- }
175
-
176
- /// <summary>Gets the tenant id from a token.</summary>
177
- /// <param name="securityToken">A JWT token.</param>
178
- /// <returns>A string containing tenantId, if found or <see cref="string.Empty"/>.</returns>
179
- /// <remarks>Only <see cref="JwtSecurityToken"/> and <see cref="JsonWebToken"/> are acceptable types.</remarks>
180
- private static string GetTenantIdFromToken ( SecurityToken securityToken )
181
- {
182
- if ( securityToken is JwtSecurityToken jwtSecurityToken )
174
+ }
175
+
176
+ /// <summary>Gets the tenant id from a token.</summary>
177
+ /// <param name="securityToken">A JWT token.</param>
178
+ /// <returns>A string containing tenantId, if found or <see cref="string.Empty"/>.</returns>
179
+ /// <remarks>Only <see cref="JwtSecurityToken"/> and <see cref="JsonWebToken"/> are acceptable types.</remarks>
180
+ private static string GetTenantIdFromToken ( SecurityToken securityToken )
181
+ {
182
+ if ( securityToken is JwtSecurityToken jwtSecurityToken )
183
183
{
184
184
if ( jwtSecurityToken . Payload . TryGetValue ( ClaimConstants . Tid , out object tenantId ) )
185
185
return tenantId as string ;
@@ -191,9 +191,9 @@ private static string GetTenantIdFromToken(SecurityToken securityToken)
191
191
var tid = jsonWebToken . GetPayloadValue < string > ( ClaimConstants . Tid ) ;
192
192
if ( tid != null )
193
193
return tid ;
194
- }
195
-
196
- return string . Empty ;
197
- }
198
- }
194
+ }
195
+
196
+ return string . Empty ;
197
+ }
198
+ }
199
199
}
0 commit comments