Unlimited Access with CORS

by Brian Rinaldi on June 4, 2014

The modern web is always changing, and this article is more than two years old.

cors_access_header

By Edaqa Mortoray

Opening a REST service for browser use requires CORS. Browsers have a very strict cross-domain policy that will either block the request, or just block access to the returned content. If you intend on having an open service, these restrictions will just get in the way. CORS is the answer, but it isn’t trivial to set up. Many of the references online are incorrect and won’t lead you to a working solution. I have a working solution on my Redid client servers and, in this article, I will discuss details of the approach I used.

Note that a completely open server is only appropriate for some services. If you actually have a social system like Facebook, or one where the user has client credentials, then an open server is not likely what you want. Of course CORS only offers a minimal level of protection. You absolutely need server-side verification. I’ll cover the security topic at the end of this article.

Simple Requests

The CORS standard makes a distinction between a simple request and a preflight request. Just ignore the simple request; its applicability is very limited. Also, providing full preflight support covers the simple requests, should the browser make one. There is no need to do special “simple request” support.

OPTIONS Request and Headers

The browser first makes an OPTIONS method request to the URL it wishes to access. The response is expected to contain a series of headers specifying the access restrictions. It doesn’t go to a special endpoint. It uses whatever URL it happens to want at the moment.

After it gets the OPTIONS response the browser makes the real request: GET, POST, or some other method. This response must also include the same access headers.

It’s best to return the appropriate headers on every request to your server. Every endpoint. Every method. Most frameworks make this relatively easy. Below is the code I use in Flask; it adds the headers to every response. Note my comment about failures: the client also needs access to failure results, thus it is really important that all responses have CORS headers.

@app.after_request
def add_cors(resp):
    """ Ensure all responses have the CORS headers. This ensures any failures are also accessible
        by the client. """
    resp.headers['Access-Control-Allow-Origin'] = flask.request.headers.get('Origin','*')
    resp.headers['Access-Control-Allow-Credentials'] = 'true'
    resp.headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS, GET'
    resp.headers['Access-Control-Allow-Headers'] = flask.request.headers.get( 
        'Access-Control-Request-Headers', 'Authorization' )
    # set low for debugging
    if app.debug:
        resp.headers['Access-Control-Max-Age'] = '1'
    return resp

Access-Control-Allow-Origin

Many references state a value of * should be provided here. This won’t work. All preflighted requests must include a specific origin and not a wildcard. Fortunately for you the request must also have an Origin header. Simply copy the Origin request header to the Access-Control-Allow-Origin response.

If for some reason there is no Origin header, use the wildcard. This may cover a few old browsers’ situations with simple requests.

Access-Control-Allow-Credentials

Setting this to true indicates that browser is allowed to send the user’s credentials to the server (it sends cookies). Not allowing this by default is the core protection against cross-site forgery of requests. This flag is also required if you wish to do HTTP authentication.

Access-Control-Allow-Methods

Only the methods listed here will be allowed for requests. If you only handle a limited number of methods you can simply list them here. The client will however also indicate which methods it wants in the Access-Control-Request-Methods header. You can also echo that in the response.

Access-Control-Allow-Headers

By default the client is only allowed to look at a limited number of header fields in the response. If it wishes to see additional headers they must be listed here. The incoming request will have a Access-Control-Request-Headers field that lists the headers it wants. If the OPTIONS response do not allow access to these headers the entire request will be blocked by the browser. No request with partial header access will be performed.

The best response here is thus an echo of the incoming request. Copy the request Access-Control-Request-Headers to the response header Access-Control-Allow-Headers. In my code I put a fallback of Authorization in, though in practice I’m not sure that is ever used.

Access-Control-Max-Age

Without this setting you’ll just go mad trying to debug your system. The browser caches the response of preflight requests. It does this to avoid repeated OPTIONS requests to the same server. If you’re actively working on CORS support this becomes a problem. You’ll make some changes and the browser just won’t see them.

The Access-Control-Max-Age header indicates how long the browser may cache the response. I set this to 1 second for development. I want my changes to be reflected quickly while testing. For deployment I don’t return the header and just let the user-agent (browser) decide how long to cache. Firefox seems to clear this cache each session.

CORS Does Not Protect You

That should get your running with CORS. It opens up access to your website so any request from the browser will be honored and accessible. This is critical if you intend on offering cross-domain services.

It’s important to note that CORS doesn’t offer any kind of API security. Typical browsers honor these settings, but a non-browser agent can do whatever it wants. The only thing the browser is protecting are access credentials (your cookies). Unfortunately the cross-domain policies of browsers are so restrictive that normal and safe requests are also blocked.

This makes the CORS standard highly redundant. All of these settings give an illusion of additional safety. In actuality all that was needed was a guarantee that the Origin header is set. So long as you receive this header your server is capable of making all security decisions on its own. There is simply no need for anything more complex.

This article was originally published at http://mortoray.com/2014/04/09/allowing-unlimited-access-with-cors/

© 2017 Modern Web & our authors. All rights reserved.