Archive for the ‘Cross Domain Resource Sharing’ Category

REST, WEB API And CORS   Leave a comment

Introduction

Cross Domain AJAX calls (CORS) on desktop browsers require special processing on both the server side and in the way we call AJAX from within the browser. A general overview of CORS can be found hereASP.Net WEB API allows a couple of fairly straight forward ways to implement REST HTTP endpoints with CORS support.  Using the current release build of WEB API we can code our CORS handlers directly or if you want to use the nightly builds of the WEB API you can use an attribute approach.  This post will concentrate on how to write CORS handlers directly since this is the approach I have in test right now and this approach allows you more flexibility in implementation and improved debugging options.  I will be concentrating on implementation details and assume you have read the background blogs listed above before we start.  I will also be looking at the browser side implementation of the CORS call and some issues with IE browsers (IE 9 in particular).  We are testing with Windows Server 2012 and are using Firefox, Chrome and IE as our test browsers.

So What’s the Problem.

The W3C defines special procedures required if a browser is going to make an AJAX call to a server which is not in the domain of the page which served the page which is making the call (hence Cross Domain).  To enable CORS the server must implement CORS and the browser must make the AJAX call following some conventions.  In the WEB API framework CORS can be implemented on the method or site level.  We will focus on site level CORS in this post.  The WEB API pipeline allows us to hook in message handlers at several places.  The canonical CORS handler, given by the links listed above looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Tracing;

public class CorsHandler : DelegatingHandler
{
const string AccessControlRequestMethod = “Access-Control-Request-Method”;
const string AccessControlRequestHeaders = “Access-Control-Request-Headers”;
const string AccessControlAllowOrigin = “Access-Control-Allow-Origin”;
const string AccessControlAllowMethods = “Access-Control-Allow-Methods”;
const string AccessControlAllowHeaders = “Access-Control-Allow-Headers”;

       protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
{
const string Origin = “Origin”;
bool isCorsRequest = request.Headers.Contains( Origin );
bool isPreflightRequest = request.Method == HttpMethod.Options;
if ( isCorsRequest )
{
//HTTP CORS OPTIONS
if ( isPreflightRequest )
{
HttpResponseMessage response = new HttpResponseMessage( HttpStatusCode.OK );
response.Headers.Add( AccessControlAllowOrigin, request.Headers.GetValues( Origin ).First( ) );
                   string accessControlRequestMethod = request.Headers.GetValues( AccessControlRequestMethod ).FirstOrDefault( );
if ( accessControlRequestMethod != null )
{
response.Headers.Add( AccessControlAllowMethods, accessControlRequestMethod );
}

                   string requestedHeaders = string.Join( “, “, request.Headers.GetValues( AccessControlRequestHeaders ) );
if ( !string.IsNullOrEmpty( requestedHeaders ) )
{
response.Headers.Add( AccessControlAllowHeaders, requestedHeaders+”, AICJWT” );
}

                   TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>( );
tcs.SetResult( response );
return tcs.Task;
}
else
{
//HTTP CORS GET
return base.SendAsync( request, cancellationToken ).ContinueWith<HttpResponseMessage>( t =>
{
HttpResponseMessage resp = t.Result;
resp.Headers.Add( AccessControlAllowOrigin, request.Headers.GetValues( Origin ).First( ) );
return resp;
} );
}
}
else
{
//NOT A CORS CALL
return base.SendAsync( request, cancellationToken );
}
}
}

Lets break this down from the simplest part first.  We create a class derived from DelegatingHandler (since we are implementing at the site level).  We hook this handler into the system within the framework generated class WebApiConfig as

public static class WebApiConfig
{
public static void Register( HttpConfiguration config )
{
//your route code here

           );
config.MessageHandlers.Add( new WebAPI.Handler.CorsHandler( ) );

//other handlers are included here.
}
}

If you have other classes based on DelegatingHandler the order in which they are added in WebApiConfig matters.

In the simplest case where we are not making a CORS call we can simply return the handler without action as:

return base.SendAsync( request, cancellationToken );

When the CORS call is made by the browser the caller should include the standard HTTP header: Origin with a value of the calling pages domain.  The canonical code assumes this and uses the presence of this header to detect a CORS call. Hence the code:

const string Origin = “Origin”;
bool isCorsRequest = request.Headers.Contains( Origin );

