/// <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).ConfigureAwait(false); 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).ConfigureAwait(false)) { // The AppId is not valid. Not Authorized. throw new UnauthorizedAccessException($"Invalid AppId passed on token: {appIdFromClaim}"); } return(identity); }
// The following code is based on SkillValidation.AuthenticateChannelToken private async Task <ClaimsIdentity> SkillValidation_AuthenticateChannelTokenAsync(string authHeader, string channelId, CancellationToken cancellationToken) { var tokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuers = new[] { // TODO: presumably this table should also come from configuration "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", // Auth v3.1, 1.0 token "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", // Auth v3.1, 2.0 token "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", // Auth v3.2, 1.0 token "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", // Auth v3.2, 2.0 token "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", // Auth for US Gov, 1.0 token "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0" // Auth for US Gov, 2.0 token }, ValidateAudience = false, // Audience validation takes place manually in code. ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5), RequireSignedTokens = true }; // TODO: what should the openIdMetadataUrl be here? var tokenExtractor = new JwtTokenExtractor( _authHttpClient, tokenValidationParameters, _toBotFromEmulatorOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, _authConfiguration.RequiredEndorsements).ConfigureAwait(false); await SkillValidation_ValidateIdentityAsync(identity, cancellationToken).ConfigureAwait(false); return(identity); }
/// <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> /// <returns> /// A valid ClaimsIdentity. /// </returns> public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials) { var tokenExtractor = new JwtTokenExtractor( ToBotFromChannelTokenValidationParameters, AuthenticationConstants.ToBotFromChannelOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms, null); var identity = await tokenExtractor.GetIdentityAsync(authHeader); 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 audianceClaim = identity.Claims.FirstOrDefault( c => c.Issuer == AuthenticationConstants.BotFrameworkTokenIssuer && c.Type == AuthenticationConstants.AudienceClaim); if (audianceClaim == null) { // The relevant Audiance 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. Note that // the Bot Framwork uses the Audiance claim ("aud") to pass the AppID. string appIdFromClaim = audianceClaim.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="channelProvider">The channelService value that distinguishes public Azure from US Government Azure.</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> #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var openIdMetadataUrl = channelProvider != null && channelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl : AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl; var tokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuers = new[] { "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", // Auth v3.1, 1.0 token "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", // Auth v3.1, 2.0 token "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", // Auth v3.2, 1.0 token "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", // Auth v3.2, 2.0 token "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", // Auth for US Gov, 1.0 token "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", // Auth for US Gov, 2.0 token "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", // Auth for US Gov, 1.0 token "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", // Auth for US Gov, 2.0 token }, ValidateAudience = false, // Audience validation takes place manually in code. ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5), RequireSignedTokens = true }; // Add allowed token issuers from configuration (if present) if (authConfig.ValidTokenIssuers != null && authConfig.ValidTokenIssuers.Any()) { var validIssuers = tokenValidationParameters.ValidIssuers.ToList(); validIssuers.AddRange(authConfig.ValidTokenIssuers); tokenValidationParameters.ValidIssuers = validIssuers; } 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 a Bot Framework Government Channel Service. /// </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="serviceUrl">The service url from the request</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> /// <returns></returns> public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId) { var tokenExtractor = new JwtTokenExtractor(httpClient, ToBotFromGovernmentChannelTokenValidationParameters, OpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId).ConfigureAwait(false); await ValidateIdentity(identity, credentials, serviceUrl).ConfigureAwait(false); return(identity); }
// The following code is based on GovernmentChannelValidation.AuthenticateChannelToken private async Task <ClaimsIdentity> GovernmentChannelValidation_AuthenticateChannelTokenAsync(string authHeader, string serviceUrl, string channelId, CancellationToken cancellationToken) { var tokenValidationParameters = GovernmentChannelValidation_GetTokenValidationParameters(); var tokenExtractor = new JwtTokenExtractor( _authHttpClient, tokenValidationParameters, _toBotFromChannelOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, _authConfiguration.RequiredEndorsements).ConfigureAwait(false); await GovernmentChannelValidation_ValidateIdentityAsync(identity, serviceUrl, cancellationToken).ConfigureAwait(false); return(identity); }
/// <summary> /// Validate the incoming Auth Header as a token sent from a Bot Framework Government Channel Service. /// </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="serviceUrl">The service url from the request.</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>ClaimsIdentity.</returns> public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var tokenExtractor = new JwtTokenExtractor( httpClient, ToBotFromGovernmentChannelTokenValidationParameters, OpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfig.RequiredEndorsements).ConfigureAwait(false); await ValidateIdentity(identity, credentials, serviceUrl).ConfigureAwait(false); return(identity); }
/// <summary> /// Validate the incoming Auth Header as a token sent from a Bot Framework Channel Service. /// </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="channelProvider">The user defined configuration for the channel.</param> /// <param name="serviceUrl">The service url from the request.</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>ClaimsIdentity.</returns> #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var channelService = await channelProvider.GetChannelServiceAsync().ConfigureAwait(false); var tokenExtractor = new JwtTokenExtractor( httpClient, ToBotFromEnterpriseChannelTokenValidationParameters, string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ToBotFromEnterpriseChannelOpenIdMetadataUrlFormat, channelService), AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfig.RequiredEndorsements).ConfigureAwait(false); await ValidateIdentity(identity, credentials, serviceUrl).ConfigureAwait(false); 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="channelProvider">The channelService value that distinguishes public Azure from US Government Azure.</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> #pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) #pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var openIdMetadataUrl = channelProvider != null && channelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl : 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); }
// The following code is based on EmulatorValidation.AuthenticateEmulatorToken private async Task <ClaimsIdentity> EmulatorValidation_AuthenticateEmulatorTokenAsync(string authHeader, string channelId, CancellationToken cancellationToken) { var toBotFromEmulatorTokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidIssuers = new[] { // TODO: presumably this table should also come from configuration "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", // Auth v3.1, 1.0 token "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", // Auth v3.1, 2.0 token "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", // Auth v3.2, 1.0 token "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", // Auth v3.2, 2.0 token "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", // Auth for US Gov, 1.0 token "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", // Auth for US Gov, 2.0 token }, ValidateAudience = false, // Audience validation takes place manually in code. ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5), RequireSignedTokens = true, }; var tokenExtractor = new JwtTokenExtractor( _authHttpClient, toBotFromEmulatorTokenValidationParameters, _toBotFromEmulatorOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, _authConfiguration.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 _credentialFactory.IsValidAppIdAsync(appID, cancellationToken).ConfigureAwait(false)) { throw new UnauthorizedAccessException($"Invalid AppId passed on token: {appID}"); } 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="channelProvider">The channelService value that distinguishes public Azure from US Government Azure.</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> #pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> AuthenticateEmulatorToken(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) #pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var openIdMetadataUrl = (channelProvider != null && channelProvider.IsGovernment()) ? GovernmentAuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl : 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); }
/// <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> /// <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) { var tokenExtractor = new JwtTokenExtractor( ToBotFromEmulatorTokenValidationParameters, AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms, null); var identity = await tokenExtractor.GetIdentityAsync(authHeader); 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 == 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 == 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 if (tokenVersion == "3.0") { // The v3.0 Token types have been disallowed. Not Authorized. throw new UnauthorizedAccessException("Emulator token version '3.0' is depricated."); } else if (tokenVersion == "3.1" || tokenVersion == "3.2") { // The emulator for token versions "3.1" & "3.2" puts the AppId in the "Audiance" claim. Claim audianceClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AudienceClaim); if (audianceClaim == null) { // No claim around AppID. Not Authorized. throw new UnauthorizedAccessException("'aud' claim is required on Emulator Token version '3.x'."); } appID = audianceClaim.Value; } else { // Unknown Version. Not Authorized. throw new UnauthorizedAccessException($"Unknown Emulator Token version '{tokenVersion}'."); } if (!await credentials.IsValidAppIdAsync(appID)) { throw new UnauthorizedAccessException($"Invalid AppId passed on token: {appID}"); } return(identity); }