Microsoft Web API, CORS, OPTIONS & Credentials via POST

Thursday, Mar 12, 2015 at 17:33

David Martin

I have spent way too many hours trying to make jQuery Ajax CORS POSTs operations work with credentials on the Microsoft Web API. So I decided to cut to the chase and write about the steps I took to get this to work. I have installed this into a long standing website that is web forms based and so trying to find the information to initially get the API to work in a web forms app was hard enough.

Firstly use NuGet to install the Microsoft ASP.NET Web API 2.2 Client Libraries, Core Libraries, Cross-Orgin Support and Web Host. Once these are loaded into your existing web forms project create a folder at the root level called App_Start (if it does not exist already). In this folder we are going to install a new class called WebApiCofig.vb (or .cs, dependent on your development environment - I am still using vb in this massive project). The contents of this class should be something like below:

Imports System.Web.Http
Imports System.Web.Http.Cors

Public Class WebApiConfig
Public Shared Sub Register(ByVal config As HttpConfiguration)
Dim cors As EnableCorsAttribute = New EnableCorsAttribute("http://local.site.com," & _
"http://www.site.com", _
"*", "*")
cors.SupportsCredentials = True
config.EnableCors(cors)

GlobalConfiguration.Configuration.MessageHandlers.Add(New ITBeyond.Handlers.CompressHandler())

config.Routes.MapHttpRoute("Feedback", "api/{controller}/{id}", New With {.controller = "Feedback", .id = RouteParameter.Optional})
End Sub
End Class

Now in this class I have configured the Route for the Web API method plus the CORS support. The CompressHandler will be discussed in another blog later so ignore this line for the moment. The Routes are the mechanism that links the inbound requests to the correct method. In my case I have built a controller called Feedback - the class name is FeedbackController.vb and was built using the Add New Item - Web API - Web API Controller Class (v2.1). NOTE: a Web API controller must be called xxxController.vb or .cs to be correctly registered. Once you have used the Add New function the wizard built class will be all that is needed to run a test as it creates a GET, POST, DELETE method.

There are some changes required to the Global.asax.vb to register the WebApiConfig class so your Application_Start method should look like this:

Imports System.Web.Http
Imports System.Web.Routing
Imports System.Net.Http
Imports System.Net

Public Class Global_asax
Inherits System.Web.HttpApplication

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the application is started
WebApiConfig.Register(GlobalConfiguration.Configuration)
End Sub
.......
End Class

Now the all important and impossible to find function to ensure CORS OPTIONS pre-flight works. I had to build a new function again stored in the Global.asax.vb file in the Application_BeginRequest method to pick up an OPTIONS pre-flight request and return the correct headers to allow the Ajax call to succeed. My code for this method is as follows:
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
If Request.Headers.AllKeys.Contains("Origin") And Request.HttpMethod = "OPTIONS" Then
Response.Headers.Add("Access-Control-Allow-Origin", Request.Headers.GetValues("Origin").First())
Dim accessControlRequestMethod As String = Request.Headers.GetValues("Access-Control-Request-Method").FirstOrDefault()
If (accessControlRequestMethod <> "") Then Response.Headers.Add("Access-Control-Allow-Methods", accessControlRequestMethod)
Dim requestedHeaders As String = String.Join(", ", Request.Headers.GetValues("Access-Control-Request-Headers"))
If (Not String.IsNullOrEmpty(requestedHeaders)) Then Response.Headers.Add("Access-Control-Allow-Headers", requestedHeaders)
Response.Headers.Add("Access-Control-Allow-Credentials", "true")
Response.StatusCode = HttpStatusCode.OK
Response.Flush()
End If
End Sub
This code will return the calling domain name in the Access-Control-Allow-Origin header and will also setup the Access-Control-Allow-Credentials header to return "true" as a string with lower case t not True as in boolean.
Without this code the controller methods will never fire and you will be sent back an enable CORS message in the browser console.

Now the jQuery part to glue this all together, I use an encrypted cookie to manage auth for the application so I needed to have the cross site request pass the domains cookie data that is persisted on the client. So the call I used is:
$jq.ajax({
url: '/api/Feedback/',
data: JSON.stringify(postdata),
xhrFields: { withCredentials: true },
type: "POST", datatype: "json", contentType: "application/json; charset=utf-8",
error: function () { blah },
success: function (c) { do something... }
}
});
The all important part to the passing of credentials is the xhrFields: { withCredentials: true }. This option will tell jQuery Ajax to send the site cookies or auth data with the CORS request.

I hope this helps someone out there that is most likely struggling as I was to make this work. If this is not clear or you need some more help register and provide your comments and I will try to answer your questions.
David Martin
Managing Director
Lifetime Member:My Profile  My Blog  Send Message
BlogID: 6347
Views: 8536

Comments & Reviews

Post a Comment
Loading...
Blog Index