If the CORS call is not an OPTIONS call (which the canonical code call preFlight) we see the code:

return base.SendAsync( request, cancellationToken ).ContinueWith<HttpResponseMessage>( t =>
{
HttpResponseMessage resp = t.Result;
resp.Headers.Add( AccessControlAllowOrigin, request.Headers.GetValues( Origin ).First( ) );
return resp;
} );

Here the code returns a required header for the Browser: Access-Control-Allow-Origin with the value taken from the Origin Header of the caller.

We could, if we choice to, have set the value to  the wild card value ( * ) but this openness may make your system administrator nervous.  Notice here that nothing in the W3C specification restricts what other Headers the sender can include in the CORS call.  However certain browsers (IE) and certain Javascript packages (jQuery) restrict the call to standard HTTP request Headers.  In our implementation this gave us some problems but more on this later. The browser code (User-Agent), not the user code, will refuse to accept the return if the Origin Header is missing or does not contain either the wild card or the calling page’s domain in the value for the Origin header.

So What is the Rest of the Handler Code Doing?

Following this document from Mozilla.org, the browser making the call may make an optional CORS OPTIONS call (see here for HTTP verbs if this one is new to you).  This preflight call (as the canonical code names it) has asks the server for details about what may be in the CORS request when it is actually call.  Following the Mozilla explanation here is what needs to happen:

    • 1a. The User-Agent, rather than doing the call directly, asks the server, the API, the permission to do the request. It does so with the following headers:
      • Access-Control-Request-Headers, contains the headers the User-Agent want to access.
      • Access-Control-Request-Method contains the method the User-Agent want to access.
    • 1b. The API answers what is authorized:
      • Access-Control-Allow-Origin the origin that’s accepted. Can be * or the domain name.
      • Access-Control-Allow-Methods a list of allowed methods. This can be cached. Note than the request asks permission for one method and the
        server should return a list of accepted methods.
      • Access-Allow-Headers a list of allowed headers, for all of the methods, since this can be cached as well.

In the canonical code given above here is what happens in the CORS OPTIONS call:

//( 0 )create a response object

HttpResponseMessage response = new HttpResponseMessage( HttpStatusCode.OK );
//( 1 ) build the value string for the Access-Control-Allow-Origin header from the ORIGIN header value of the request

response.Headers.Add( AccessControlAllowOrigin, request.Headers.GetValues( Origin ).First( ) );

//( 3 )build the value string for the Access-Control-Request-Headers from the values in the request
string accessControlRequestMethod = request.Headers.GetValues( AccessControlRequestMethod ).FirstOrDefault( );
if ( accessControlRequestMethod != null )
{
response.Headers.Add( AccessControlAllowMethods, accessControlRequestMethod );
}

//( 4 ) build the value string for the Access-Control-Allow-Headers header from the ORIGIN headers value of the request

string requestedHeaders = string.Join( “, “, request.Headers.GetValues( AccessControlRequestHeaders ) );
if ( !string.IsNullOrEmpty( requestedHeaders ) )
{
response.Headers.Add( AccessControlAllowHeaders, requestedHeaders);
}

//( 5 ) interrupt the pipeline and return the response object to the caller.

TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>( );
tcs.SetResult( response );
return tcs.Task;

Please note that we can put whatever we need into the Header values.  For example if we wanted to limit CORS calls to GET request only we could replace ( 3) with the simple:

response.Headers.Add( AccessControlAllowMethods, “GET” );

To allow a specific domain only to make the CORS call we could replace ( 1 ) with:

response.Headers.Add( AccessControlAllowOrigin, “www.special.com” );

In our case we wanted to allow a specific non-standard Header into the CORS request.  We called this Header AICJWT. So we expanded the key line in ( 4 ) to be:

response.Headers.Add( AccessControlAllowHeaders, requestedHeaders+”, AICJWT” );

The reason we added it explicitly here is due to problems in both JQuery and in IE.  Please note again that the CORS OPTIONS call is optional.  At this point in our development we were using the awesome async Framework 4.5  object: System.Net.Http.HttpClient.  This is a get object and very useful during development BUT there is no User-Agent (browser side code) involved.

The Trouble Begins: Browser Side Code

