Home > English, work > Writing integration tests for CORS headers (with Karate)

Writing integration tests for CORS headers (with Karate)

On many projects CORS headers are configured incorrectly. Usually by putting some wildcards (*) in the config and things ‘work’. In this article I will show how to create tests for the correct headers (using Karate, but it should be applicable to any test framework).

Introduction

CORS stands for Cross-Origin Resource Sharing. And that’s pretty much everything I will tell about CORS. You’re probably here to get some quick results and won’t listen to me when I tell you it is a wise thing to expand your CORS knowledge first.

Hopefully some of the tests in this article will fail on your project and then you have to read about CORS anyway. An excellent source is Mozilla’s article about CORS and will pretty much cover everything you need to know. I also recommend this article by Derric Gilling, it is a bit easier to read when you’re new to CORS (or want some more elaborate explanations).

My tests are written in Karate. They’re easy to understand without any knowledge about Karate and it’s my preferred way to do integration testing.

With CORS there are two kinds of requests, simple and advanced :

Simple request (and response)

You’r probably dealing with a simple request when the HTTP request method is GET/HEAD/POST, does not contain ‘forbidden’ headers and has Content-Type: text/plain or form data.
You won’t find many simple requests nowadays since most requests have Content-Type: application/json, (which triggers an advanced request).

More details about simple requests

Your web server should treat a request as a CORS-request when you provide the Origin header. The Origin header is not modifiable in your browser. The header is still editable with tools like curl so never trust a request header!

The response should contain an Access-Control-Allow-Origin header with the same value as your Origin. When your Origin does not contain a wildcard (*), the Vary header should also contain Origin

In a Karate test this will look like :

Scenario: Simple CORS request
  Given path '/simple'
  And header Origin = 'http://localhost:3000'
  When method GET
  Then status 200
  And match header Access-Control-Allow-Origin == 'http://localhost:3000'
  And match header Vary contains 'Origin'

Advanced requests

All other requests are advanced and your browser precedes these requests with a ‘preflight request’ (unless a cached version is still valid). The preflight request is an HTTP OPTIONS request. Note that your browser does all the work, you don’t need to manually create or send an OPTIONS request.

Besides the Origin header the Access-Control-Request-Method header is set, this will contain the HTTP method that will be used in the successive call (the actual request). Finally the Access-Control-Request-Headers is set, this will contain the headers that will be sent along with the successive request.

Advanced response

The response should contain the same headers as a simple response plus some more. The first is

  • Access-Control-Max-Age: This indicates how long a browser can cache the response (in seconds). This is important since the response can contain information regarding other requests you might make in the future and you don’t want the extra roundtrip on every call.
  • Access-Control-Allow-Methods: will contain all the HTTP methods that are allowed for CORS-requests
  • Access-Control-Allow-Headers: will contain the HTTP headers that are allowed to use in a CORS-request. Any other header supplied should result in your browser NOT making the call (the conversation stops after the preflight response and prints an error message in your console).
  • Access-Control-Allow-Credentials: indicates whether the client can send credentials (cookies, authorization headers or TLS client certificates).
  • Access-Control-Expose-Headers: contains a list of headers that can be included in the response, any other headers are ignored by the browser (unless it’s a Safelisted header.

The test for a preflight request/response will look like :

Scenario: CORS preflight request (one valid, one invalid header)
  Given path '/advanced'
  And header Origin = 'http://localhost:3000'
  And header Content-type = 'application/json'
  And header Access-Control-Request-Method = 'GET'
  And header Access-Control-Request-Headers = 'authorization,fake-header'
  When method OPTIONS
  Then status 200
  And match header Access-Control-Allow-Origin == 'http://localhost:3000'
  And match header Access-Control-Allow-Headers == 'authorization'
  And match header Access-Control-Allow-Methods == 'GET'
  And match header Cache-Control == '#string'
  And match header Vary contains 'Origin'

Note that when a browser made this call the actual successive GET wouldn’t be executed because of the fake-header. The Cache-Control header is a safelisted header that doesn’t need to be in the Access-Control-Request-Headers.

Extra checks and debugging

It can also be a good idea to check explicitly that some headers are missing. Maybe your web server includes too much (and/or wrong) information for example.

When you’re debugging/creating your tests you might want Karate to show some extra information. Lowering the log level is one option, but produces a lot of noise. A better idea is to print your response and/or header with the following statements :

* print response
* print responseHeaders

Remember to clean up those print statements after you’re done!

There is a website where you can test your CORS headers : https://www.test-cors.org/. This site of course uses the Origin: https://www.test-cors.org/, so adapt your web server accordingly. It is also possible to check out the source code and run this code yourself (because with the website your data is sent over the internet).

How to configure CORS on your web server

Now follows a list of guidelines for configuring CORS properly.

Don’t set the Access-Control-Allow-Origin to * unless you want to allow any Origin to access your resources. * also cannot be used in combination with credentials. Make sure you have a server side list with allowed Origins.

For caching of the preflight response (Access-Control-Max-Age header) pick a reasonable value. Most browsers have a cap on the value ranging from 10 minutes to 1 day. Disabling (-1) will degrade performance (you need an extra roundtrip on every CORS-request), so a value of a few minutes is reasonable.

It’s okay to allow all HTTP methods (Access-Control-Allow-Methods: *) unless you have a very specific reason not to (only read only request for example).

Pay attention with Access-Control-Allow-Headers. Be aware that there are some default headers and Content-Type is not one of them (since most people use this nowadays for json request/responses).

Access-Control-Allow-Credentials should return true when the withCredentials property of an XMLHttpRequest is set to true (and your server expects credentials along the the request).

Access-Control-Expose-Headers are the response headers that are visible to the client browser. When this property is set incorrectly the client browser will ignore these headers.

Conclusion

I found a lot of articles about CORS, but ultimately the Mozilla site contained all the (correct) info. I hope I condensed it into a usable article. If you have any remarks/improvements/tips please feel free to comment on this article. Since it’s a bit of a rabbit hole I might have missed some important information.

Be aware that there are a lot of bad explanations of CORS on Stack Overflow and the rest of the internet. When you found something always cross check with reputable sources like Mozilla or the rfc’s on IETF (so also don’t trust this article).

Remember that this is all protection in your browser, with full control over your HTTP headers (ie with Curl) it’s still possible to make all kind of malicious calls. So always treat incoming calls with caution.

Advertisements
Categories: English, work Tags: , , ,
  1. No comments yet.
  1. 11 May 2019 at 09:45

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: