Same-origin Policy and CORS

DONG Yuxuan @ Feb 07, 2020


Theory

Two web pages have the same origin if and only if1:

There’re some limits to access resources from other origins. This is called the same-origin policy of browers.

By default, you can’t access following resources from another origin2:

However, it’s also allowed to access the above cross-origin resources if you configure properly. Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin3.

In this article, we focus on sending AJAX requests.

A request meets the following conditions is called a simple request.

Simple requests will be sent to the server. However, the client code can’t read the response if the response disallows the client origin to access. We’ll discuss later about how the server specifies those behaviors.

The bevaior of non-simple requests is different. The brower will not directly send the request to the server. Instead, it sends a preflighted request which is an HTTP request with the method OPTIONS to determine if the actual request is safe to send. If the response of the preflighted request says the actual request is safe, the brower sends the actual request.

As we discussed above, whether the client can access a cross-origin resource is determined by how the server responses the request. In fact, the server control the accessibility of corss-origin requests by the following response headers:

For a simple request the browser checks the Access-Control-Allow-Origin header of the reponse to determine whether the client code can get the response, and checks Access-Control-Expose-Headers to determine which headers can be read.

For a non-simple request the browser checks Access-Control-Allow-Origin, Access-Control-Allow-Methods, + Access-Control-Allow-Headers,and

Besides, an AJAX request can be set to send with withCredentials to carry cookies and the HTTP authentication of the target origin. If it’s a cross-origin request, the response must have the header Access-Control-Allow-Credentials: true to allow the client code to access. If Access-Control-Allow-Credentials is true, the Access-Control-Allow-Origin can’t be *.

Practice in Flask

How do we build an API server supporting CORS using Flask? We could add a after_request handler to add propper headers to the response.

ACAO = 'Access-Control-Allow-Origin'
ACAM = 'Access-Control-Allow-Methods'

@app.after_request
def cors(resp):
	headers = resp.headers

	if ACAO not in headers:
		# allow requests from any origin
		headers.add(ACAO, '*')

	# for preflighted requests
	# we need to fill allowed methods in the ACAM header
	# because Flask automatically fills allowed methods
	# in the `Allow` header for `OPTIONS` requests
	# we can just copy it
	if request.method == 'OPTIONS' and ACAM not in headers:
		headers.add(ACAM, headers['Allow'])

	return resp

However, as of Flask 0.7 this handler might not be executed at the end of the request in case an unhandled exception occurred. So we need to support CORS in error handlers too. I have wrote an ariticle about handling exceptions in Flask-based API server4. The following code is based on it.

from werkzeug import exceptions

def error_non500(e):
	return jsonify(e.description), e.code, {
		ACAO: '*'
	}

def error500(e):
	return jsonify("Internal Server Error"), 500, {
		ACAO: '*'
	}

api.register_error_handler(exceptions.BadRequest, error_non500)
api.register_error_handler(exceptions.NotFound, error_non500)
api.register_error_handler(exceptions.Forbidden, error_non500)
api.register_error_handler(exceptions.InternalServerError, error500)

The above snippet doesn’t handle credentials, but it can be a very good start.

References

  1. MDN web docs. Same-origin policy. 

  2. 阮一峰. 跨域资源共享 CORS 详解. 

  3. MDN web docs. Cross-Origin Resource Sharing (CORS). 

  4. DONG Yuxuan. A Pattern for Handling Errors in the API Service Using Flask.