/// <summary> /// Get the ordered JWT configurations for validating tokens /// </summary> /// <returns></returns> private static List <JwtConfig> GetJwtConfigs() { // JWT configs are stored as defined values. The value is the OpenID configuration URL var definedTypeCache = DefinedTypeCache.Get(SystemGuid.DefinedType.JWT_CONFIGURATION); if (definedTypeCache == null || !definedTypeCache.IsActive || definedTypeCache.DefinedValues == null) { return(null); } // Additional JWT configuration properties are stored as attributes of the defined value var issuerAttributeCache = AttributeCache.Get(SystemGuid.Attribute.DEFINED_VALUE_JWT_ISSUER); var audienceAttributeCache = AttributeCache.Get(SystemGuid.Attribute.DEFINED_VALUE_JWT_AUDIENCE); var searchKeyAttributeCache = AttributeCache.Get(SystemGuid.Attribute.DEFINED_VALUE_JWT_SEARCH_KEY); // The configs should be ordered since they will be attempted in the order given here. The Rock admin may want them // tried in a specific order. return(definedTypeCache.DefinedValues.OrderBy(dv => dv.Order).ThenBy(dv => dv.Id).Select(dv => { var config = new JwtConfig { OpenIdConfigurationUrl = dv.Value }; if (dv.AttributeValues != null) { if (issuerAttributeCache != null && dv.AttributeValues.ContainsKey(issuerAttributeCache.Key)) { config.Issuer = dv.AttributeValues[issuerAttributeCache.Key]?.Value; } if (audienceAttributeCache != null && dv.AttributeValues.ContainsKey(audienceAttributeCache.Key)) { config.Audience = dv.AttributeValues[audienceAttributeCache.Key]?.Value; } if (searchKeyAttributeCache != null && dv.AttributeValues.ContainsKey(searchKeyAttributeCache.Key)) { var searchKeyGuid = dv.AttributeValues[searchKeyAttributeCache.Key]?.Value; config.SearchTypeValueId = DefinedValueCache.Get(searchKeyGuid)?.Id; } } return config; }).ToList()); }
/// <summary> /// Validates the JWT using the provided configuration and returns the parsed token if valid /// </summary> /// <param name="jwtString">The token.</param> /// <param name="jwtConfig">The configuration on how to validate the token</param> /// <returns></returns> private static JwtSecurityToken ValidateToken(JwtConfig jwtConfig, string jwtString) { if (jwtString.IsNullOrWhiteSpace()) { return(null); } if (jwtConfig == null) { return(null); } // It is standard to prefix JWTs with "Bearer ", but JwtSecurityTokenHandler.ValidateToken will // say the token is malformed if the prefix is not removed if (jwtString.StartsWith(HeaderTokens.JwtPrefix)) { jwtString = jwtString.Substring(HeaderTokens.JwtPrefix.Length); } // Retrieve the configuration manager, which is cached according to the jwtConfig.JwksJsonFileUrl var configurationManager = GetConfigurationManager(jwtConfig.OpenIdConfigurationUrl); if (configurationManager == null) { return(null); } // The configuration manager handles caching the configuration documents and keys, which are from another // server or provider like Auth0. var openIdConnectConfiguration = AsyncHelper.RunSync(() => configurationManager.GetConfigurationAsync()); if (openIdConnectConfiguration == null || openIdConnectConfiguration.SigningKeys == null || !openIdConnectConfiguration.SigningKeys.Any()) { return(null); } // Validate the items that are configured to be validated var validateAudience = !jwtConfig.Audience.IsNullOrWhiteSpace(); var validateIssuer = !jwtConfig.Issuer.IsNullOrWhiteSpace(); var validationParameters = new TokenValidationParameters { ValidateAudience = validateAudience, ValidAudience = validateAudience ? jwtConfig.Audience : null, ValidateIssuer = validateIssuer, ValidIssuer = validateIssuer ? jwtConfig.Issuer : null, RequireExpirationTime = true, RequireSignedTokens = true, ValidateIssuerSigningKey = true, IssuerSigningKeys = openIdConnectConfiguration.SigningKeys, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(1) // Allow a minute of play in server times since we're dealing with a third party key provider }; try { var principal = new JwtSecurityTokenHandler().ValidateToken(jwtString, validationParameters, out var validatedToken); // If the principal identity is null, we should not accept this as a validated token if (principal == null || principal.Identity == null) { return(null); } var jwtToken = validatedToken as JwtSecurityToken; return(jwtToken); } catch (Exception ex) { // The JWT was not well formed or did not validate in some other way ExceptionLogService.LogException(ex); Debug.WriteLine(ex.Message); return(null); } }