DEV Community

Cover image for How to secure public APIs in ASP.NET Core?
Harshal Suthar
Harshal Suthar

Posted on • Originally published at ifourtechnolab.com

How to secure public APIs in ASP.NET Core?

ASP.NET Core is a popular structure. Its key advantages include features such as cross-platform execution, high performance, built-in dependency injection and modular HTTP request pipeline.

Challenges

The ASP.NET Core provides support for many authentication providers to secure applications through numerous authentication workflows. However, in many scenarios, we have to provide a web application/site that is based on an unauthenticated API with anonymous access.

For example, we have a list of products in the database and we want to display these products on a web page. We can write an API to provide a list of products and the front end (web site) can receive this list through the API and display it on our public products web page.

Without applying a level of security, such architectures can be an open security vulnerability to exploitation.

Available Security Controls in ASP.NET

ASP.NET Provides solutions for common vulnerabilities including core

  • Cross-site scripting
  • SQL injection,
  • Cross-Site Request Forgery (CSRF)
  • Open redirects

Going a step further

As developers, we should also protect our applications from other common attack vectors including

  • Distributed denial-of-service (DDOS)
  • Denial-of-service (DOS)
  • Bulk data egress
  • Probe response
  • Scraping

The two steps we can take careof to verify the referrer header and rate-limiting, discussed below in detail.

Use IP based request limit action filter

We can limit customers to a certain number of requests over a specified period of time to prevent malicious bot attacks.We havecreated IP based requestlimitactionfilter in the ASP.NET Core. Keep in mind that multiple clients can sit behind a single IP address so you can meet this within your limits, or combine the IP address with other request data to make requests more unique.

To try the filter, you just need to add an ActionAttribute at the top of the controller action.

Read More: Asp.net Core Api With Entity Framework

[HttpGet()]
[ValidateReferrer]
[RequestLimit("Test-Action", NoOfRequest = 3, Seconds = 10)]
publicasync Task<actionresult>GetAsync(CancellationTokenct)
{
// code here  
}</actionresult>

Enter fullscreen mode Exit fullscreen mode

Here is the implementation of the filter:

      namespace Security.Api.Filters
{
    using System;
    using System.Net;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Caching.Memory;
    [AttributeUsage(AttributeTargets.Method)]
    public class RequestAttribute :ActionFilterAttribute
    {
        public RequestAttribute(string name)
        {
            Name = name;
        }
        public string Name
        {
            get;
        }
        public intNoOfRequest
        {
            get;
            set;
        } = 1;
        public int Seconds
        {
            get;
            set;
        } = 1;
        private static MemoryCachememoryCache
        {
            get;
        } = new MemoryCache(new MemoryCacheOptions());
        public override void OnActionExecuting(ActionExecutingContext context)
        {
varipAddress = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
varmemoryCacheKey = $ "{Name}-{ipAddress}";
memoryCache.TryGetValue(memoryCacheKey, out intprevReqCount);
            if (prevReqCount>= NoOfRequest)
            {
context.Result = new ContentResult
                {
                    Content = $ "Request is exceeded. Try again in seconds.",
                };
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
            }
            else
            {
varcacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds));
memoryCache.Set(memoryCacheKey, (prevReqCount + 1), cacheEntryOptions);
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Add referrercheck action filter

To protect the API from abuse and to provide additional protection against Cross-Site Request Forgery (CSRF) attacks, security checks are performed on the request referrer header for each REST API request sent to the server.

This API validates where the request comes from. We have created a Referrer Check Action Filter in ASP.NET Core. It prevents access to tools like POSTMEN, REST client, etc.

You just need to do is add an ActionAttribute to the top of the controller Action.

[HttpGet()]
[ValidateReferrer]
publicasync Task<actionresult>GetAsync(CancellationTokenct)
{
// your code here  
}</actionresult>

Enter fullscreen mode Exit fullscreen mode

Here is the implementation of the filter

namespace Security.Api.Filters
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Configuration;
    using System;
    using System.Linq;
    using System.Net;
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class ValidateAttribute :ActionFilterAttribute
    {
        private IConfiguration _configuration;
        public ValidateAttribute() { }
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            _configuration = (IConfiguration)context.HttpContext.RequestServices.GetService(typeof(IConfiguration));
base.OnActionExecuting(context);
            if (!IsValidRequest(context.HttpContext.Request))
            {
context.Result = new ContentResult
                {
                    Content = $ "Invalid header"
                };
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.ExpectationFailed;
            }
        }
        private bool IsValidRequest(HttpRequest request)
        {
            string referrerURL = "";
            if (request.Headers.ContainsKey("Referer"))
            {
referrerURL = request.Headers["Referer"];
            }
            if (string.IsNullOrWhiteSpace(referrerURL)) return false;
           //Allows to check customer list
varUrls = _configuration.GetSection("CorsOrigin").Get<string[]>()?.Select(url => new Uri(url).Authority).ToList();
            //add current host for swagger calls    
var host = request.Host.Value;
Urls.Add(host);
            bool isValidClient = Urlsl.Contains(new Uri(referrerURL).Authority);
            // comapre with base uri
            return isValidClient;
        }
    }
}
</string[]>

