How to apply various Security Feature in ASP.NET Core application
How to apply various Security Feature in ASP.NET Core application

In this post, I will show How to apply various Security Feature in ASP.NET Core application.

In today's world, the attacks against internet-exposed web apps are the top cause of data breaches that is why Web application security is crucial. There is various important security measure one should implement in a website or in a web application.

I will show how to implement the most important listed security features in ASP.NET Core application. For this demo, I have used the latest ASP.NET Core 3.1 version. However, you can apply this to a lower version like 2.0, 2.1, and 2.2 as well.

Related Post

Security Feature in ASP.NET Core

Problem Statement

Most of us create ASP.NET Core application using the default template using Visual Studio IDE. The created template does not have the security feature implemented by default.

Missing security feature
Missing security implementation will have this kind of response from an API

Security Feature Implementation

I will show how to implement all the listed security features using Middleware in ASP.NET Core. The complete working code is available in GitHub for your reference.

Create a folder named "Middleware" in your project that keeps us separated for readability.

Now, add a file named CspSettings which should have the properties shown in the below code.

namespace KarthikTechBlog.SecurityFeatures.API
{
    public class CspSettings
    {
        public string Default { get; set; }
        public string Image { get; set; }
        public string Style { get; set; }
        public string Font { get; set; }
        public string Script { get; set; }
        public bool UseHttps { get; set; }
        public bool BlockMixedContent { get; set; }
    }
}

Add a file named MiddlewareExtensions and copy paste the below code.

using Microsoft.AspNetCore.Builder;
using System;

namespace KarthikTechBlog.SecurityFeatures.API
{
    public static class MiddlewareExtensions
    {
        public static IApplicationBuilder UseSecurityHeader(this IApplicationBuilder app)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }

            return app.UseMiddleware(new CspSettings
            {
                Default = "'self' https://*.karthiktechblog.com",
                Image = "'self' data: https://*.karthiktechblog.com",
                Style = "'self' 'unsafe-inline' https://*.karthiktechblog.com",
                Font = "'self' data: * https://*.karthiktechblog.com",
                Script = "'self' 'unsafe-inline' https://*.karthiktechblog.com",
                BlockMixedContent = true,
                UseHttps = true
            });
        }

        public static IApplicationBuilder UseSecurityHeader(
            this IApplicationBuilder app,
            Action configure)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }

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

            var settings = new CspSettings();
            configure(settings);
            return app.UseMiddleware(settings);
        }
    }
}

Now add a file named SecurityHeader and copy the below code to it.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace KarthikTechBlog.SecurityFeatures.API
{
    /// 
    /// A middleware for adding security headers.
    /// 
    public class SecurityHeader
    {
        private readonly RequestDelegate _next;
        private readonly CspSettings _settings;
        private readonly SecurityHeaderService _securityHeaderService;

        private readonly ILogger _logger;

        public SecurityHeader(
            RequestDelegate next,
            ILoggerFactory loggerFactory,
            CspSettings settings)
        {
            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }

            _next = next ?? throw new ArgumentNullException(nameof(next));
            _securityHeaderService = new SecurityHeaderService();
            _logger = loggerFactory.CreateLogger();
            _settings = settings ?? throw new ArgumentNullException(nameof(settings));
        }

        public Task Invoke(HttpContext context)
        {
            if (_settings == null)
            {
                _logger.LogDebug("no csp settings found");
                return _next(context);
            }

            var isOptionsRequest = string.Equals(context.Request.Method, HttpMethods.Options, StringComparison.OrdinalIgnoreCase);
            if (!isOptionsRequest)
            {
                context.Response.OnStarting(OnResponseStarting, context);
                return _next(context);
            }

            return _next(context);
        }

        private Task OnResponseStarting(object state)
        {
            var context = (HttpContext)state;
            try
            {
                _securityHeaderService.ApplyResult(context.Response, _settings);
            }
            catch (Exception exception)
            {
                _logger.LogError(exception, "Failed to set security header");
            }
            return Task.CompletedTask;
        }
    }
}

Add another file named SecurityHeaderService and add the code shown below.

using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Text;

namespace KarthikTechBlog.SecurityFeatures.API
{
    public class SecurityHeaderService
    {
        private readonly string[] RemoveHeader = new[] { "Server" };
        private readonly string CSP_HEADER = "Content-Security-Policy";

