Giix

preflight

10/03/15 authentication , webservices # , , , , , , , ,

Cross-origin Resource Sharing (CORS) and Kerberos (webserver auth)

Cross-origin Resource Sharing (CORS) and Kerberos (webserver auth)

Background

Recently we decided to start separating our web application development. The default framework we use for web applications is CakePHP and we build the entire application with this framework, so both the server and client (view) side. We wanted to separate our front-end and our back-end creating a RESTful API based on CakePHP 3 and a front-end based on AngularJS.

Due to a multi-developer, multi-domain environment (api.developer-1.domain-a.com and app.developer-2.domain-a.com) we ran into the CORS (cross-origin resource sharing) issue. Developer 2 wanted to consume the REST API developer 1 had created on his own domain. First thing Google tells you when searching for the Chrome (or Firefox) cors errors is visit either HTML5 ROCKS or Enable-CORS.org.

These websites and tutorials will tell you everything you need to know about CORS and how to fix or prevent browser security errors concerning cross origin requests.

“So why the hell are you writing this blogpost??”. Fair question, in our company environment we use Kerberos authentication for single-sign-on. By default we enable mod_auth_kerb (Apache2) on our webservers so users don’t have to log in to web applications since their username is known to us ($_SERVER[‘REMOTE_USER’]  variable) due to the provided (browser) Kerberos ticket.

The browser provides the user credentials to the webserver so it can authenticate the visiting user. On a same-domain application (API and front-end) this works fine… going CORS starts a real sh*t storm…

Problem

The real problem here is the fact that a ‘kerberized’ environment requires authentication credentials. Therefore any HTTP request made to this domain not providing these credentials will be killed with a 401 response. Assuming we can fix this problem (spoiler) the next problem occurs when sending a HTTP request with a POST, PUT or DELETE method; Browser request preflight. The specs of HTTP preflighted requests dictate the browser doesn’t send any authentication credentials… So there we go, we need auth credentials to allow the browser to proceed the request but we can’t send them due to the HTTP specs. How do we solve these problems;

Solutions

Landing at this chapter you can stop holding your breath; We found a solution for both of the problems.

401 response on Ajax call to kerberized CORS REST API

Solving this problem requires 2 solutions, first (1) we need to make the server respond to the browser telling the browser the server doesn’t care where you come from. (Server anti-racism measure). Next (2) we need the client to provide credentials in the xhr (ajax) call to the server. By default a browser will not do that. This is quite annoying since you can test a CORS API with webserver auth (NTLM/Kerberos) from your browser or a tool like Rest Easy or Postman and all works fine but when you make an Ajax call you get weird 401 error messages.

OPTIONS method cannot send authentication credentials upon request preflight

Since the HTTP spec’s forbid browsers to send authentication credentials upon a request preflight with the OPTIONS method, Kerberos (mod_auth_kerb) will always reject it with a 401 server response… No fancy tricks or hidden features to fix this I’m afraid. So why don’t we fix this the easy way? Since OPTIONS methods can be caught (by any serverside lang or your framework e.g.) we can safely just disable Kerberos for OPTIONS method calls so they can proceed and initiate a real browser request fixed with the credentials solution above.

Implementation

401 response on Ajax call to kerberized CORS REST API

First up; lets make our webserver more friendly, since we are developers (at least; I am) I want to keep my code close. So if I have to make any webserver config I want to add it to my root .htaccess file (Apache). Check out this code:

In the code above its important to notice the specific domains provided. This is required by many browsers because the Allow-Credentials header is set to true. You will find many examples (like on enable-cors.org) that tell you to set Allow-Origin to “*”. When Allow-Credentials is set browsers do not accept wildcard Allow-Origin’s and the server explicitly expects credentials.

The added Content-Type and Accept headers allow for PUT and DELETE requests. The methods are allowed and defined in the Allow-Headers part.

Next up is the API consumer (front-end), here we only have to indicate we are going to request cross domain and we want to provide credentials (cookie, kerb, other) if available. Example below includes jQuery and AngularJS examples:

 

 

OPTIONS method cannot send authentication credentials upon request preflight

Now that we can GET responses from the server we need to make sure we can POST, PUT and DELETE (maybe PATCH later). These methods are preflighted with an OPTIONS method request that, as we stated, cannot contain credentials. So this is the solution we came up with for the Kerberos part, these settings are in our Apache vhost config file (only a small part displayed):

The settings above use the LimitExcept tag to disable Kerberos auth only for the OPTIONS method requests. All other requests (like the ones following a OPTIONS preflight) will be under Kerberos authentication.

The .htaccess code we saw earlier has to be changed, like we said, when Allow-Credentials is true the server expects credentials, only with OPTIONS none are provided:

Now all REST methods are accepted, authenticated and allowed 😀

Caveats

  • Internet Explorer 8 and 9 do not support withCredentials and therefore must die… O yes, and so they won’t work with CORS requests to Kerberized environments.
  • In many cases, like for example with a CakePHP 3 REST API, you have to implement the OPTIONS method yourself. In many cases just returning a 200 OK on a OPTIONS call is sufficient.
0 likes 2 responses