IP address filtering from C# code in ASP.NET MVC and Web Api

Restrict or allow specific IP addresses to access your WebApi or MVC

Last year I wrote an article about IP address filtering on MVC and WebApi Restrict Access to an MVC Action or Controller based on IP address. Over time the requirements for IP filtering in several web based application increased, so I had t work on this class to make it more extendible and reusable in different scenarios.

The new scenarios I wanted to cover are:

  • Allowing list of IP addresses to access the controller or controller action
  • Restricting list of IP addresses to access the controller or controller action
  • Allowing list of IP address ranges to access the controller or controller action
  • Restricting list of IP address ranges to access the controller or controller action

The class which I mentioned above, for sure did not cover these four scenarios, so I had to come up with the way to maintain basic functionality, extend with the new functionality and make the class reusable again in code.

For this I had to make some additional classes. Since there is no .NET class for IP address range out of the box, I decided too create a simple POCO class which will represent the IP address range.

    public class IPAddressRange
    {
        private IPAddress startIPAddress;
        private IPAddress endIPAddress;

        public IPAddress StartIPAddress
        {
            get
            {
                return this.startIPAddress;
            }
        }

        public IPAddress EndIPAddress
        {
            get
            {
                return this.endIPAddress;
            }
        }

        public IPAddressRange(string startIPAddress, string endIPAddress)
            : this(IPAddress.Parse(startIPAddress), IPAddress.Parse(endIPAddress))
        {

        }
        public IPAddressRange(IPAddress startIPAddress, IPAddress endIPAddress)
        {
            this.startIPAddress = startIPAddress;
            this.endIPAddress = endIPAddress;
        }
    }
    

Since I wanted to cover scenarios for both allowing and restricting the IP addresses as list or IP address ranges as list and as well as creating the restriction which applies the same rule, the best way was to pass those lists in a constructor. The problem is that I would not be able to create two constructors for blocking the list and ranges and two constructor for allowing list and ranges because they would have the same signature as they should receive same type of element lists.

The resolution for this I found in using one more property for the main class which was the enumeration value

    public enum IPAddressFilteringAction
    {
        Allow,
        Restrict
    }
    

This enumeration value will allow me to apply the logic for either blocking or allowing the request IP address.

Since I wanted to add the IP range validation as well and checking an IP against the IP range is common I implemented it as an extension method to IPAddress class.

   public static class IPAddressExtensions
    {
        public static bool IsInRange(this IPAddress address, IPAddress start, IPAddress end)
        {

            AddressFamily addressFamily = start.AddressFamily;
            byte[] lowerBytes = start.GetAddressBytes();
            byte[] upperBytes = end.GetAddressBytes();

            if (address.AddressFamily != addressFamily)
            {
                return false;
            }

            byte[] addressBytes = address.GetAddressBytes();

            bool lowerBoundary = true, upperBoundary = true;

            for (int i = 0; i < lowerBytes.Length &&
                (lowerBoundary || upperBoundary); i++)
            {
                if ((lowerBoundary && addressBytes[i] < lowerBytes[i]) ||
                    (upperBoundary && addressBytes[i] > upperBytes[i]))
                {
                    return false;
                }

                lowerBoundary &= (addressBytes[i] == lowerBytes[i]);
                upperBoundary &= (addressBytes[i] == upperBytes[i]);
            }

            return true;
        }

    }
    

The main class code will be a lot shorter since we took out this method for validation.

Now to start with the main class. As mentioned before, key entry points are constructors. Note that there are no parameterless constructors as each class instance needs to know whether to apply blocking or allowing for either IP address list or IP address ranges list.

The base inherited by our attribute class is AuthorizeAttribute, so we just need to apply our log to override of IsAuthorized method.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http.Controllers;
using System.Web;
using System.Web.Http;

namespace IPAddressFiltering

{
    public class IPAddressFilterAttribute : AuthorizeAttribute
    {
        #region Fields
        private IEnumerable<IPAddress> ipAddresses;
        private IEnumerable<IPAddressRange> ipAddressRanges;
        private IPAddressFilteringAction filteringType;
        #endregion

        #region Properties
        public IEnumerable<IPAddress> IPAddresses
        {
            get
            {
                return this.ipAddresses;
            }
        }