Enter fullscreen mode Exit fullscreen mode

Searching for Dedicated ASP.Net Core Web Developer? Your Search ends here.

Add DoSattack middleware

If you have the auto scale configured, DOS attacks overwhelm your APIs, making them unauthorized and/or expensive. There are various ways to avoid this problem by request throttling. There is an option here to use intermediaries to restrict the number of requests from particulate client IP addresses.

Below is the code for DosAttackMiddleware.cs

 namespace Security.Api.Middlewares
{
    using Microsoft.AspNetCore.Http;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
    using System.Timers;
    public sealed class DosAttackMiddleware
    {
        private static IDictionary _IpAdresses = new Dictionary();
        private static Stack _Banded = new Stack();
        private static Timer _Timer = CreateTimer();
        private static Timer _BannedTimer = CreateBanningTimer();
        private
        const int BANNED_REQUESTS = 10;
        private
        const int REDUCTION_INTERVAL = 1000;
        private
        const int RELEASE_INTERVAL = 3 * 60 * 1000; // 3 minutes    
        private RequestDelegate _next;
        public DosAttackMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task InvokeAsync(HttpContexthttpContext)
        {
            string ip = httpContext.Connection.RemoteIpAddress.ToString();
            if (_Banned.Contains(ip))
            {
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            }
CheckIpAddress(ip);
            await _next(httpContext);
        }
        private static void CheckIpAddress(string ip)
        {
            if (!_IpAdresses.ContainsKey(ip))
            {
                _IpAdresses[ip] = 1;
            }
            else if (_IpAdresses[ip] == BANNED_REQUESTS)
            {
                _Banned.Push(ip);
                _IpAdresses.Remove(ip);
            }
            else
            {
                _IpAdresses[ip]++;
            }
        }
        private static Timer CreateTimer()
        {
            Timer timer = GetTimer(REDUCTION_INTERVAL);
timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
            return timer;
        }
        private static Timer CreateTimer()
        {
            Timer timer = GetTimer(RELEASE_INTERVAL);
timer.Elapsed += delegate {
                if (_Banned.Any()) _Banned.Pop();
            };
            return timer;
        }
        private static Timer GetTimer(int interval)
        {
            Timer timer = new Timer();
timer.Interval = interval;
timer.Start();
            return timer;
        }
        private static TimerElapsed(object sender, ElapsedEventArgs e)
        {
            foreach (string key in _IpAdresses.Keys.ToList())
            {
                _IpAdresses[key]--;
                if (_IpAdresses[key] == 0) _IpAdresses.Remove(key);
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

An unauthorized API is open to abuse. We should prevent the explicit attack vector by adding additional code. Hopefully this blog makes it easier to enforce these restrictions while making the lives of these attackers more difficult.

Top comments (1)

Collapse
 
artydev profile image
artydev

Great than you