-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add bot authentication support for asp net core 2 authentication midd…
…leware (#3687) * Add bot authentication support for asp net core 2 authentication pipeline * Remove app id and password from aspnetcore2 sample
- Loading branch information
1 parent
b6dfd9c
commit ba60581
Showing
13 changed files
with
495 additions
and
1 deletion.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
CSharp/Library/Microsoft.Bot.Connector.AspNetCore2/BotAuthentication.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Bot.Connector | ||
{ | ||
public static class BotAuthenticationExtensions | ||
{ | ||
public static AuthenticationBuilder AddBotAuthentication(this AuthenticationBuilder builder, string microsoftAppId, string microsoftAppPassword) | ||
=> builder.AddBotAuthentication(new StaticCredentialProvider(microsoftAppId, microsoftAppPassword)); | ||
|
||
public static AuthenticationBuilder AddBotAuthentication(this AuthenticationBuilder builder, ICredentialProvider credentialProvider) | ||
=> builder.AddBotAuthentication(JwtBearerDefaults.AuthenticationScheme, displayName: "botAuthenticator", configureOptions: options => | ||
{ | ||
options.CredentialProvider = credentialProvider; | ||
options.Events = new JwtBearerEvents(); | ||
}); | ||
public static AuthenticationBuilder AddBotAuthentication(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<BotAuthenticationOptions> configureOptions) | ||
{ | ||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<BotAuthenticationOptions>, JwtBearerPostConfigureOptions>()); | ||
return builder.AddScheme<BotAuthenticationOptions, BotAuthenticationHandler>(authenticationScheme, displayName, configureOptions); | ||
} | ||
} | ||
} |
192 changes: 192 additions & 0 deletions
192
CSharp/Library/Microsoft.Bot.Connector.AspNetCore2/BotAuthenticationHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
using System; | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Security.Claims; | ||
using System.Text.Encodings.Web; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Bot.Connector | ||
{ | ||
/// <summary> | ||
/// Bot authentication hanlder used by <see cref="BotAuthenticationMiddleware"/>. | ||
/// </summary> | ||
public class BotAuthenticationHandler : AuthenticationHandler<BotAuthenticationOptions> | ||
{ | ||
public BotAuthenticationHandler(IOptionsMonitor<BotAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) | ||
: base(options, logger, encoder, clock) | ||
{ | ||
this.Events = options.CurrentValue.Events; | ||
} | ||
|
||
/// <summary> | ||
/// The handler calls methods on the events which give the application control at certain points where processing is occurring. | ||
/// If it is not provided a default instance is supplied which does nothing when the methods are called. | ||
/// </summary> | ||
protected new JwtBearerEvents Events | ||
{ | ||
get { return (JwtBearerEvents)base.Events; } | ||
set { base.Events = value; } | ||
} | ||
|
||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() | ||
{ | ||
if (await Options.CredentialProvider.IsAuthenticationDisabledAsync()) | ||
{ | ||
var principal = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Bot") })); | ||
|
||
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options) | ||
{ | ||
Principal = principal | ||
}; | ||
tokenValidatedContext.Success(); | ||
return tokenValidatedContext.Result; | ||
} | ||
|
||
string token = null; | ||
try | ||
{ | ||
// Give application opportunity to find from a different location, adjust, or reject token | ||
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options); | ||
|
||
// event can set the token | ||
await Events.MessageReceived(messageReceivedContext); | ||
if (messageReceivedContext.Result != null) | ||
{ | ||
return messageReceivedContext.Result; | ||
} | ||
|
||
// If application retrieved token from somewhere else, use that. | ||
token = messageReceivedContext.Token; | ||
|
||
if (string.IsNullOrEmpty(token)) | ||
{ | ||
string authorization = Request.Headers["Authorization"]; | ||
|
||
// If no authorization header found, nothing to process further | ||
if (string.IsNullOrEmpty(authorization)) | ||
{ | ||
return AuthenticateResult.NoResult(); | ||
} | ||
|
||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
token = authorization.Substring("Bearer ".Length).Trim(); | ||
} | ||
|
||
// If no token found, no further work possible | ||
if (string.IsNullOrEmpty(token)) | ||
{ | ||
return AuthenticateResult.NoResult(); | ||
} | ||
} | ||
|
||
// If no token found, no further work possible | ||
// and Authentication is not disabled fail | ||
if (string.IsNullOrEmpty(token)) | ||
{ | ||
return AuthenticateResult.Fail("No JwtToken is present and BotAuthentication is enabled!"); | ||
} | ||
var authenticator = new BotAuthenticator(Options.CredentialProvider, Options.OpenIdConfiguration, Options.DisableEmulatorTokens); | ||
var identityToken = await authenticator.TryAuthenticateAsync(Options.Challenge, token, CancellationToken.None); | ||
|
||
if (identityToken.Authenticated) | ||
{ | ||
Logger.TokenValidationSucceeded(); | ||
|
||
identityToken.Identity.AddClaim(new Claim(ClaimTypes.Role, "Bot")); | ||
var principal = new ClaimsPrincipal(identityToken.Identity); | ||
Context.User = principal; | ||
|
||
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options) | ||
{ | ||
Principal = principal, | ||
SecurityToken = new JwtSecurityToken(token) | ||
}; | ||
|
||
await Events.TokenValidated(tokenValidatedContext); | ||
if (tokenValidatedContext.Result != null) | ||
{ | ||
return tokenValidatedContext.Result; | ||
} | ||
|
||
if (Options.SaveToken) | ||
{ | ||
tokenValidatedContext.Properties.StoreTokens(new[] | ||
{ | ||
new AuthenticationToken { Name = "access_token", Value = token } | ||
}); | ||
} | ||
|
||
tokenValidatedContext.Success(); | ||
return tokenValidatedContext.Result; | ||
} | ||
else | ||
{ | ||
Logger.TokenValidationFailed(token, null); | ||
return AuthenticateResult.Fail($"Failed to authenticate JwtToken {token}"); | ||
} | ||
|
||
} | ||
catch (Exception ex) | ||
{ | ||
Logger.ErrorProcessingMessage(ex); | ||
|
||
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options) | ||
{ | ||
Exception = ex | ||
}; | ||
|
||
await Events.AuthenticationFailed(authenticationFailedContext); | ||
if (authenticationFailedContext.Result != null) | ||
{ | ||
return authenticationFailedContext.Result; | ||
} | ||
|
||
throw; | ||
} | ||
} | ||
} | ||
|
||
internal static class LoggingExtensions | ||
{ | ||
private static Action<ILogger, string, Exception> _tokenValidationFailed; | ||
private static Action<ILogger, Exception> _tokenValidationSucceeded; | ||
private static Action<ILogger, Exception> _errorProcessingMessage; | ||
|
||
static LoggingExtensions() | ||
{ | ||
_tokenValidationFailed = LoggerMessage.Define<string>( | ||
eventId: 1, | ||
logLevel: LogLevel.Information, | ||
formatString: "Failed to validate the token {Token}."); | ||
_tokenValidationSucceeded = LoggerMessage.Define( | ||
eventId: 2, | ||
logLevel: LogLevel.Information, | ||
formatString: "Successfully validated the token."); | ||
_errorProcessingMessage = LoggerMessage.Define( | ||
eventId: 3, | ||
logLevel: LogLevel.Error, | ||
formatString: "Exception occurred while processing message."); | ||
} | ||
|
||
public static void TokenValidationFailed(this ILogger logger, string token, Exception ex) | ||
{ | ||
_tokenValidationFailed(logger, token, ex); | ||
} | ||
|
||
public static void TokenValidationSucceeded(this ILogger logger) | ||
{ | ||
_tokenValidationSucceeded(logger, null); | ||
} | ||
|
||
public static void ErrorProcessingMessage(this ILogger logger, Exception ex) | ||
{ | ||
_errorProcessingMessage(logger, ex); | ||
} | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
CSharp/Library/Microsoft.Bot.Connector.AspNetCore2/BotAuthenticationOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
|
||
namespace Microsoft.Bot.Connector | ||
{ | ||
public sealed class BotAuthenticationOptions : JwtBearerOptions | ||
{ | ||
|
||
/// <summary> | ||
/// The <see cref="ICredentialProvider"/> used for authentication. | ||
/// </summary> | ||
public ICredentialProvider CredentialProvider { set; get; } | ||
|
||
/// <summary> | ||
/// The OpenId configuation. | ||
/// </summary> | ||
public string OpenIdConfiguration { set; get; } = JwtConfig.ToBotFromChannelOpenIdMetadataUrl; | ||
|
||
/// <summary> | ||
/// Flag indicating if emulator tokens should be disabled. | ||
/// </summary> | ||
public bool DisableEmulatorTokens { set; get; } = false; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...rp/Library/Microsoft.Bot.Connector.AspNetCore2/Microsoft.Bot.Connector.AspNetCore2.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="..\Microsoft.Bot.Connector.AspNetCore\TrustServiceUrlAttribute.cs" Link="TrustServiceUrlAttribute.cs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Microsoft.Bot.Connector.NetCore\Microsoft.Bot.Connector.NetCore.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
CSharp/Samples/Microsoft.Bot.Sample.AspNetCore2.Echo/Controllers/MessagesController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.Bot.Connector; | ||
using Microsoft.Extensions.Configuration; | ||
|
||
namespace Microsoft.Bot.Sample.AspNetCore2.Echo.Controllers | ||
{ | ||
[Route("api/[controller]")] | ||
public class MessagesController : Controller | ||
{ | ||
private readonly IConfiguration configuration; | ||
|
||
public MessagesController(IConfiguration configuration) | ||
{ | ||
this.configuration = configuration; | ||
} | ||
|
||
[Authorize(Roles = "Bot")] | ||
// POST api/values | ||
[HttpPost] | ||
public virtual async Task<OkResult> Post([FromBody]Activity activity) | ||
{ | ||
var appCredentials = new MicrosoftAppCredentials(this.configuration); | ||
var client = new ConnectorClient(new Uri(activity.ServiceUrl), appCredentials); | ||
var reply = activity.CreateReply(); | ||
if (activity.Type == ActivityTypes.Message) | ||
{ | ||
reply.Text = $"echo: {activity.Text}"; | ||
} | ||
else | ||
{ | ||
reply.Text = $"activity type: {activity.Type}"; | ||
} | ||
await client.Conversations.ReplyToActivityAsync(reply); | ||
return Ok(); | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...amples/Microsoft.Bot.Sample.AspNetCore2.Echo/Microsoft.Bot.Sample.AspNetCore2.Echo.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\Library\Microsoft.Bot.Connector.AspNetCore2\Microsoft.Bot.Connector.AspNetCore2.csproj" /> | ||
<ProjectReference Include="..\..\Library\Microsoft.Bot.Connector.NetCore\Microsoft.Bot.Connector.NetCore.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.