Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure cors to allow all subdomains using ASP.NET Core (Asp.net 5, MVC6, VNext)

I have cors setup correctly in an ASP.NET Core web app. Im using the following package...

"Microsoft.AspNet.Cors": "6.0.0-rc1-final" 

and here is the startup.cs snippet...

public virtual IServiceProvider ConfigureServices(IServiceCollection services) {     services.AddCors     (         options =>         {             options.AddPolicy             (                 CORSDefaults.PolicyName,                  builder =>                 {                     //From config...                     var allowedDomains = new []{"http://aaa.somewhere.com","https://aaa.somewhere.com","http://bbb.somewhere.com","https://bbb.somewhere.com"};                      //Load it                     builder                         .WithOrigins(allowedDomains)                         .AllowAnyHeader()                         .AllowAnyMethod()                         .AllowCredentials();                 }             );         }     ); } 

This works great except that the list of subdomains to allow is growing fast and I want to allow all subdomains of "somewhere.com". Something like "*.somewhere.com". I cant seem to find any documentation on how to do this in the new ASP.NET Core (MVC6, ASP.NET5, VNext). All the docs/examples I'm finding that demonstrate how to do this are for earlier versions of MVC or WebApi. How can I achieve this in the new stack?

like image 741
sjdirect Avatar asked Apr 26 '16 23:04

sjdirect


2 Answers

This has now been implemented in version 2.0.0. In your ConfigureServices use the following:

options.AddPolicy("MyCorsPolicy",    builder => builder       .SetIsOriginAllowedToAllowWildcardSubdomains()       .WithOrigins("https://*.mydomain.com")       .AllowAnyMethod()       .AllowCredentials()       .AllowAnyHeader()       .Build()    ); 

Also, don't forget to call UseCors in your Configure call too:

app.UseCors("MyCorsPolicy"); 
like image 193
Lloyd Powell Avatar answered Sep 21 '22 00:09

Lloyd Powell


I submitted a pull request to the ASP.NET team with this change so hopefully it will make it into the nuget package. Until then, I use this workaround.

Below you register cors as usual with the exception of having to register the WildCardCorsService class in the di container.

public virtual IServiceProvider ConfigureServices(IServiceCollection services) {     services.TryAdd(ServiceDescriptor.Transient<ICorsService, WildCardCorsService>());     services.AddCors     (         options =>         {             options.AddPolicy             (                 CORSDefaults.PolicyName,                  builder =>                 {                     builder                         .WithOrigins("http://*.withwildcardsubdomain.com", "http://nowildcard.com")                         .AllowAnyHeader()                         .AllowAnyMethod()                         .AllowCredentials();                 }             );         }     ); } 

Save this class locally in your solution. It is a copy and edit of the Microsoft.AspNet.Cors.CorsService.cs class to allow it to handle wildcard subdomains. If it finds a wildcard char '*' it will check if the root domain matches on allowed origins and actual origin. It does NOT support partial wildcard matching.

namespace Microsoft.AspNet.Cors.Infrastructure {     /// <summary>     /// This ICorsService should be used in place of the official default CorsService to support origins      /// like http://*.example.comwhich will allow any subdomain for example.com     /// </summary>     public class WildCardCorsService : ICorsService     {         private readonly CorsOptions _options;          /// <summary>         /// Creates a new instance of the <see cref="CorsService"/>.         /// </summary>         /// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>         public WildCardCorsService(IOptions<CorsOptions> options)         {             if (options == null)             {                 throw new ArgumentNullException(nameof(options));             }              _options = options.Value;         }          /// <summary>         /// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in         /// <paramref name="context"/>.         /// </summary>         /// <param name="requestContext"></param>         /// <param name="policyName"></param>         /// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be         /// used by the caller to set appropriate response headers.</returns>         public CorsResult EvaluatePolicy(HttpContext context, string policyName)         {             if (context == null)             {                 throw new ArgumentNullException(nameof(context));             }              var policy = _options.GetPolicy(policyName);             return EvaluatePolicy(context, policy);         }          /// <inheritdoc />         public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)         {             if (context == null)             {                 throw new ArgumentNullException(nameof(context));             }              if (policy == null)             {                 throw new ArgumentNullException(nameof(policy));             }              var corsResult = new CorsResult();             var accessControlRequestMethod = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlRequestMethod];             if (string.Equals(context.Request.Method, Microsoft.AspNet.Cors.Infrastructure.CorsConstants.PreflightHttpMethod, StringComparison.Ordinal) &&                 !StringValues.IsNullOrEmpty(accessControlRequestMethod))             {                 EvaluatePreflightRequest(context, policy, corsResult);             }             else             {                 EvaluateRequest(context, policy, corsResult);             }              return corsResult;         }          public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)         {             var origin = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.Origin];             if (!OriginIsAllowed(origin, policy))             {                 return;             }              AddOriginToResult(origin, policy, result);             result.SupportsCredentials = policy.SupportsCredentials;             AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);         }          public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)         {             var origin = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.Origin];             if (!OriginIsAllowed(origin, policy))             {                 return;             }              var accessControlRequestMethod = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlRequestMethod];             if (StringValues.IsNullOrEmpty(accessControlRequestMethod))             {                 return;             }              var requestHeaders =                 context.Request.Headers.GetCommaSeparatedValues(Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlRequestHeaders);              if (!policy.AllowAnyMethod && !policy.Methods.Contains(accessControlRequestMethod))             {                 return;             }              if (!policy.AllowAnyHeader &&                 requestHeaders != null &&                 !requestHeaders.All(header => Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase) ||                                               policy.Headers.Contains(header, StringComparer.OrdinalIgnoreCase)))             {                 return;             }              AddOriginToResult(origin, policy, result);             result.SupportsCredentials = policy.SupportsCredentials;             result.PreflightMaxAge = policy.PreflightMaxAge;             result.AllowedMethods.Add(accessControlRequestMethod);             AddHeaderValues(result.AllowedHeaders, requestHeaders);         }          /// <inheritdoc />         public virtual void ApplyResult(CorsResult result, HttpResponse response)         {             if (result == null)             {                 throw new ArgumentNullException(nameof(result));             }              if (response == null)             {                 throw new ArgumentNullException(nameof(response));             }              var headers = response.Headers;              if (result.AllowedOrigin != null)             {                 headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;             }              if (result.VaryByOrigin)             {                 headers["Vary"] = "Origin";             }              if (result.SupportsCredentials)             {                 headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowCredentials] = "true";             }              if (result.AllowedMethods.Count > 0)             {                 // Filter out simple methods                 var nonSimpleAllowMethods = result.AllowedMethods                     .Where(m =>                         !Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleMethods.Contains(m, StringComparer.OrdinalIgnoreCase))                     .ToArray();                  if (nonSimpleAllowMethods.Length > 0)                 {                     headers.SetCommaSeparatedValues(                         Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowMethods,                         nonSimpleAllowMethods);                 }             }              if (result.AllowedHeaders.Count > 0)             {                 // Filter out simple request headers                 var nonSimpleAllowRequestHeaders = result.AllowedHeaders                     .Where(header =>                         !Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))                     .ToArray();                  if (nonSimpleAllowRequestHeaders.Length > 0)                 {                     headers.SetCommaSeparatedValues(                         Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowHeaders,                         nonSimpleAllowRequestHeaders);                 }             }              if (result.AllowedExposedHeaders.Count > 0)             {                 // Filter out simple response headers                 var nonSimpleAllowResponseHeaders = result.AllowedExposedHeaders                     .Where(header =>                         !Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleResponseHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))                     .ToArray();                  if (nonSimpleAllowResponseHeaders.Length > 0)                 {                     headers.SetCommaSeparatedValues(                         Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlExposeHeaders,                         nonSimpleAllowResponseHeaders);                 }             }              if (result.PreflightMaxAge.HasValue)             {                 headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlMaxAge]                     = result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);             }         }          protected virtual bool OriginIsAllowed(string origin, CorsPolicy policy)         {             if (!string.IsNullOrWhiteSpace(origin) &&                 (policy.AllowAnyOrigin ||                  policy.Origins.Contains(origin) ||                  IsWildCardSubdomainMatch(origin, policy)))                 return true;              return false;         }          private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)         {             if (policy.AllowAnyOrigin)             {                 if (policy.SupportsCredentials)                 {                     result.AllowedOrigin = origin;                     result.VaryByOrigin = true;                 }                 else                 {                     result.AllowedOrigin = Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AnyOrigin;                 }             }             else             {                 result.AllowedOrigin = origin;             }         }          private static void AddHeaderValues(IList<string> target, IEnumerable<string> headerValues)         {             if (headerValues == null)             {                 return;             }              foreach (var current in headerValues)             {                 target.Add(current);             }         }          private bool IsWildCardSubdomainMatch(string origin, CorsPolicy policy)         {             var actualOriginUri = new Uri(origin);             var actualOriginRootDomain = GetRootDomain(actualOriginUri);              foreach (var o in policy.Origins)             {                 if (!o.Contains("*"))                     continue;                  // 1) CANNOT USE System.Text.RegularExpression since it does not exist in .net platform 5.4 (which the Microsoft.AspNet.Cors project.json targets)                 // 2) '*' char is not valid for creation of a URI object so we replace it just for this comparison                 var allowedOriginUri = new Uri(o.Replace("*", "SOMELETTERS"));                 if (allowedOriginUri.Scheme == actualOriginUri.Scheme &&                     actualOriginRootDomain == GetRootDomain(allowedOriginUri))                     return true;             }              return false;         }          private string GetRootDomain(Uri uri)         {             //Got this snippet here http://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net             var host = uri.Host;             int index = host.LastIndexOf('.'), last = 3;              while (index > 0 && index >= last - 3)             {                 last = index;                 index = host.LastIndexOf('.', last - 1);             }              return host.Substring(index + 1);         }     }      /// <summary>     /// Needed to copy these in since some of them are internal to the Microsoft.AspNet.Cors project     /// </summary>     public static class CorsConstants     {         /// <summary>The HTTP method for the CORS preflight request.</summary>         public static readonly string PreflightHttpMethod = "OPTIONS";         /// <summary>The Origin request header.</summary>         public static readonly string Origin = "Origin";         /// <summary>         /// The value for the Access-Control-Allow-Origin response header to allow all origins.         /// </summary>         public static readonly string AnyOrigin = "*";         /// <summary>The Access-Control-Request-Method request header.</summary>         public static readonly string AccessControlRequestMethod = "Access-Control-Request-Method";         /// <summary>The Access-Control-Request-Headers request header.</summary>         public static readonly string AccessControlRequestHeaders = "Access-Control-Request-Headers";         /// <summary>The Access-Control-Allow-Origin response header.</summary>         public static readonly string AccessControlAllowOrigin = "Access-Control-Allow-Origin";         /// <summary>The Access-Control-Allow-Headers response header.</summary>         public static readonly string AccessControlAllowHeaders = "Access-Control-Allow-Headers";         /// <summary>The Access-Control-Expose-Headers response header.</summary>         public static readonly string AccessControlExposeHeaders = "Access-Control-Expose-Headers";         /// <summary>The Access-Control-Allow-Methods response header.</summary>         public static readonly string AccessControlAllowMethods = "Access-Control-Allow-Methods";         /// <summary>The Access-Control-Allow-Credentials response header.</summary>         public static readonly string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";         /// <summary>The Access-Control-Max-Age response header.</summary>         public static readonly string AccessControlMaxAge = "Access-Control-Max-Age";         internal static readonly string[] SimpleRequestHeaders = new string[4]         {       "Origin",       "Accept",       "Accept-Language",       "Content-Language"         };         internal static readonly string[] SimpleResponseHeaders = new string[6]         {       "Cache-Control",       "Content-Language",       "Content-Type",       "Expires",       "Last-Modified",       "Pragma"         };         internal static readonly string[] SimpleMethods = new string[3]         {       "GET",       "HEAD",       "POST"         };     } } 

Enjoy!

like image 39
sjdirect Avatar answered Sep 23 '22 00:09

sjdirect