All seemed swell, till the JavaScript coders tried to call into the system.  JQuery forces a CORS OPTIONS call when it detects a Cross Domain AJAX call.  For reasons which remain unclear JQUERY does not include custom headers in the OPTIONS request.  Some people think this is in the W3C spec for CORS but I don’t see it there, do you?  Some folks out there indicate that the user-agent is forcing the OPTIONS request but this is not true.  If we use a direct AJAX call, not using JQUERY we can make our own CORS OPTIONS request or skip the OPTIONS call completely.  Here is the code to make the call using JavaScript in IE:

function callHttpReq() {
var invocation = new XMLHttpRequest();
var url = ‘

http://whereever/api/Concert?Year=1980′;

    var body = ”;

var token = “myspecialstuff”;
function callOtherDomain(){
if(invocation)
{
invocation.open(‘GET’, url, true);
invocation.setRequestHeader(‘AICJWT’, token);
invocation.setRequestHeader(‘Accept’, ‘application/json’);
invocation.onreadystatechange = httpHandler;
invocation.send(body);
}
}
callOtherDomain();

    function httpHandler() {
if(invocation.readyState == 4) {
var jsonObj = $.parseJSON(invocation.responseText);
if(jsonObj.length > 0) {
htmlStr = “<ul>”;
$.each(jsonObj, function(i,row) {
htmlStr += “<li>” + row.Date + ‘—-’ + ” ” + row.Venue +”</li>”;
});
htmlStr += “</ul>”;
$(“#responeBody”).append(htmlStr);
}
}
}

}

Note we are skipping JQUERY because we require a custom header in our use case.  This step is not necessary if you are NOT using custom headers in the CORS call.  Note also that if you are not using JQUERY you need to use different AJAX object other than IE’s XMLHttpRequest.  If you can use JQUERY there is a masive amount of documentation about how to make AJAX calls and JQUERY will handle CORS and differences between the IE and other AJAX objects automatically.

IE Blues

OK all is good but when we test with IE 8 or 9 we get back the data from the CORS get BUT the user also gets the dialog box:

image

Microsoft tells us the USER can suppress this IN IE8 and IE9 by following this procedure:

You can check your Security Zone level as well as change this setting by going to Tools, Internet Options, and click Security tab. Your Security Zone level will be listed on this screen, by default this is set to Medium-high. To change the setting for your message click Custom Level , locate the Access data sources across domains under the Miscellaneous section and change the setting from Prompt to a desired setting.

image

We do not have this problem in Chrome or Firefox. Live Free or Die.

One Last Server Side Issue

During our testing, using Windows Server 2012 we ran into one additional problem.  Our CORS OPTIONS calls were not getting to our site but where being intercepted by the HTTP Module prior to the site Delegate Handler.  Without getting into it too deeply we needed to modify the web.config for our CORS site and disable WebDAV (don’t ask) and allow OPTIONS for the ExtensionlessUrlHandler.  See here for details.  As far as we know this is a pure Windows 2012 server issue.

ASP.Net Web API: Cross-Domain AJAX and Server Techniques: JSONP and CORS   6 comments

AJAX

AJAX calls make the modern web go around.  Interactive web applications make AJAX calls using the HTTP protocol to call HTTP Endpoints on servers to deliver new content to the browser without round tripping the page.  Normally we try to architect Restful endpoints on Web API servers.  Although the X in AJAX once meant XML is now stands for nothing since most AJAX calls over the web package the data as JSON, not XML. JSON eliminates the need for complex proxy code required for SOAP and XML and assures fidelity between the data format and the consuming code (JavaScript) within the browser.  We let the Web be the Web, Restful HTTP rules and life was good. Although one can make AJAX calls in JavaScript using fairly low level calls with vendor specific code most of us use a helper library of one type or another.  I normally use JQuery as my browser abstraction layer in order to minimize browser differences and I find their code to be pretty darn smart.  Much smarter than my JavaScript code.  As we toyed around with this AJAX thing it is a logical extension of the concept to attempt to make AJAX calls to servers NOT in the domain which served up the page to the browser.  That’s is the WEB right we want to connect data from all over the web not just from the server which originally delivered our page.  In formal terms this is know as Cross Domain Resource Sharing and without special cooperation between the browser code, the browser and the server code.

The Server Side

To enable AJAX calls to domains other than the server of the calling page requires that the server support either JSONP or CORS. Of the two methods, CORS is the newest and the preferred method.  However CORS is not supported by all browsers and only on some versions of supporting browsers.  JSONP can be used from all browsers except Opera.  Some history is in order here, the W3C which at one time controlled (or at least managed) these things was, prior to the development of smart phones (i.e. iPhone), pretty slow moving.  Indeed the “Same Origin Policy” expletively forbad cross domain resource sharing.  Although there was quite a bit of pressure since the turn of the century for cross domain AJAX no new spec was forth coming. To work around this clever coders (on the server and browser sides) took what was once a security  exploit (the “cross-site request forgery”) and turned it into a virtue (JSONP).  Eventually the W3C developed a “Working Draft” on CORS (the original draft is dated March 2009 and the most recent draft is April 2012).  As this draft stabilized,  server code could be developed and browser vendors could enable CORS support.  OK, lets look at some server code.  I am going to use examples for GET request with NO security (clearly the simplest case, I will follow up with folding POST and security in a later blog entry).

Controllers On ASP.Net Web API

A bare bone Web API Get controller for ASP.Net MVC 4 might look like:

DeadBase2.DAL.DeadBase2Entities  _DBContext = new DAL.DeadBase2Entities( );

public IEnumerable JSONP( string MM, string DD )
{
return _DBContext.ViewConcerts.ToList( )
.Where( c => string.Compare( c.YYYYMMDD.Substring( 4, 4 ), MM + DD ) == 0 ).OrderBy( c => c.YYYYMMDD );
}

This will return JSON if the caller includes the media type header with the argument:

text/javascript

In this form the controller can return JSON but it does not yet support COR or JSONP.

We can limit the controller to HTTP GET requests by adding the attribute

[System.Web.Mvc.AcceptVerbs( HttpVerbs.Get )]

There have been some attempts to develop an attribute to limit controller methods to AJAX only calls.  Since these are usually based on examining header values I take of dim view of this since headers from the caller can be faked and falsified.

Let’s expand the controller model to include the ability to return HTTP Error Codes

public HttpResponseMessage<ienumerable> JSONP( string MM, string DD )
{
HttpResponseMessage<IEnumerable<DAL.ViewConcert>> msg;
_Concerts = _DBContext.ViewConcerts.ToList( )

                   .Where( c => string.Compare( c.YYYYMMDD.Substring( 4, 4 ), MM + DD ) == 0 ).OrderBy( c => c.YYYYMMDD );
if ( _Concerts.Count( ) == 0 )
{
msg = new HttpResponseMessage<IEnumerable<DAL.ViewConcert>>( System.Net.HttpStatusCode.Forbidden );
}
else
{
msg = new HttpResponseMessage<IEnumerable<DAL.ViewConcert>>( _Concerts );
}
return msg;
}

These changes will allow us to  return HTTP error codes to AJAX and CORS calls.  JSONP callers will not (more on this below). receive these codes.

Enabling CORS On The Server

To enable CORS on the server side all we need to doo is provide appropriate W3C recommended server-side headers to the response just prior to returning from the method.  The most important of these is the “Access-Control-Allow-Origin”.  To allow all CORS callers the argument value is the wild card:

Access-Control-Allow-Origin: *

to limit the callers to specific cross-domain callers, like:


Access-Control-Allow-Origin: 
http://example.com:8080
 
http://foo.example.com

In our model method controller  we simply add the following line just prior to the return statement:

 msg.Headers.Add( “Access-Control-Allow-Origin”, “*” );

Browser Side Code for CORS

Simple, easy and fun.  This header allows the browser code to allow the return data stream to passed to your application code (your JavaScript).  Not all browsers support the working draft.  Currently the list includes:

Gecko 1.9.1 (Firefox 3.5,[3] SeaMonkey 2.0[4]) and above.

WebKit (Initial revision uncertain, Safari 4 and above,[1] Google Chrome 3 and above.

MSHTML/Trident 4.0 (Internet Explorer 8 and Explorer 9) provides partial support via the XDomainRequest object.[1]

Assuming you are working with a conformant browser a CORS call in JQuery looks like:

function CallAJAX (){
$.ajax(
{
type: “GET”,
url: “
http://localhost:8080/deadbase2api/api/Concerts/MMDD/12/31″,
dataType: “json”,
success: function (data) { alert(‘Success AJAX’); alert(‘Objects Returned: ‘ + data.length); },
error: function (XMLHttpRequest, textStatus, errorThrown) { alert(“Error AJAX”); }
}
);
}

 

See this link for more JQuery details.  If you are not using IE8 or IE9 that’s it. Done.  If you are using IE8 or IE9 things an additional steps are required.  Since IE8 and IE9 use XDomainRequest object for CORS calls JQuery $.ajax calls will not work out of the box.  You must include a JQuery plug-in (Get a local copy from here).  Include this file after you have loaded the JQuery library (we load our JQuery at run time from a Google edge server).  Now the above $.ajax code will work on all CORS supporting  browsers.  Two more notes on IE8 and IE9, in my tests IE9 and IE8 will not trapped HTTP errors in CORS calls (strange but true).  The somewhat mythical IE10 reportedly will not use XDomainRequest object and will be conformant with the WebKit model in which case the extra JQuery plug in will not be necessary.  We shall see.

Enabling JSONP on the Server

A cross-domain JSONP AJAX call includes a query string argument named “callback” with an argument determined by the caller.  In our example code the calling might compose something like this:


http://localhost:8080/deadbase2api/api/Concerts/MMDD/12/31
?callback=mycallbackfunction

and pass the proper JSON media type header (text/javascript).  It’s the servers job to do two tasks:

  • process the call and output JSON formatted data; and
  • wrap the JSON data in the argument of the callback function like this: mycallbackfunction({JSON DATA goes here})

Inside of the controller method we could read the argument of the query string argument and generate the necessary string.  A slightly more elegant approach is to add JSONP formatter to do this for all method calls which meet the criteria for a JSONP response. Alex Zeitler has contributed to GitHub an excellent JSONP formatter which can be hooked into the Web API system.  Get it here.  His blog post on the formatter is here.  This guy rocks.  Pull his code into a code file in your Web API project (change the namespace if you need to).  Add the formatter to your application_start event in global.asax file.  My application_start code looks like this:

protected void Application_Start( )
{
AreaRegistration.RegisterAllAreas( );

          RegisterGlobalFilters( GlobalFilters.Filters );
GlobalConfiguration.Configuration.Formatters.Insert( 0, new DeadBase2.Formatters.JsonpMediaTypeFormatter( ) );
RegisterRoutes( RouteTable.Routes );

          BundleTable.Bundles.RegisterTemplateBundles( );
}

This formatter is at the model level, that is it will apply to all controller methods which the formatter tests as qualifying for JSONP formatting.

Browser Side Code For JSONP

OK now you are good to go to respond to JSONP requests. All browsers that we know of, except Opera, support JSONP calls for cross-domain resource calls.  In JQuery the call looks like:

function CallJSONP() {
$.ajax(
{
type: “GET”,
url: “
http://localhost:8080/deadbase2api/api/Concerts/MMDD/12/31″,
dataType: “jsonp”,
success: function (data) { alert(‘Success JSONP’); alert(‘Objects Returned: ‘ +data.length); },
error: function (XMLHttpRequest, textStatus, errorThrown) { alert(“ERROR JSONP”); alert(textStatus); } //see note below
}
);
}

JQuery will generate the appropriate query string and provide a unique random name to use as the argument for the callback key in the query string.  Note that JQuery uses “jsonp” as the argument for “datatype” it will use this key to generate a query string which includes a random argument for the callback function, provide a json media type header and handle decoding the response and calling the success function.  For most browsers HTTP errors of all types will not be trapped and the error function will never be called (this includes 404 errors!).  Internally generated errors with the controller method (like the System.Net.HttpStatusCode.Forbidden in the model code above) will generate a trappable error in IE8 and IE9 only – however the error returned is NOT the HTTP Error code but a strange “parse error” message.  All of this weirdness about error code trapping is caused by the browser code not the Server code and not the JavaScript code.  A review of the wire traffic using Fiddler (you do use Fiddler for debugging don’t you) shows that the proper HTTP Error codes are returned to the browser, the browser just thrown them away.  AJAX calls which are not cross domain do receive the HTTP status codes properly. Strange.

Cross Domain Error Response Summary:

  Chrome IE9 FoxFire Safari
CORS Trapped Not Trapped Trapped Trapped
JSONP Not Trapped Trapped* Not Trapped Not Trapped

 

Ok. That’s All Folks.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: