/// <summary> /// Creates the appropriate cloud environment instance. /// </summary> /// <param name="channelService">The Channel Service.</param> /// <param name="validateAuthority">The validate authority value to use.</param> /// <param name="toChannelFromBotLoginUrl">The to Channel from bot login url.</param> /// <param name="toChannelFromBotOAuthScope">The to Channel from bot oauth scope.</param> /// <param name="toBotFromChannelTokenIssuer">The to bot from Channel Token Issuer.</param> /// <param name="oAuthUrl">The oAuth url.</param> /// <param name="toBotFromChannelOpenIdMetadataUrl">The to bot from Channel Open Id Metadata url.</param> /// <param name="toBotFromEmulatorOpenIdMetadataUrl">The to bot from Emulator Open Id Metadata url.</param> /// <param name="callerId">The Microsoft app password.</param> /// <param name="credentialFactory">The IServiceClientCredentialsFactory to use to create credentials.</param> /// <param name="authConfiguration">The AuthenticationConfiguration to use.</param> /// <param name="httpClient">The HttpClient to use.</param> /// <param name="logger">The ILogger instance to use.</param> /// <returns>A new cloud environment.</returns> public static BotFrameworkAuthentication Create( string channelService, bool validateAuthority, string toChannelFromBotLoginUrl, string toChannelFromBotOAuthScope, string toBotFromChannelTokenIssuer, string oAuthUrl, string toBotFromChannelOpenIdMetadataUrl, string toBotFromEmulatorOpenIdMetadataUrl, string callerId, ServiceClientCredentialsFactory credentialFactory, AuthenticationConfiguration authConfiguration, HttpClient httpClient, ILogger logger) { if (string.IsNullOrEmpty(channelService)) { return(new PublicCloudBotFrameworkAuthentication(credentialFactory, authConfiguration, httpClient, logger)); } else if (channelService == GovernmentAuthenticationConstants.ChannelService) { return(new GovernmentCloudBotFrameworkAuthentication(credentialFactory, authConfiguration, httpClient, logger)); } else { return(new ParameterizedBotFrameworkAuthentication( channelService, validateAuthority, toChannelFromBotLoginUrl, toChannelFromBotOAuthScope, toBotFromChannelTokenIssuer, oAuthUrl, toBotFromChannelOpenIdMetadataUrl, toBotFromEmulatorOpenIdMetadataUrl, callerId, credentialFactory, authConfiguration, httpClient, logger)); } }
// The following code is based on GovernmentChannelValidation.AuthenticateChannelToken private async Task <ClaimsIdentity> GovernmentChannelValidation_AuthenticateChannelTokenAsync(string authHeader, ServiceClientCredentialsFactory credentialFactory, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig, CancellationToken cancellationToken) { var tokenValidationParameters = GovernmentChannelValidation_GetTokenValidationParameters(); var tokenExtractor = new JwtTokenExtractor( httpClient, tokenValidationParameters, _toBotFromChannelOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfig.RequiredEndorsements).ConfigureAwait(false); await GovernmentChannelValidation_ValidateIdentityAsync(identity, credentialFactory, serviceUrl, cancellationToken).ConfigureAwait(false); return(identity); }
// The following code is based on EmulatorValidation.AuthenticateEmulatorToken private async Task <ClaimsIdentity> EmulatorValidation_AuthenticateEmulatorTokenAsync(string authHeader, ServiceClientCredentialsFactory credentialFactory, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfiguration, 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( httpClient, 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); }
// The following code is based on SkillValidation.AuthenticateChannelToken private async Task <ClaimsIdentity> SkillValidation_AuthenticateChannelTokenAsync(string authHeader, ServiceClientCredentialsFactory credentialFactory, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfiguration, 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( httpClient, tokenValidationParameters, _toBotFromEmulatorOpenIdMetadataUrl, AuthenticationConstants.AllowedSigningAlgorithms); var identity = await tokenExtractor.GetIdentityAsync(authHeader, channelId, authConfiguration.RequiredEndorsements).ConfigureAwait(false); await SkillValidation_ValidateIdentityAsync(identity, credentialFactory, cancellationToken).ConfigureAwait(false); return(identity); }
/// <summary> /// Authenticates the request and adds the activity's <see cref="Activity.ServiceUrl"/> /// to the set of trusted URLs. /// </summary> /// <param name="activity">The activity.</param> /// <param name="authHeader">The authentication header.</param> /// <param name="credentials">The bot's credential provider.</param> /// <param name="provider">The bot's channel service provider.</param> /// <param name="authConfig">The optional authentication configuration.</param> /// <param name="httpClient">The HTTP client.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the task completes successfully, the result contains the claims-based /// identity for the request.</remarks> #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> AuthenticateRequest(IActivity activity, string authHeader, ICredentialProvider credentials, IChannelProvider provider, AuthenticationConfiguration authConfig, HttpClient httpClient = null) #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } if (string.IsNullOrWhiteSpace(authHeader)) { var isAuthDisabled = await credentials.IsAuthenticationDisabledAsync().ConfigureAwait(false); if (!isAuthDisabled) { // No Auth Header and Auth is required. Request is not authorized. throw new UnauthorizedAccessException(); } // Check if the activity is for a skill call and is coming from the Emulator. if (activity.ChannelId == Channels.Emulator && activity.Recipient?.Role == RoleTypes.Skill) { // Return an anonymous claim with an anonymous skill AppId return(SkillValidation.CreateAnonymousSkillClaim()); } // In the scenario where Auth is disabled, we still want to have the // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return(new ClaimsIdentity(new List <Claim>(), AuthenticationConstants.AnonymousAuthType)); } // Validate the header and extract claims. var claimsIdentity = await ValidateAuthHeader(authHeader, credentials, provider, activity.ChannelId, authConfig, activity.ServiceUrl, httpClient ?? _httpClient).ConfigureAwait(false); return(claimsIdentity); }
/// <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); }
public PublicCloudBotFrameworkAuthentication(ServiceClientCredentialsFactory credentialFactory, AuthenticationConfiguration authConfiguration, HttpClient httpClient = null, ILogger logger = null) : base( AuthenticationConstants.ToChannelFromBotOAuthScope, AuthenticationConstants.ToChannelFromBotLoginUrlTemplate, CallerIdConstants.PublicAzureChannel, null, credentialFactory, authConfiguration, httpClient, logger) { }
/// <summary> /// Validate the incoming Auth Header as a token sent from the Bot Framework 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">Service url.</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 UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) #pragma warning restore UseAsyncSuffix // Use Async suffix { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } var identity = await AuthenticateChannelToken(authHeader, credentials, httpClient, channelId, authConfig).ConfigureAwait(false); var serviceUrlClaim = identity.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.ServiceUrlClaim)?.Value; if (string.IsNullOrWhiteSpace(serviceUrlClaim)) { // Claim must be present. Not Authorized. throw new UnauthorizedAccessException(); } if (!string.Equals(serviceUrlClaim, serviceUrl, StringComparison.OrdinalIgnoreCase)) { // Claim must match. Not Authorized. throw new UnauthorizedAccessException(); } return(identity); }
/// <summary> /// Authenticates the request and add's the activity's <see cref="Activity.ServiceUrl"/> /// to the set of trusted URLs. /// </summary> /// <param name="activity">The activity.</param> /// <param name="authHeader">The authentication header.</param> /// <param name="credentials">The bot's credential provider.</param> /// <param name="provider">The bot's channel service provider.</param> /// <param name="authConfig">The optional authentication configuration.</param> /// <param name="httpClient">The HTTP client.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the task completes successfully, the result contains the claims-based /// identity for the request.</remarks> public static async Task <ClaimsIdentity> AuthenticateRequest(IActivity activity, string authHeader, ICredentialProvider credentials, IChannelProvider provider, AuthenticationConfiguration authConfig, HttpClient httpClient = null) { if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } if (string.IsNullOrWhiteSpace(authHeader)) { bool isAuthDisabled = await credentials.IsAuthenticationDisabledAsync().ConfigureAwait(false); if (isAuthDisabled) { // In the scenario where Auth is disabled, we still want to have the // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return(new ClaimsIdentity(new List <Claim>(), "anonymous")); } // No Auth Header. Auth is required. Request is not authorized. throw new UnauthorizedAccessException(); } var claimsIdentity = await ValidateAuthHeader(authHeader, credentials, provider, activity.ChannelId, authConfig, activity.ServiceUrl, httpClient ?? _httpClient); MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl); return(claimsIdentity); }
/// <summary> /// Validates the authentication header of an incoming request. /// </summary> /// <param name="authHeader">The authentication header to validate.</param> /// <param name="credentials">The bot's credential provider.</param> /// <param name="channelProvider">The bot's channel service provider.</param> /// <param name="channelId">The ID of the channel that sent the request.</param> /// <param name="authConfig">The authentication configuration.</param> /// <param name="serviceUrl">The service URL for the activity.</param> /// <param name="httpClient">The HTTP client.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the task completes successfully, the result contains the claims-based /// identity for the request.</remarks> public static async Task <ClaimsIdentity> ValidateAuthHeader(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, AuthenticationConfiguration authConfig, string serviceUrl = null, HttpClient httpClient = null) { if (string.IsNullOrEmpty(authHeader)) { throw new ArgumentNullException(nameof(authHeader)); } if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } httpClient = httpClient ?? _httpClient; bool usingEmulator = EmulatorValidation.IsTokenFromEmulator(authHeader); if (usingEmulator) { return(await EmulatorValidation.AuthenticateEmulatorToken(authHeader, credentials, channelProvider, httpClient, channelId, authConfig)); } else if (channelProvider == null || channelProvider.IsPublicAzure()) { // No empty or null check. Empty can point to issues. Null checks only. if (serviceUrl != null) { return(await ChannelValidation.AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient, channelId, authConfig)); } else { return(await ChannelValidation.AuthenticateChannelToken(authHeader, credentials, httpClient, channelId, authConfig)); } } else if (channelProvider.IsGovernment()) { return(await GovernmentChannelValidation.AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient, channelId, authConfig).ConfigureAwait(false)); } else { return(await EnterpriseChannelValidation.AuthenticateChannelToken(authHeader, credentials, channelProvider, serviceUrl, httpClient, channelId, authConfig).ConfigureAwait(false)); } }
/// <summary> /// Authenticates the auth header token from the request. /// </summary> private static async Task <ClaimsIdentity> AuthenticateTokenAsync(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, AuthenticationConfiguration authConfig, string serviceUrl, HttpClient httpClient) { if (SkillValidation.IsSkillToken(authHeader)) { return(await SkillValidation.AuthenticateChannelToken(authHeader, credentials, channelProvider, httpClient, channelId, authConfig).ConfigureAwait(false)); } if (EmulatorValidation.IsTokenFromEmulator(authHeader)) { return(await EmulatorValidation.AuthenticateEmulatorToken(authHeader, credentials, channelProvider, httpClient, channelId, authConfig).ConfigureAwait(false)); } if (channelProvider == null || channelProvider.IsPublicAzure()) { // No empty or null check. Empty can point to issues. Null checks only. if (serviceUrl != null) { return(await ChannelValidation.AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient, channelId, authConfig).ConfigureAwait(false)); } return(await ChannelValidation.AuthenticateChannelToken(authHeader, credentials, httpClient, channelId, authConfig).ConfigureAwait(false)); } if (channelProvider.IsGovernment()) { return(await GovernmentChannelValidation.AuthenticateChannelToken(authHeader, credentials, serviceUrl, httpClient, channelId, authConfig).ConfigureAwait(false)); } return(await EnterpriseChannelValidation.AuthenticateChannelToken(authHeader, credentials, channelProvider, serviceUrl, httpClient, channelId, authConfig).ConfigureAwait(false)); }
/// <summary> /// Validates the authentication header of an incoming request. /// </summary> /// <param name="authHeader">The authentication header to validate.</param> /// <param name="credentials">The bot's credential provider.</param> /// <param name="channelProvider">The bot's channel service provider.</param> /// <param name="channelId">The ID of the channel that sent the request.</param> /// <param name="authConfig">The authentication configuration.</param> /// <param name="serviceUrl">The service URL for the activity.</param> /// <param name="httpClient">The HTTP client.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the task completes successfully, the result contains the claims-based /// identity for the request.</remarks> #pragma warning disable UseAsyncSuffix // Use Async suffix (can't change this without breaking binary compat) public static async Task <ClaimsIdentity> ValidateAuthHeader(string authHeader, ICredentialProvider credentials, IChannelProvider channelProvider, string channelId, AuthenticationConfiguration authConfig, string serviceUrl = null, HttpClient httpClient = null) #pragma warning restore UseAsyncSuffix // Use Async suffix { if (string.IsNullOrEmpty(authHeader)) { throw new ArgumentNullException(nameof(authHeader)); } if (authConfig == null) { throw new ArgumentNullException(nameof(authConfig)); } httpClient = httpClient ?? _httpClient; var identity = await AuthenticateTokenAsync(authHeader, credentials, channelProvider, channelId, authConfig, serviceUrl, httpClient).ConfigureAwait(false); await ValidateClaimsAsync(authConfig, identity.Claims).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); }
// The following code is based on JwtTokenValidation.AuthenticateRequest private async Task <ClaimsIdentity> JwtTokenValidation_AuthenticateRequestAsync(Activity activity, string authHeader, ServiceClientCredentialsFactory credentialFactory, AuthenticationConfiguration authConfiguration, HttpClient httpClient, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(authHeader)) { var isAuthDisabled = await credentialFactory.IsAuthenticationDisabledAsync(cancellationToken).ConfigureAwait(false); if (isAuthDisabled) { // In the scenario where Auth is disabled, we still want to have the // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return(new ClaimsIdentity(new List <Claim>(), "anonymous")); } // No Auth Header. Auth is required. Request is not authorized. throw new UnauthorizedAccessException(); } var claimsIdentity = await JwtTokenValidation_ValidateAuthHeaderAsync(authHeader, credentialFactory, activity.ChannelId, authConfiguration, activity.ServiceUrl, httpClient, cancellationToken).ConfigureAwait(false); AppCredentials.TrustServiceUrl(activity.ServiceUrl); return(claimsIdentity); }
protected BuiltinBotFrameworkAuthentication(string toChannelFromBotOAuthScope, string loginEndpoint, string callerId, string channelService, ServiceClientCredentialsFactory credentialFactory, AuthenticationConfiguration authConfiguration, HttpClient httpClient, ILogger logger) { _toChannelFromBotOAuthScope = toChannelFromBotOAuthScope; _loginEndpoint = loginEndpoint; _callerId = callerId; _channelService = channelService; _credentialFactory = credentialFactory; _authConfiguration = authConfiguration; _httpClient = httpClient; _logger = logger; }
/// <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); }
// The following code is based on JwtTokenValidation.AuthenticateRequest private async Task <ClaimsIdentity> JwtTokenValidation_AuthenticateRequestAsync(Activity activity, string authHeader, ServiceClientCredentialsFactory credentialFactory, AuthenticationConfiguration authConfiguration, HttpClient httpClient, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(authHeader)) { var isAuthDisabled = await credentialFactory.IsAuthenticationDisabledAsync(cancellationToken).ConfigureAwait(false); if (!isAuthDisabled) { // No Auth Header. Auth is required. Request is not authorized. throw new UnauthorizedAccessException(); } // Check if the activity is for a skill call and is coming from the Emulator. if (activity.ChannelId == Channels.Emulator && activity.Recipient?.Role == RoleTypes.Skill) { // Return an anonymous claim with an anonymous skill AppId return(SkillValidation.CreateAnonymousSkillClaim()); } // In the scenario where Auth is disabled, we still want to have the // IsAuthenticated flag set in the ClaimsIdentity. To do this requires // adding in an empty claim. return(new ClaimsIdentity(new List <Claim>(), AuthenticationConstants.AnonymousAuthType)); } // Validate the header and extract claims. var claimsIdentity = await JwtTokenValidation_ValidateAuthHeaderAsync(authHeader, credentialFactory, activity.ChannelId, authConfiguration, activity.ServiceUrl, httpClient, cancellationToken).ConfigureAwait(false); AppCredentials.TrustServiceUrl(activity.ServiceUrl); return(claimsIdentity); }
/// <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> #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, HttpClient httpClient, string channelId, AuthenticationConfiguration authConfig) #pragma warning restore UseAsyncSuffix // Use Async suffix { 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); }
private async Task <ClaimsIdentity> JwtTokenValidation_ValidateAuthHeaderAsync(string authHeader, ServiceClientCredentialsFactory credentialFactory, string channelId, AuthenticationConfiguration authConfiguration, string serviceUrl, HttpClient httpClient, CancellationToken cancellationToken) { var identity = await JwtTokenValidation_AuthenticateTokenAsync(authHeader, credentialFactory, channelId, authConfiguration, serviceUrl, httpClient, cancellationToken).ConfigureAwait(false); await JwtTokenValidation_ValidateClaimsAsync(authConfiguration, identity.Claims).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); }
private async Task <ClaimsIdentity> JwtTokenValidation_AuthenticateTokenAsync(string authHeader, ServiceClientCredentialsFactory credentialFactory, string channelId, AuthenticationConfiguration authConfiguration, string serviceUrl, HttpClient httpClient, CancellationToken cancellationToken) { if (SkillValidation.IsSkillToken(authHeader)) { return(await SkillValidation_AuthenticateChannelTokenAsync(authHeader, credentialFactory, httpClient, channelId, authConfiguration, cancellationToken).ConfigureAwait(false)); } if (EmulatorValidation.IsTokenFromEmulator(authHeader)) { return(await EmulatorValidation_AuthenticateEmulatorTokenAsync(authHeader, credentialFactory, httpClient, channelId, authConfiguration, cancellationToken).ConfigureAwait(false)); } return(await GovernmentChannelValidation_AuthenticateChannelTokenAsync(authHeader, credentialFactory, serviceUrl, httpClient, channelId, authConfiguration, cancellationToken).ConfigureAwait(false)); }
/// <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); }
public GovernmentCloudBotFrameworkAuthentication(ServiceClientCredentialsFactory credentialFactory, AuthenticationConfiguration authConfiguration, HttpClient httpClient = null, ILogger logger = null) : base( GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope, GovernmentAuthenticationConstants.ToChannelFromBotLoginUrl, CallerIdConstants.USGovChannel, GovernmentAuthenticationConstants.ChannelService, GovernmentAuthenticationConstants.OAuthUrlGov, credentialFactory, authConfiguration, httpClient, logger) { }