Git Product home page Git Product logo

echobotforcore's Introduction

ASP.NET Core Bot Authentication

This repository shows how you can use ASP.NET Core and authenticate your Bot.

Approach

The Bot Framework C# SDK currently targets full .NET and makes use of System.Configuration for grabbing your Microsoft Application ID and Key. The key to getting this to work is to provide an alternative mechanism to providing those key credentials.

Implementation

BotOptions

BotOptions are POCO option classes which are populated using the new Options capabilities which are a part of the ASP.NET core configuration system.

public class BotOptions
{
    public BotAuthenticationOptions Authentication { get; set; }
}

public class BotAuthenticationOptions
{
    public string BotId { get; set; }
    public string MicrosoftAppId { get; set; }
    public string MicrosoftAppPassword { get; set; }
}

CoreBotAuthententicationAttribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class CoreBotAuthententicationAttribute : Attribute, IAsyncActionFilter
{

    public CoreBotAuthententicationAttribute(BotOptions botOptions)
    {
        BotOptions = botOptions;
    }

    public string MicrosoftAppId { get; set; }
    public string MicrosoftAppIdSettingName { get; set; }
    public bool DisableSelfIssuedTokens { get; set; }
    public virtual string OpenIdConfigurationUrl { get; set; } = JwtConfig.ToBotFromChannelOpenIdMetadataUrl;
    public BotOptions BotOptions { get; }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        MicrosoftAppId = MicrosoftAppId ?? BotOptions?.Authentication?.MicrosoftAppId ?? string.Empty;

        if (Debugger.IsAttached && String.IsNullOrEmpty(MicrosoftAppId))
        {
            // then auth is disabled
            await next();
            return;
        }


        var tokenExtractor = new JwtTokenExtractor(JwtConfig.GetToBotFromChannelTokenValidationParameters(MicrosoftAppId), OpenIdConfigurationUrl);
        var request = context.HttpContext.GetHttpRequestMessage();
        var identity = await tokenExtractor.GetIdentityAsync(request);

        // No identity? If we're allowed to, fall back to MSA
        // This code path is used by the emulator
        if (identity == null && !DisableSelfIssuedTokens)
        {
            tokenExtractor = new JwtTokenExtractor(JwtConfig.ToBotFromMSATokenValidationParameters, JwtConfig.ToBotFromMSAOpenIdMetadataUrl);
            identity = await tokenExtractor.GetIdentityAsync(request);

            // Check to make sure the app ID in the token is ours
            if (identity != null)
            {
                // If it doesn't match, throw away the identity
                if (tokenExtractor.GetBotIdFromClaimsIdentity(identity) != MicrosoftAppId)
                    identity = null;
            }
        }

        // Still no identity? Fail out.
        if (identity == null)
        {
            var host = request.RequestUri.DnsSafeHost;
            context.HttpContext.Response.Headers.Add("WWW-Authenticate", $"Bearer realm=\"{host}\"");
            context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
            return;
        }

        var activity = context.ActionArguments.Select(t => t.Value).OfType<Activity>().FirstOrDefault();
        if (activity != null)
        {
            MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
        }
        else
        {
            // No model binding to activity check if we can find JObject or JArray
            var obj = context.ActionArguments.Where(t => t.Value is JObject || t.Value is JArray).Select(t => t.Value).FirstOrDefault();
            if (obj != null)
            {
                Activity[] activities = (obj is JObject) ? new Activity[] { ((JObject)obj).ToObject<Activity>() } : ((JArray)obj).ToObject<Activity[]>();
                foreach (var jActivity in activities)
                {
                    if (!string.IsNullOrEmpty(jActivity.ServiceUrl))
                    {
                        MicrosoftAppCredentials.TrustServiceUrl(jActivity.ServiceUrl);
                    }
                }
            }
            else
            {
                Trace.TraceWarning("No activity in the Bot Authentication Action Arguments");
            }
        }

        var principal = new ClaimsPrincipal(identity);
        Thread.CurrentPrincipal = principal;
        // Inside of ASP.NET this is required
        if (context.HttpContext != null)
            context.HttpContext.User = principal;
        await next();
    }
}

CoreDialogModule

The CoreDialogModule class encapsulates a set of Autofac registrations needed to provide the Bot Builder and Bot Connector with the credentials it needs.

public class CoreDialogModule : Module
 {
     protected override void Load(ContainerBuilder builder)
     {
         // Allow the filter to be resolved
         builder
             .RegisterType<CoreBotAuthententicationAttribute>()
             .InstancePerLifetimeScope();

         // Allow for BotId to be resolved from the options object
         builder.Register(ctx =>
         {
             var botOptions = ctx.Resolve<BotOptions>();
             return new Microsoft.Bot.Builder.Dialogs.Internals.BotIdResolver(botOptions?.Authentication?.MicrosoftAppId);
         })
         .AsImplementedInterfaces()
         .AsSelf()
         .SingleInstance();

         // Allow for credentials to come from Bot Options
         builder
             .Register(ctx =>
             {
                 var botOptions = ctx.Resolve<BotOptions>();
                 return new MicrosoftAppCredentials(botOptions.Authentication.MicrosoftAppId,
                     botOptions.Authentication.MicrosoftAppPassword);
             })
             .AsSelf()
             .SingleInstance();
     }
 }

Startup Changes

Below are the changes to Startup to get this all working.

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }
    public IContainer ApplicationContainer { get; private set; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services
            .AddMvc()
        // The configuration below applies the JSON serialization settings the BOT framework expects
            .AddJsonOptions(options =>
            {
                options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                options.SerializerSettings.Formatting = Formatting.Indented;
                JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver(),
                    Formatting = Newtonsoft.Json.Formatting.Indented,
                    NullValueHandling = NullValueHandling.Ignore,
                };
            });

        services.ConfigurePoco<BotOptions>(Configuration.GetSection("Bot"));

        var builder = new ContainerBuilder();
        builder.Populate(services);
        builder.RegisterModule(new ReflectionSurrogateModule());
        builder.RegisterModule(new CoreDialogModule());
        builder.Update(Conversation.Container);
        ApplicationContainer = Conversation.Container;
        return new AutofacServiceProvider(ApplicationContainer);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        // If you want to dispose of resources that have been resolved in the
        // application container, register for the "ApplicationStopped" event.
        lifetime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
        app.UseMvc();
    }
}

echobotforcore's People

Contributors

damianreeves avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.