4/9/16

Web API OWIN CORS Handling No Access-Control-Allow-Origin Header



XMLHttpRequest cannot load http://localhost:7802/token. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:7812' is therefore not allowed access

This error means that there is an attempt to access a resource from a different domain by a web app, and the requested resource does not have any policies that would allow the apps from the origin domain to consume it. This error can be reproduced by loading a web app on a different domain (or port) and making an AJAX call to an API. The code would be something similar to the following:

Client App
API App

http://localhost:51223

http://localhost:61677

$.ajax({
type: "POST",
7/api/contact",
contentType: 'application/json',
data: JSON.stringify(contact),
headers: {
'Authorization': 'Bearer ' + tokenProvider.token,
},
success: function (res) {
    callback();
},
error: function (req, status, error) {
    callbackError();
}
});


[HttpPost]
[ResponseType(typeof(Contact))]
[Route("~/api/contact")]
public IHttpActionResult PostContact
        ([FromBody]Contact contact)
{
    IHttpActionResult result = null;

    if (!ModelState.IsValid)
    {
          result=BadRequest(ModelState);
    }
    else
     {
          //handle request
     }

     return result;
}

When making a CORS request, we need to understand that for every request that we send to a different domain, there may be two requests made to the server, preflight and actual requests. For each of these requests, the server must respond with the Access-Control-Allow-Origin header set with the name of the origin domain.

Preflight Request

A preflight or OPTIONS (HTTP verb) request is created by the browser before the actual request (PUT,POST) is sent for a resource in another domain. The goal is to have the browser and server validate that the other domain has access to that particular resource. This is done by setting the Access-Control-Allow-Origin header with the host or origin domain.

Actual Request

Once the preflight request has a response with the corresponding headers, the browser sends the actual request. For this request, the server also checks the CORS policies and adds the Access-Control-Allow-Origin header with the host domain.

OWINS CORS

The way to implement CORS using OWIN and MVC Web API is by first configuring the oAuth server options with an implementation of OAuthAtuhorizationServerProvider on the Startup class.


[assembly: OwinStartup(typeof(ozkary.Startup))]

namespace ozkary
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }

        private void ConfigureAuth(IAppBuilder app)
        {          
            // Configure the application for OAuth based flow
            var oAuthOptions = new OAuthAuthorizationServerOptions
            {
                Provider = new AuthorizationServerProvider(),
                                
            };

            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthAuthorizationServer(oAuthOptions);
           
        }  
    }
}

The OwinStartup directive allows OWIN to run the StartUp class and inject the AppBuilder instance in the Configuration method. This is what we need in order to configure the custom provider that can handle our token management, user validation and CORS headers. (token and user validation is not in the scope of this article). The implementation of AuthorizationServerProvider is listed below: (listing only the areas that are relevant to support CORS)

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
    {                      
        /// <summary>
        /// match endpoint is called before Validate Client Authentication. we need
        /// to allow the clients based on domain to enable requests
        /// the header
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task MatchEndpoint(OAuthMatchEndpointContext context)
        {
            SetCORSPolicy(context.OwinContext);
            if (context.Request.Method == "OPTIONS")   
            {               
                context.RequestCompleted();
                return Task.FromResult(0);
            }

            return base.MatchEndpoint(context);
        }
       
      
        /// <summary>
        /// add the allow-origin header only if the origin domain is found on the     
        /// allowedOrigin list
        /// </summary>
        /// <param name="context"></param>
        private void SetCORSPolicy(IOwinContext context)
        {
            string allowedUrls = ConfigurationManager.AppSettings["allowedOrigins"];

            if (!String.IsNullOrWhiteSpace(allowedUrls))
            {
                var list = allowedUrls.Split(',');
                if (list.Length > 0)
                {

                    string origin = context.Request.Headers.Get("Origin");
                    var found = list.Where(item => item == origin).Any();
                    if (found){
                        context.Response.Headers.Add("Access-Control-Allow-Origin",
                                                     new string[] { origin });
                    }                   
                }
                
            }
            context.Response.Headers.Add("Access-Control-Allow-Headers"
                                   new string[] {"Authorization", "Content-Type" });
            context.Response.Headers.Add("Access-Control-Allow-Methods"
                                   new string[] {"OPTIONS", "POST" });

        }                
    }


The code above overrides the MatchEndpoint handler. This area of the code is called for every request including the preflight request (OPTIONS). Note that this handler is called before a call is made to Validate Client Authentication. This is where we need to manage the logic of adding the headers using OWIN.  We do this by calling the SetCORSPolicy method.

The SetCORSPolicy method looks for a configuration setting (allowedOrigins) that contains the whitelist of domains that can consume the resources from our API. It then reads the Origin header to get the domain name (including scheme and port i.e. http://mydomain.com:80). If the origin domain is found in the configuration, the Access-Control-Allow-Origin response header is set to the origin domain value. This is what helps us resolve the “No 'Access-Control-Allow-Origin' header is present on the requested resource”.

The SetCORSPolicy method is also a good place to add other response headers that may be required for accessing our API. For example, we may need to add the Access-Control-Allow-(Header and Methods) for additional access. (Not CORS related)

We can use the browser developer tools to take a look at the network request and response headers. When CORS is configured properly, the response headers should look similar to this:


What to Avoid

When using the OWIN middleware to handle our CORS calls, we want to stay away from adding these headers in other areas of the application as this causes a duplicate header exception. Some of the things to avoid include:

  • Do not add these headers on the web.config file
  • Do not use app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll) as this allows all origin domains to have access. We could use this option for public APIs with no restrictions.
  • Do not use the config.EnableCors from WebApiConfig (HttpConfiguration)  as we are already using a middleware to handle this concern. This option can be used when OWIN is not being used and we need to allow CORS
  • When configuring the whitelist for CORS, we need to make sure to include the scheme, domain and port number  (especially when testing with Visual Studio).   http://localhost is not the same as http://localhost:5733 and  www.ozkary .com is not the same as http://ozkary.com.  We need to match the Origin header value

Conclusion


We need to keep in mind that once we start using OWIN, we are essentially passing the CORS concern to the middleware. We do not want to start making changes in different areas of the application as that just makes thing confusing. We just need to focus on the AuthorizationServerProvider and add the necessary implementation to enable CORS in our apps. 

0 comments :

Post a Comment

What do you think?