Recently, I helped colleague securing his ASP.NET WebApi. The WebApi was called from SharePoint Online using JavaScript. We thought that an integration with OAuth would be simple for CORS (cross-origin resource sharing) but we took a week to figure out what went wrong with mash up code.
Securing WebApi with OAuth
We've classes of WebApi and we see this example project. It looks simple to follow but there is a catch for that.
- You need to add OWIN startup class yourself. Visual Studio can handle this for us.
- In
AppOAuthProvider
class, you may need to modify the credential checking logic. For us, we've just asking for caller (just email) permission from SharePoint itself. - This example project doesn't mention an integration with CORS!
In summary, we load Microsoft.Owin.Security.OAuth
package and a bunch of its dependency as suggested on the article. We annotate classes and methods with [System.Web.Http.Authorize]
. We test it without CORS first and it works well. Next, we will add CORS support to our WebApi.
Setup CORS for WebApi
There is a well known document from Microsoft. After adding Microsoft.AspNet.WebApi.Cors
package to the project, annotating classes and methods with [System.Web.Http.Cors.EnableCors]
, and finally, adding
config.EnableCors();
on WebApi configuration method, we are good to go. The benefit for using this method is granularity of methods or classes to be exposed for CORS. We are ready to test CORS context but it doesn't work as expected. OAuth token issuer endpoint isn't CORS compliant!
Force additional header
First, it doesn't pass OAuth token issuer due to lack of Access-Control-Allow-Origin
. There is one workaround that works well which is adding custom Access-Control-Allow-Origin
header via web.config
. This looks defeating purpose of annotating [EnableCors]
because we cannot control the header to be specific only WebApi responses. So, I try to avoid that.
Microsoft.Owin.Cors
middleware
There is another unmentioned solution which also works. That is using Microsoft.Owin.Cors
middleware. It is really easy and simple. From the OWIN startup class, the Owin.IAppBuilder
extension method UseCors
is injected.
app.UseCors(CorsOption.AllowAll);
(We try to simplify this first. We can give it other options.)
This will make the token issuer endpoint and all WebApi endpoints expose to CORS! The consequence from using this solution is undoing the annotation [EnableCors] solution because it conflicts each other. When we try this change from local context, the token issuer always response with Access-Control-Allow-Origin
header. There is no such header from WebApi. When we try this from CORS context, the WebApi calls always fail! The Access-Control-Allow-Origin
now is returning multiple origin headers.
Access-Control-Allow-Origin: http://hosta, *
This is weird. The only way solving this is by undoing the [EnableCors] solution. The downside of this method is no controlling on which methods or classes to be exposed on CORS. If there is no requirement to protect the CORS exposure, this is an easy solution.
Better solution
We end up modify AppOAuthProvider.GrantResourceOwnerCredentials
method to properly inject Access-Control-Allow-Origin
header for an authenticated session.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
This allows us to have a control over WebApi classes and methods and also for token issuer endpoint.
I hope, this can help everyone which may finding that the Microsoft document looks wrong when integrate with the OAuth to understand the behavior of the middleware. It depends on the requirement for choosing the CORS solution but you can't use them both at the same time.