allBlogsList

Sitecore CORS Enable Access-Control-Allow-Credentials on Web API controllers

Cross Origin Resources Sharing (CORS) is a way to allow restricted resources on a web page to be requested from a domain outside of the domain from which the original resource was served.

In this post, I will explore how to dynamically allow Web API controllers to respect a Sitecore API key and allow the Access-Control-Allow-Credentials = true header to be sent.

Sitecore offers three methods of configuring CORS: https://doc.sitecore.com/developers/91/sitecore-experience-manager/en/cross-origin-resource-sharing--cors-.html

  1. In a web config
  2. Using an API Key
  3. Using the EnableCors attribute

The issue with #1 and #3 is that you cannot dynamically change the origins like you can with method #2. The issue with method #2 is that it does not output Access-Control-Allow-Credentials which would be needed for cross site cookies. For MVC controllers we have EnableApiKeyCors decorator to allow for this. We will borrow the same logic for Web API controllers.

Custom Attribute

using Sitecore.DependencyInjection;
using Sitecore.LayoutService.Mvc.Security;
using Sitecore.Services.Core.Model;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Cors;
using System.Web.Http.Cors;

namespace MyNamespace.Attributes.WebApi
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
    public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider
    {
        protected readonly IApiKeyResolver ApiKeyResolver;

        public MyCorsPolicyAttribute()
            : this(ServiceLocator.ServiceProvider.GetService(typeof(IApiKeyResolver)) as IApiKeyResolver)
        {            
        }

        public MyCorsPolicyAttribute(IApiKeyResolver apiKeyResolver)
        {
            ApiKeyResolver = apiKeyResolver;
        }

        public Task GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // Grab the existing API key resolver
            ApiKeyData apiKeyData;
            ApiKeyResolver.ResolveApiKey(new HttpContextWrapper(HttpContext.Current).Request, out apiKeyData);

            // grab the requested origin
            // for Access-Control-Allow-Credentials it's needed that we match
            // the requested origin rather than *
            string header = HttpContext.Current.Request.Headers["Origin"];

            // checking if the key exist and there are CORS origin specified
            // and the CORS is wild card or matches the origin request
            if (apiKeyData == null || !apiKeyData.CorsOrigins.Any()
                || !apiKeyData.CorsOrigins.Contains("*")
                   && !apiKeyData.CorsOrigins.Contains(header, StringComparer.OrdinalIgnoreCase))
            {
                return Task.FromResult(new CorsPolicy());
            }

            // feel free to change flags
            var allowPolicy = new CorsPolicy
            {
                AllowAnyMethod = true,
                AllowAnyHeader = true,
                SupportsCredentials = true,
            };

            // add apiKey setting to origins list
            foreach(var domain in apiKeyData.CorsOrigins)
            {
                allowPolicy.Origins.Add(domain);
            }
            return Task.FromResult(allowPolicy);
        }
    }
}

Usage

[MyCorsPolicy]
public class MyController : ServicesApiController