Sang Suantak
Sang Suantak

Reputation: 5265

How to protect open/public Web API endpoints

I have a few Web API endpoints with no authentication/authorization since it could be used for guest users as well. These endpoints will be consumed directly through XHR/Ajax/JS. However, i would like to allow the request from only a few origins. For this, i've used the Cors middleware like below:

ConfigureServices Method

services.AddCors(options =>
{
    options.AddPolicy("AllowSpecific", builder => 
        builder.WithOrigins("http://localhost:55476")
            .AllowAnyHeader()
            .AllowAnyMethod());
});

Configure Method

app.UseCors("AllowSpecific");

This restriction works for requests coming from browsers. However, if the request is coming from Http Clients such as Postman, Fiddler, etc., the request goes through.

Is there any way to tackle such scenarios?

Upvotes: 3

Views: 1599

Answers (1)

Sang Suantak
Sang Suantak

Reputation: 5265

For lack of a better alternative for now, i've replaced CORS middleware with a custom middleware which will check each request's header Origin and allow/restrict based on configuration. This works both for cross-browser requests and HTTP Client requests.

Middleware

public class OriginRestrictionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;
    private readonly ILogger _logger;

    public OriginRestrictionMiddleware(RequestDelegate next, IConfiguration configuration, ILoggerFactory loggerFactory)
    {
        _next = next;
        _configuration = configuration;
        _logger = loggerFactory.CreateLogger<OriginRestrictionMiddleware>();
    }

    public Task Invoke(HttpContext context)
    {
        try
        {
            var allowedOriginsConfig = _configuration.GetSection("AllowedOrigins").Value;

            var allowedOrigins = allowedOriginsConfig.Split(',');

            _logger.LogInformation("Allowed Origins: " + allowedOriginsConfig);

            var originHeader = context.Request.Headers.Where(h => h.Key == "Origin");
            if (originHeader.Any())
            {
                var requestOrigin = originHeader.First().Value.ToString();

                _logger.LogInformation("Request Origin: " + requestOrigin);

                foreach (var origin in allowedOrigins)
                {
                    //if(origin.StartsWith(requestOrigin))
                    if (requestOrigin.Contains(origin))
                    {
                        return _next(context);
                    }
                }
            }

            context.Response.StatusCode = 401;
            return context.Response.WriteAsync("Not Authorized");
        }
        catch(Exception ex)
        {
            _logger.LogInformation(ex.ToString());
            throw;
        }
    }
}

public static class OriginRestrictionMiddlewareExtension
{
    public static IApplicationBuilder UseOriginRestriction(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<OriginRestrictionMiddleware>();
    }
}

Startup Configuration

app.UseOriginRestriction();

AppSettings.json

"AllowedOrigins": "http://localhost:55476,http://localhost:55477,chrome-extension"

chrome-extension entry is there to allow request from Postman during development. It will be removed when deployed to server.

I suspect that this solution can also be bypassed one way or another. However, i'm hoping it will work for most of the cases.

Upvotes: 1

Related Questions