Esempio n. 1
0
        /// <summary>
        /// Assert that all the <see cref="IdTokenRequirements"/> are met by a JWT ID token for a given point in time.
        /// </summary>
        /// <param name="required"><see cref="IdTokenRequirements"/> that should be asserted.</param>
        /// <param name="rawIDToken">Raw ID token to assert requirements against.</param>
        /// <param name="pointInTime">Optional <see cref="DateTime"/> to act as "Now" in order to facilitate unit testing with static tokens.</param>
        /// <param name="signatureVerifier">Optional <see cref="ISignatureVerifier"/> to perform signature verification and token extraction. If unspecified
        /// <see cref="AsymmetricSignatureVerifier"/> is used against the <paramref name="required"/> Issuer.</param>
        /// <exception cref="IdTokenValidationException">Exception thrown if <paramref name="rawIDToken"/> fails to
        /// meet the requirements specified by <paramref name="required"/>.
        /// </exception>
        /// <returns><see cref="Task"/> that will complete when the token is validated.</returns>
        internal static async Task AssertTokenMeetsRequirements(this IdTokenRequirements required, string rawIDToken, DateTime?pointInTime = null, ISignatureVerifier signatureVerifier = null)
        {
            if (string.IsNullOrWhiteSpace(rawIDToken))
            {
                throw new IdTokenValidationException("ID token is required but missing.");
            }

            var token = DecodeToken(rawIDToken);

            // For now we want to support HS256 + ClientSecret as we just had a major release.
            // TODO: In the next major (v4.0) we should remove this condition as well as Auth0ClientOptions.ClientSecret
            if (token.SignatureAlgorithm != "HS256")
            {
                (signatureVerifier ?? await AsymmetricSignatureVerifier.ForJwks(required.Issuer)).VerifySignature(rawIDToken);
            }

            AssertTokenClaimsMeetRequirements(required, token, pointInTime ?? DateTime.Now);
        }
        /// <summary>
        /// Assert that all the claims within a <see cref="JwtSecurityToken"/> meet the specified <see cref="IdTokenRequirements"/> for a given point in time.
        /// </summary>
        /// <param name="required"><see cref="IdTokenRequirements"/> that should be asserted.</param>
        /// <param name="token"><see cref="JwtSecurityToken"/> to assert requirements against.</param>
        /// <param name="pointInTime"><see cref="DateTime"/> to act as "Now" when asserting time-based claims.</param>
        /// <exception cref="IdTokenValidationException">Exception thrown if <paramref name="token"/> fails to
        /// meet the requirements specified by <paramref name="required"/>.
        /// </exception>
        public static void AssertClaimsMeetRequirements(IdTokenRequirements required, JwtSecurityToken token, DateTime?pointInTime = null)
        {
            var epochNow = EpochTime.GetIntDate(pointInTime ?? DateTime.Now);

            // Issuer
            if (string.IsNullOrWhiteSpace(token.Issuer))
            {
                throw new IdTokenValidationException("Issuer (iss) claim must be a string present in the ID token.");
            }
            if (token.Issuer != required.Issuer)
            {
                throw new IdTokenValidationException($"Issuer (iss) claim mismatch in the ID token; expected \"{required.Issuer}\", found \"{token.Issuer}\".");
            }

            // Subject
            if (string.IsNullOrWhiteSpace(token.Subject))
            {
                throw new IdTokenValidationException("Subject (sub) claim must be a string present in the ID token.");
            }

            // Audience
            var audienceCount = token.Audiences.Count();

            if (audienceCount == 0)
            {
                throw new IdTokenValidationException("Audience (aud) claim must be a string or array of strings present in the ID token.");
            }
            if (!token.Audiences.Contains(required.Audience))
            {
                throw new IdTokenValidationException($"Audience (aud) claim mismatch in the ID token; expected \"{required.Audience}\" but was not one of \"" +
                                                     $"{String.Join(", ", token.Audiences)}\".");
            }

            {
                // Expires at
                var exp = GetEpoch(token.Claims, JwtRegisteredClaimNames.Exp);
                if (exp == null)
                {
                    throw new IdTokenValidationException("Expiration Time (exp) claim must be an integer present in the ID token.");
                }
                var expiration = exp + required.Leeway.TotalSeconds;
                if (epochNow >= expiration)
                {
                    throw new IdTokenValidationException($"Expiration Time (exp) claim error in the ID token; current time ({epochNow}) is after expiration time ({exp}).");
                }
            }

            // Issued at
            var iat = GetEpoch(token.Claims, JwtRegisteredClaimNames.Iat);

            if (iat == null)
            {
                throw new IdTokenValidationException("Issued At (iat) claim must be an integer present in the ID token.");
            }

            // Nonce
            if (required.Nonce != null)
            {
                if (string.IsNullOrWhiteSpace(token.Payload.Nonce))
                {
                    throw new IdTokenValidationException("Nonce (nonce) claim must be a string present in the ID token.");
                }
                if (token.Payload.Nonce != required.Nonce)
                {
                    throw new IdTokenValidationException($"Nonce (nonce) claim mismatch in the ID token; expected \"{required.Nonce}\", found \"{token.Payload.Nonce}\".");
                }
            }

            // Authorized Party
            if (audienceCount > 1)
            {
                if (string.IsNullOrWhiteSpace(token.Payload.Azp))
                {
                    throw new IdTokenValidationException("Authorized Party (azp) claim must be a string present in the ID token when Audiences (aud) claim has multiple values.");
                }
                if (token.Payload.Azp != required.Audience)
                {
                    throw new IdTokenValidationException($"Authorized Party (azp) claim mismatch in the ID token; expected \"{required.Audience}\", found \"{token.Payload.Azp}\".");
                }
            }

            // Authentication time
            if (required.MaxAge.HasValue)
            {
                var authTime = GetEpoch(token.Claims, JwtRegisteredClaimNames.AuthTime);
                if (!authTime.HasValue)
                {
                    throw new IdTokenValidationException("Authentication Time (auth_time) claim must be an integer present in the ID token when MaxAge specified.");
                }

                var authValidUntil = (long)(authTime + required.MaxAge.Value.TotalSeconds + required.Leeway.TotalSeconds);

                if (epochNow > authValidUntil)
                {
                    throw new IdTokenValidationException($"Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time ({epochNow}) is after last auth at {authValidUntil}.");
                }
            }

            // Organization
            if (!string.IsNullOrWhiteSpace(required.Organization))
            {
                var organization = GetClaimValue(token.Claims, Auth0ClaimNames.Organization);
                if (string.IsNullOrWhiteSpace(organization))
                {
                    throw new IdTokenValidationException("Organization claim must be a string present in the ID token.");
                }
                if (organization != required.Organization)
                {
                    throw new IdTokenValidationException($"Organization claim mismatch in the ID token; expected \"{required.Organization}\", found \"{organization}\".");
                }
            }
        }