andrewm
andrewm

Reputation: 2642

Is there a way to dynamically update a list of allowed origins in Web API CORS

I'm trying to enable CORS for certain domains within my .Net Web API application, and am able to do this on Application Start via this code..

public static void Register(HttpConfiguration config)
{
    //below comma separated string is built from database
    var domains = "http://www.myfirstdomain.com,http://www.myseconddomain.co.uk .... about 130 domains more..";
    config.EnableCors(new EnableCorsAttribute(domains, "*", "*"));

However if new domains are added while the application is live, these will not be allowed to post cross domain until the app pool is recycled and this list is built up again.

Is there any way I can update this list during the lifetime of my application? I know I can periodically recycle the app pool but this will cause delays in some requests that I could ideally do without.

I know that I can enable this on the controller method, ie..

[EnableCors("http://domain1.com,http://domain2.com", "*", "*")]
public HttpResponseMessage PostAsync([FromBody] MyRequest myRequest)
{

However again the comma separated parameter has to be declared as a constant, and therefore cannot be dynamic.

Am I missing something blatantly obvious or can anyone think of a decent way of doing this?

EDIT

Here is my attempt at writing my own custom EnableCors attribute..

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class EnableCorsByDomainAttribute : Attribute, ICorsPolicyProvider
{
    private readonly CorsPolicy _policy;

    public EnableCorsByDomainAttribute()
    {
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        var originsString = "http://www.test1.com,http://www.test2.com";
        if (!String.IsNullOrEmpty(originsString))
        {
            foreach (var origin in originsString.Split(','))
            {
                _policy.Origins.Add(origin);
            }
        }
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(_policy);
    }
}

I've then decorated the controller method with

[EnableCorsByDomain]

Upvotes: 11

Views: 8848

Answers (1)

Kiran
Kiran

Reputation: 57969

Yes, Web API CORS provides an extensibility point for this kind of scenario. You can take a look at the section called 'Implementing a custom ICorsPolicyProvider' in the following Web API functional spec document for more details.

namespace System.Web.Http.Cors
{
    public interface ICorsPolicyProvider
    {
        Task GetCorsPolicyAsync(HttpRequestMessage request);
    }
}

Note that the ICorsPolicyProvider is async so that we don’t block the thread on I/O.

Sample Here is a custom implementation of ICorsPolicyProvider that loads the origins from web.config.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class EnableCorsAppSettingsAttribute : Attribute, ICorsPolicyProvider
{
    private CorsPolicy _policy;

    public EnableCorsAppSettingsAttribute(string appSettingOriginKey)
    {
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // loads the origins from AppSettings
        string originsString = ConfigurationManager.AppSettings[appSettingOriginKey];
        if (!String.IsNullOrEmpty(originsString))
        {
            foreach (var origin in originsString.Split(','))
            {
                _policy.Origins.Add(origin);
            }
        }
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

You can apply it on the controller/action just like EnableCorsAttribute.

[EnableCorsAppSettings("internal:origins")]
public class ValuesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    public string Get(int id)
    {
        return "value " + id;
    }
}

And it will read the “internal:origins” appSetting from the web.config.

<appSettings>
  <add key="webpages:Version" value="2.0.0.0" />
  <add key="webpages:Enabled" value="false" />
  <add key="PreserveLoginUrl" value="true" />
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  <add key="internal:origins" value="http://example.com,http://webapisample.azurewebsites.net" />
</appSettings>

Upvotes: 9

Related Questions