Conditional Middleware in ASP.NET Core

 

        Introduction:

 

                 Middleware in ASP.NET first introduced in OWIN/Katana and became very famous in short time. ASP.NET 5 also support OWIN(and middleware) in slightly different way. Middleware allows you to assemble components in your application pipeline. The first registered component will execute first and then the first component decide whether to pass the request on to the next component in the pipeline. If first component passes to second component then second component execute and decide whether to execute third one and so on. This works great for most scenarios but sometimes you need to execute one or more middleware conditionally. For example, you might want to execute different middleware at debug mode and different middleware at release mode. Similarly, you might also want to execute different middleware depending upon the time and traffic. Another important use-case is using authorization header for api requests and using cookie/openid-connect authentication in normal html page requests. In this blog we will check how to use access token authentication middleware only in api requests and use cookie/openid-connect authentication middleware in html request.         

 

        Description:

 

                   Note that I am using rc1 at the time of writing. We will use IdentityServer4 because it works/support ASP.NET Core and we will use their existing sample. We will remove SampleApi project from the sample. Next, we need to open Mvc Implicit project and add IdentityServer4.AccessTokenValidation in project.json file. Then we will add UseWhen extension method from aspnet-contrib repository. This method will allow us to conditionally invoke middleware(s),   

 

    public static class AppBuilderExtensions
    {
        public static IApplicationBuilder UseWhen(this IApplicationBuilder app
            ,Func<HttpContext, bool> condition
            ,Action<IApplicationBuilder> configuration)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }

            if (condition == null)
            {
                throw new ArgumentNullException(nameof(condition));
            }

            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }

            var builder = app.New();
            configuration(builder);

            return app.Use(next => {
                builder.Run(next);

                var branch = builder.Build();

                return context => {
                    if (condition(context))
                    {
                        return branch(context);
                    }

                    return next(context);
                };
            });
        }
    }

                    Next we just need to add some code in Mvc Implicit Startup class. Here is how our Startup.Configure method look like after update,

 

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseIISPlatformHandler();
            app.UseStaticFiles();

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            Func<HttpContext, bool> isApiRequest = (HttpContext context) => context.Request.Path.ToString().StartsWith("/api/");
            app.UseWhen(context => !isApiRequest(context), appBuilder =>
            {
                app.UseCookieAuthentication(options =>
                {
                    options.AuthenticationScheme = "cookies";
                    options.AutomaticAuthenticate = true;
                });


                app.UseOpenIdConnectAuthentication(options =>
                {
                    options.AuthenticationScheme = "oidc";
                    options.SignInScheme = "cookies";
                    options.AutomaticChallenge = true;

                    options.Authority = "http://localhost:22530/";
                    options.RequireHttpsMetadata = false;

                    options.ClientId = "mvc_implicit";
                    options.ResponseType = "id_token token";

                    options.Scope.Add("profile");
                    options.Scope.Add("email");
                    options.Scope.Add("roles");
                    options.Scope.Add("api1");

                    options.TokenValidationParameters.NameClaimType = "name";
                    options.TokenValidationParameters.RoleClaimType = "role";
                });
            });
            app.UseWhen(context => isApiRequest(context), appBuilder =>
            {
                app.UseIdentityServerAuthentication(options =>
                {
                    options.AuthenticationScheme = "token";
                    options.Authority = "http://localhost:22530/";
                    options.ScopeName = "api1";
                    options.ScopeSecret = "secret";

                    options.AutomaticAuthenticate = true;
                    options.AutomaticChallenge = true;
                });
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

 

                    We are assuming that every api request in our application starts with /api/. If request url starts with /api/ then we will allow identity server authentication(access token validation) middleware to execute, this middleware will check if their is a valid access token included in request authorization header then create and set current request principal. If request url not starts with /api/ then both cookie and openid-connect authentication middleware will be allowed to take part in request execution (means set principal if valid authentication cookie is present or send user to open id connect provider if user is not authorized). We can further limit our api controller to only allow users that have valid user access token (don't allow even if they have valid auth cookie) using,

    [Authorize(ActiveAuthenticationSchemes ="token")]
    public class IdentityController : Controller

   

 

        Summary:

 

                    In this article, I showed how easily you can conditionally execute middleware(s) depending upon different conditions using an extension method provided by aspnet-contrib(thanks to them).

2 Comments

Comments have been disabled for this content.