/// <summary> /// Validate the incoming Auth Header as a token sent from the Bot Framework Service. /// </summary> /// <remarks> /// A token issued by the Bot Framework emulator will FAIL this check. /// </remarks> /// <param name="authHeader">The raw HTTP header in the format: "Bearer [longString]".</param> /// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param> /// <param name="httpClient">Authentication of tokens requires calling out to validate Endorsements and related documents. The /// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to /// setup and teardown, so a shared HttpClient is recommended.</param> /// <param name="channelId">The ID of the channel to validate.</param> /// <param name="authConfig">The authentication configuration.</param> /// <returns> /// A valid ClaimsIdentity. /// </returns> public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var tokenExtractor = new JwtTokenExtractor( httpClient, ToBotFromChannelTokenValidationParameters, OpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfig.RequiredEndorsements); if (identity == null) { // No valid identity. Not Authorized. throw new UnauthorizedAccessException(); } if (!identity.IsAuthenticated) { // The token is in some way invalid. Not Authorized. throw new UnauthorizedAccessException(); } // Now check that the AppID in the claimset matches // what we're looking for. Note that in a multi-tenant bot, this value // comes from developer code that may be reaching out to a service, hence the // Async validation. // Look for the "aud" claim, but only if issued from the Bot Framework Claim audienceClaim = identity.Claims.FirstOrDefault( c => c.Issuer == AuthenticationConstants.ToBotFromChannelTokenIssuer && c.Type == AuthenticationConstants.AudienceClaim); if (audienceClaim == null) { // The relevant audience Claim MUST be present. Not Authorized. throw new UnauthorizedAccessException(); } // The AppId from the claim in the token must match the AppId specified by the developer. // In this case, the token is destined for the app, so we find the app ID in the audience claim. string appIdFromClaim = audienceClaim.Value; if (string.IsNullOrWhiteSpace(appIdFromClaim)) { // Claim is present, but doesn't have a value. Not Authorized. throw new UnauthorizedAccessException(); } if (!await credentials.IsValidAppIdAsync(appIdFromClaim)) { // The AppId is not valid. Not Authorized. throw new UnauthorizedAccessException($"Invalid AppId passed on token: {appIdFromClaim}"); } return(identity); }
/// <summary> /// Validates that the incoming Auth Header is a token sent from a bot to a skill or from a skill to a bot. /// </summary> /// <param name="authHeader">The raw HTTP header in the format: "Bearer [longString]".</param> /// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param> /// <param name="httpClient"> /// Authentication of tokens requires calling out to validate Endorsements and related documents. The /// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to /// setup and teardown, so a shared HttpClient is recommended. /// </param> /// <param name="channelId">The ID of the channel to validate.</param> /// <param name="authConfig">The authentication configuration.</param> /// <returns>A <see cref="ClaimsIdentity"/> instance if the validation is successful.</returns> public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var openIdMetadataUrl = AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl; var tokenExtractor = new JwtTokenExtractor( httpClient, _tokenValidationParameters, openIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfig.RequiredEndorsements).ConfigureAwait(false); await ValidateIdentityAsync(identity, credentials).ConfigureAwait(false); return(identity); }
/// <summary> /// Validate the incoming Auth Header as a token sent from the Bot Framework Emulator. /// </summary> /// <param name="authHeader">The raw HTTP header in the format: "Bearer [longString]".</param> /// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param> /// <param name="httpClient">Authentication of tokens requires calling out to validate Endorsements and related documents. The /// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to /// setup and teardown, so a shared HttpClient is recommended.</param> /// <param name="channelId">The ID of the channel to validate.</param> /// <param name="authConfig">The authentication configuration.</param> /// <returns> /// A valid ClaimsIdentity. /// </returns> /// <remarks> /// A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass. /// </remarks> public static async Task <ClaimsIdentity> AuthenticateEmulatorToken(string authHeader, ICredentialProvider credentials, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var openIdMetadataUrl = AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl; var tokenExtractor = new JwtTokenExtractor( httpClient, ToBotFromEmulatorTokenValidationParameters, openIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfig.RequiredEndorsements).ConfigureAwait(false); if (identity == null) { // No valid identity. Not Authorized. throw new UnauthorizedAccessException("Invalid Identity"); } if (!identity.IsAuthenticated) { // The token is in some way invalid. Not Authorized. throw new UnauthorizedAccessException("Token Not Authenticated"); } // Now check that the AppID in the claimset matches // what we're looking for. Note that in a multi-tenant bot, this value // comes from developer code that may be reaching out to a service, hence the // Async validation. Claim versionClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.VersionClaim); if (versionClaim == null) { throw new UnauthorizedAccessException("'ver' claim is required on Emulator Tokens."); } string tokenVersion = versionClaim.Value; string appID = string.Empty; // The Emulator, depending on Version, sends the AppId via either the // appid claim (Version 1) or the Authorized Party claim (Version 2). if (string.IsNullOrWhiteSpace(tokenVersion) || tokenVersion == "1.0") { // either no Version or a version of "1.0" means we should look for // the claim in the "appid" claim. Claim appIdClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AppIdClaim); if (appIdClaim == null) { // No claim around AppID. Not Authorized. throw new UnauthorizedAccessException("'appid' claim is required on Emulator Token version '1.0'."); } appID = appIdClaim.Value; } else if (tokenVersion == "2.0") { // Emulator, "2.0" puts the AppId in the "azp" claim. Claim appZClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AuthorizedParty); if (appZClaim == null) { // No claim around AppID. Not Authorized. throw new UnauthorizedAccessException("'azp' claim is required on Emulator Token version '2.0'."); } appID = appZClaim.Value; } else { // Unknown Version. Not Authorized. throw new UnauthorizedAccessException($"Unknown Emulator Token version '{tokenVersion}'."); } if (!await credentials.IsValidAppIdAsync(appID).ConfigureAwait(false)) { throw new UnauthorizedAccessException($"Invalid AppId passed on token: {appID}"); } return(identity); }