@@ -24,119 +24,119 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2424
2525using Microsoft . Identity . Web . InstanceDiscovery ;
2626using Microsoft . IdentityModel . JsonWebTokens ;
27- using Microsoft . IdentityModel . Protocols ;
28- using Microsoft . IdentityModel . Tokens ;
27+ using Microsoft . IdentityModel . Protocols ;
28+ using Microsoft . IdentityModel . Tokens ;
2929using System ;
3030using 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+ {
4242 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" ;
4343 private const string FallbackAuthority = "https://login.microsoftonline.com/" ;
4444
4545 // TODO: separate AadIssuerValidator creation logic from the validation logic in order to unit test it
4646 private static readonly IDictionary < string , AadIssuerValidator > s_issuerValidators = new ConcurrentDictionary < string , AadIssuerValidator > ( ) ;
4747
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>
5353 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 ) ;
5858 }
5959
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.
7878 var issuerMetadata = s_configManager . GetConfigurationAsync ( ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
7979 string authorityHost ;
8080 try
8181 {
82- authorityHost = new Uri ( aadAuthority ) . Authority ;
83- }
84- catch
82+ authorityHost = new Uri ( aadAuthority ) . Authority ;
83+ }
84+ catch
8585 {
8686 authorityHost = null ;
87- }
88-
89- // Add issuer aliases of the chosen authority
87+ }
88+
89+ // Add issuer aliases of the chosen authority
9090 string authority = authorityHost ?? new Uri ( FallbackAuthority ) . Host ;
9191 var aliases = issuerMetadata . Metadata
9292 . Where ( m => m . Aliases . Any ( a => string . Equals ( a , authority , StringComparison . OrdinalIgnoreCase ) ) )
9393 . 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 )
116116 {
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+
135135 if ( IsValidIssuer ( validationParameters . ValidIssuer , tenantId , actualIssuer ) )
136136 return actualIssuer ;
137137
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
140140 throw new SecurityTokenInvalidIssuerException ( $ "Issuer: '{ actualIssuer } ', does not match any of the valid issuers provided for this application.") ;
141141 }
142142
@@ -165,21 +165,21 @@ private bool IsValidIssuer(string validIssuerTemplate, string tenantId, string a
165165 }
166166
167167 return false ;
168- }
169-
168+ }
169+
170170 private static bool IsValidTidInLocalPath ( string tenantId , Uri uri )
171171 {
172172 string trimmedLocalPath = uri . LocalPath . Trim ( '/' ) ;
173173 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 )
183183 {
184184 if ( jwtSecurityToken . Payload . TryGetValue ( ClaimConstants . Tid , out object tenantId ) )
185185 return tenantId as string ;
@@ -191,9 +191,9 @@ private static string GetTenantIdFromToken(SecurityToken securityToken)
191191 var tid = jsonWebToken . GetPayloadValue < string > ( ClaimConstants . Tid ) ;
192192 if ( tid != null )
193193 return tid ;
194- }
195-
196- return string . Empty ;
197- }
198- }
194+ }
195+
196+ return string . Empty ;
197+ }
198+ }
199199}
0 commit comments