Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you restrict requests so they only work if it is accessed by a specific domain

I have this ASP.NET Web API and I want to restrict access so it only works when called by specific host. I cannot, for what I know until now, secure it by token, because the WEB API url will be a postback url for a system that will call it automatically when a certain action is made. I have checked out CORS, but what CORS seems to do is to allow a specific domain to access the API. So, does this mean that my WEB API is already restricted for other domains? Then why I can access it by Postman locally, even if it is hosted in Azure?

I just want my service to allow calls from localhost and another specific domain only.

How do I achieve this?

Thanks!

like image 865
SamyCode Avatar asked Jun 04 '21 23:06

SamyCode


2 Answers

One possibility is to use a custom authorization filter that creates an HTTP response with a failure status code like 400 bad request or 404 not found if the requests has a host that is not allowed. We could define an authorization filter named RestrictDomain that looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

public class RestrictDomainAttribute : Attribute, IAuthorizationFilter
{
    public IEnumerable<string> AllowedHosts { get; }

    public RestrictDomainAttribute(params string[] allowedHosts) => AllowedHosts = allowedHosts;

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //  Get host from the request and check if it's in the enumeration of allowed hosts
        string host = context.HttpContext.Request.Host.Host;
        if (!AllowedHosts.Contains(host, StringComparer.OrdinalIgnoreCase))
        {
            //  Request came from an authorized host, return bad request
            context.Result = new BadRequestObjectResult("Host is not allowed");
        }
    }
}

If you want to apply the RestrictDomain filter globally, then you can add the filter in the Startup.cs file like this:

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(options =>
        {
            //  Add the restrict domain filter globally
            //  You could read the allowed hosts from a config file, here we hard code them
            options.Filters.Add(new RestrictDomainAttribute("localhost", "example.com"));
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        app.UseEndpoints(endpoints => endpoints.MapControllers());
    }
}

With this setting, if I remove "localhost" from the constructor and only allow "example.com", I get a 400 request when I use Postman since the host will be localhost.

Another option is to use the filter in a controller or controller action directly since we configure it to work as an attribute. However, the allowed hosts will have to be constant values instead of values that can be calculated at runtime. Here's an example:

using Microsoft.AspNetCore.Mvc;

[Route("")]
[ApiController]
public class HomeController : ControllerBase
{
    [HttpGet]
    public ContentResult Index() => Content("Home");

    [HttpGet("greeting")]
    [RestrictDomain("localhost", "example.com")] // values must be constants
    public ContentResult Greeting() => Content("Hello, World!");
}

If you don't want constant values and don't want to apply the filter globally, then you could inject the IConfiguration into the filter to read the allowed hosts that can access the resource.

like image 124
Jossean Yamil Avatar answered Oct 27 '22 02:10

Jossean Yamil


CORS on its own only tells the browser / client apps to stop sending requests, in many ASP.Net Web API implementation it doesn't actually block the request pipeline. This is why Postman works, Postman doesn't execute the same pre-flight OPTIONS check first, it just send the request.

You should absolutely look into adding authentication to your API, Bearer token based authentication works well enough with and between APIs. Have a read over Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2, but that is out of scope for this question.

At a conceptual level, you just need to intercept the incoming request in the OWIN pipeline before the API middleware and request the request if it doesn't match your rules. This should be using in conjunction with CORS so that browsers respond in a standard way.

For background, have a read over Block or limit unwanted traffic to Asp.Net Web Application. Usually we manage domain or IP level security filtering in the hosting architecture or routing. IIS or Azure hosts have many built in policies to help manage this.

You could implement this at a global level by adding the following OWIN request processor:

public void ConfigureOAuth(IAppBuilder app)
{
    app.Use((context, next) =>
    {
        string[] AllowedDomains = new string[] { "::1", "localhost", "mybusinessdomain.com" };

        if (!AllowedDomains.Contains(context.Request.Host.Value.ToLower()))
        {
            context.Response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
            return System.Threading.Tasks.Task.FromResult<object>(null);
        }

        return next();
    });

    // TODO: add in your other OWIN configuration AFTER the request filter.
}

If you are NOT using the OWIN hosting pipeline and you want to manage this globally then you could put a check into the global.asax.cs toi handle the Application_BeginRequest:

   private static readonly string[] AllowedDomains = new string[] { "::1", "localhost", "mybusinessdomain.com" };

   protected void Application_BeginRequest(Object sender, EventArgs e)
   {
       if >(!AllowedDomains.Contains(HttpContext.Current.Request.UserHostName.ToLower()))
       {
           HttpContext.Current.Response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
           HttpContext.Current.Response.End();

           // or transfer to a specific page
           // Server.Transfer("~/banned.aspx");
       }
   }
like image 3
Chris Schaller Avatar answered Oct 27 '22 01:10

Chris Schaller