        public IEnumerable<IPAddressRange> IPAddressRanges
        {
            get
            {
                return this.ipAddressRanges;
            }
        }
        #endregion

        #region Constructors
        public IPAddressFilterAttribute(string ipAddress, IPAddressFilteringAction filteringType)
           : this(new IPAddress[] { IPAddress.Parse(ipAddress) }, filteringType)
        {

        }

        public IPAddressFilterAttribute(IPAddress ipAddress, IPAddressFilteringAction filteringType)
            : this(new IPAddress[] { ipAddress }, filteringType)
        {

        }

        public IPAddressFilterAttribute(IEnumerable<string> ipAddresses, IPAddressFilteringAction filteringType)
            : this(ipAddresses.Select(a => IPAddress.Parse(a)), filteringType)
        {

        }

        public IPAddressFilterAttribute(IEnumerable<IPAddress> ipAddresses, IPAddressFilteringAction filteringType)
        {
            this.ipAddresses = ipAddresses;
            this.filteringType = filteringType;
        }

        public IPAddressFilterAttribute(string ipAddressRangeStart, string ipAddressRangeEnd, IPAddressFilteringAction filteringType)
            : this(new IPAddressRange[] { new IPAddressRange(ipAddressRangeStart, ipAddressRangeEnd) }, filteringType)
        {

        }

        public IPAddressFilterAttribute(IPAddressRange ipAddressRange, IPAddressFilteringAction filteringType)
            :this(new IPAddressRange[] { ipAddressRange }, filteringType)
        {

        }

        public IPAddressFilterAttribute(IEnumerable<IPAddressRange> ipAddressRanges, IPAddressFilteringAction filteringType)
        {
            this.ipAddressRanges = ipAddressRanges;
            this.filteringType = filteringType;
        }

        #endregion

        protected override bool IsAuthorized(HttpActionContext context)
        {
            string ipAddressString = ((HttpContextWrapper)context.Request.Properties["MS_HttpContext"]).Request.UserHostName;
            return IsIPAddressAllowed(ipAddressString);
        }

        private bool IsIPAddressAllowed(string ipAddressString)
        {
            IPAddress ipAddress = IPAddress.Parse(ipAddressString);

            if (this.filteringType == IPAddressFilteringAction.Allow)
            {
                if (this.ipAddresses != null && this.ipAddresses.Any() &&
                    !IsIPAddressInList(ipAddressString.Trim()))
                {
                    return false;
                }
                else if (this.ipAddressRanges != null && this.ipAddressRanges.Any() &&
                    !this.ipAddressRanges.Where(r => ipAddress.IsInRange(r.StartIPAddress, r.EndIPAddress)).Any())
                {
                    return false;
                }

            }
            else
            {
                if (this.ipAddresses != null && this.ipAddresses.Any() &&
                   IsIPAddressInList(ipAddressString.Trim()))
                {
                    return false;
                }
                else if (this.ipAddressRanges != null && this.ipAddressRanges.Any() &&
                    this.ipAddressRanges.Where(r => ipAddress.IsInRange(r.StartIPAddress, r.EndIPAddress)).Any())
                {
                    return false;
                }

            }

            return true;

        }

        private bool IsIPAddressInList(string ipAddress)
        {
            if (!string.IsNullOrWhiteSpace(ipAddress))
            {
                IEnumerable<string> addresses = this.ipAddresses.Select(a => a.ToString());
                return addresses.Where(a => a.Trim().Equals(ipAddress, StringComparison.InvariantCultureIgnoreCase)).Any();
            }
            return false;
        }

    }
}

    

This way both blocking or allowing only certain list of IPs or ranges is much more easy to control with a simple custom attribute.

Note

Complete source code can be downloaded from download section of this article page. This is also a GitHub project https://github.com/dejanstojanovic/IP-Address-Filtering so you can always check for updates and improvements on this

Now to use this attribute in ASP.NET MVC controller we only need to decorate our action with attribute with parameters

    public class DataController : ApiController
    {
        [HttpGet]
        [Route("api/data/{recordId}")]
        [IPAddressFilter("94.201.50.212", IPAddressFilteringAction.Restrict)]
        public HttpResponseMessage GetData(int recordId)
        {
            /* Create response logic here */
            return this.Request.CreateResponse<object>(HttpStatusCode.OK, new object());
        }
    }
    

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article