         private readonly IDictionary SecurityHeader = new Dictionary
        {
            { "X-Content-Type-Options", "nosniff" },            
            { "X-UA-Compatible", "IE=edge,chrome=1" },
            { "X-Download-Options", "noopen" },
            { "X-Frame-Options", "Deny" },
            { "X-XSS-Protection", "1; mode=block" },
            // The Referer header will be omitted entirely to avoid sending of url to oter domain. eg seding to CDN
            // there is chance that the referer url will have a token or password that you do not want to send to other domain/web server.
            { "Referrer-Policy", "no-referrer" }
        };
        internal void ApplyResult(HttpResponse response, CspSettings settings)
        {
            var headers = response.Headers;

            StringBuilder stb = new StringBuilder();
            if (!string.IsNullOrWhiteSpace(settings.Default))
                stb.Append($"default-src {settings.Default};");
            if (!string.IsNullOrWhiteSpace(settings.Image))
                stb.Append($"img-src {settings.Image};");
            if (!string.IsNullOrWhiteSpace(settings.Style))
                stb.Append($"style-src {settings.Style};");
            if (!string.IsNullOrWhiteSpace(settings.Font))
                stb.Append($"font-src {settings.Font};");
            if (!string.IsNullOrWhiteSpace(settings.Script))
                stb.Append($"script-src {settings.Script};");
            if (settings.BlockMixedContent)
                // block all mixed contents( force to use https )
                stb.Append("block-all-mixed-content;");
            if (settings.UseHttps)
                // redirect to https
                stb.Append("upgrade-insecure-requests;");

            headers[CSP_HEADER] = stb.ToString();
            foreach (var headerValuePair in SecurityHeader)
            {
                headers[headerValuePair.Key] = headerValuePair.Value;
            }

            foreach (var header in RemoveHeader)
            {
                headers.Remove(header);
            }
        }
    }
}

Let's add a static class called ApplicationBuilderExtensions and make use of calling UseSecurityHeaders to set Security headers. This class can go directly to your project.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace KarthikTechBlog.SecurityFeatures.API
{
    /// 
    /// A simple middleware to process the request to use UseSecurityHeaders
    /// 
    public static class ApplicationBuilderExtensions
    {
         /// Security Header middleware
        internal static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app,
            IConfiguration configuration,
            IWebHostEnvironment env)
            => MiddlewareExtensions.UseSecurityHeader(app, (settings) =>
            {
                settings.Default = configuration["SecurityHeader:CSP:default"];
                settings.Image = configuration["SecurityHeader:CSP:image"];
                settings.Style = configuration["SecurityHeader:CSP:style"];
                settings.Font = configuration["SecurityHeader:CSP:font"];
                settings.Script = configuration["SecurityHeader:CSP:script"];
                settings.BlockMixedContent = !env.IsDevelopment();
                settings.UseHttps = !env.IsDevelopment();
            });
    }
}

Few more simple code to be added in our settings file and startup class.

Add the SecurityHeader settings for csp in your environment settings's json file.

 "SecurityHeader": {
    "CSP": {
      "default": "* data: blob: 'unsafe-inline' 'unsafe-eval'",
      "script": "* data: blob: 'unsafe-inline' 'unsafe-eval'"
    }
  }

Add the below in the settings.json file

 "SecurityHeader": {
    "CSP": {
      "default": "'self' https://*.karthiktechblog.com",
      "image": "'self' data: https://*.karthiktechblog.com",
      "style": "'self' 'unsafe-inline' https://*.karthiktechblog.com",
      "font": "'self' data: * https://*.karthiktechblog.com",
      "script": "'self' 'unsafe-inline' https://*.karthiktechblog.com"
    }
  }

don't forget to change karthiktechblog.com to your website name or your organization project domain name.

The final piece of code to consume all the implementation. Go to the Startup class and inside the Configure Method, add app..UseSecurityHeaders(Configuration, env);

To Add HSTS security feature, add the below code.

 if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

Explanation of the above implementation

In this implementation, I have configured the application to use SecurityHeader middleware to include all the required settings like CSP and other security headers. Also, the logic applies all these features on each and every API's response while the response is been prepared. E.g. OnResponseStarting method.

Result after applying Security Feature in ASP.NET Core

How to apply various Security Feature in ASP.NET Core
API response with all the security feature implemented

GitHub Reference

The entire working code is in GitHub. Repository name is SecurityFeature

Conclusion

In this post, I showed how to apply various Security Feature in ASP.NET Core application 3.1. That’s all from this post. If you have any questions or just want to chat with me, feel free to leave a comment below

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights