public JwtTokenExtractor(TokenValidationParameters tokenValidationParameters, string metadataUrl, string[] allowedSigningAlgorithms, EndorsementsValidator validator) { // Make our own copy so we can edit it _tokenValidationParameters = tokenValidationParameters.Clone(); _tokenValidationParameters.RequireSignedTokens = true; _allowedSigningAlgorithms = allowedSigningAlgorithms; _validator = validator; _openIdMetadata = _openIdMetadataCache.GetOrAdd(metadataUrl, key => { return(new ConfigurationManager <OpenIdConnectConfiguration>(metadataUrl, new OpenIdConnectConfigurationRetriever())); }); _endorsementsData = _endorsementsCache.GetOrAdd(metadataUrl, key => { var retriever = new EndorsementsRetriever(); return(new ConfigurationManager <IDictionary <string, string[]> >(metadataUrl, retriever, retriever)); }); }
private async Task <ClaimsPrincipal> ValidateTokenAsync(string jwtToken, string channelId, string[] requiredEndorsements) { if (requiredEndorsements == null) { throw new ArgumentNullException(nameof(requiredEndorsements)); } // _openIdMetadata only does a full refresh when the cache expires every 5 days OpenIdConnectConfiguration config = null; try { config = await _openIdMetadata.GetConfigurationAsync().ConfigureAwait(false); } catch (Exception e) { Trace.TraceError($"Error refreshing OpenId configuration: {e}"); // No config? We can't continue if (config == null) { throw; } } // Update the signing tokens from the last refresh _tokenValidationParameters.IssuerSigningKeys = config.SigningKeys; var tokenHandler = new JwtSecurityTokenHandler(); try { var principal = tokenHandler.ValidateToken(jwtToken, _tokenValidationParameters, out SecurityToken parsedToken); var parsedJwtToken = parsedToken as JwtSecurityToken; // Validate Channel / Token Endorsements. For this, the channelID present on the Activity // needs to be matched by an endorsement. var keyId = (string)parsedJwtToken?.Header?[AuthenticationConstants.KeyIdHeader]; var endorsements = await _endorsementsData.GetConfigurationAsync(); // Note: On the Emulator Code Path, the endorsements collection is empty so the validation code // below won't run. This is normal. if (!string.IsNullOrEmpty(keyId) && endorsements.TryGetValue(keyId, out var endorsementsForKey)) { // Verify that channelId is included in endorsements var isEndorsed = EndorsementsValidator.Validate(channelId, endorsementsForKey); if (!isEndorsed) { throw new UnauthorizedAccessException($"Could not validate endorsement for key: {keyId} with endorsements: {string.Join(",", endorsementsForKey)}"); } // Verify that additional endorsements are satisfied. If no additional endorsements are expected, the requirement is satisfied as well var additionalEndorsementsSatisfied = requiredEndorsements.All( endorsement => EndorsementsValidator.Validate(endorsement, endorsementsForKey)); if (!additionalEndorsementsSatisfied) { throw new UnauthorizedAccessException($"Could not validate additional endorsement for key: {keyId} with endorsements: {string.Join(",", endorsementsForKey)}. Expected endorsements: {string.Join(",", requiredEndorsements)}"); } } if (_allowedSigningAlgorithms != null) { var algorithm = parsedJwtToken?.Header?.Alg; if (!_allowedSigningAlgorithms.Contains(algorithm)) { throw new UnauthorizedAccessException($"Token signing algorithm '{algorithm}' not in allowed list"); } } return(principal); } catch (SecurityTokenSignatureKeyNotFoundException) { var keys = string.Join(", ", (config?.SigningKeys ?? Enumerable.Empty <SecurityKey>()).Select(t => t.KeyId)); Trace.TraceError("Error finding key for token. Available keys: " + keys); throw; } }