Handling CORS

What is CORS request?

Cross-Origin Resource Sharing (CORS) request is HTTP request which Origin header does not same with Host header. By default browser blocks such requests due to same origin policy.

This may pose some problem for backend API which, sometime, configured to run on different host or port.

To allow such requests, backend API needs to tells browser that it acknowledges such request by sending appropriate response header.

Handling CORS with middleware

Fano Framework provides built-in middleware class TCorsMiddleware which is to simplify task for sending appropriate CORS headers. Read Middlewares for more information about working with middlewares.

Constructor of TCorsMiddleware expects ICors interface instance which responsible to handle CORS request.

Built-in ICors implementation

Fano Framework provides two built-in ICors implementation.

  • TNullCors, ICors implementation which simply allow all CORS request without restriction.
  • TCors class is implementation which can be configured to selectively apply restriction.

Register CORS middleware with container

Fano Framework has TCorsMiddlewareFactory or TNullCorsMiddlewareFactory class which allows you to register TCorsMiddleware with TCors or TNullCors with service container. Both factory classes are derived from TBaseCorsMiddlewareFactory.

container.add('globalMiddlewares', TMiddlewareListFactory.create());
container.add(
    'cors',
    (TCorsMiddlewareFactory.create())
        .allowedOriginsPatterns(['http\:\/\/localhost\:[0-9]{1,5}'])
        .allowedMethods(['GET', 'POST', 'OPTIONS'])
        .allowedHeaders(['X-My-Custom-Header'])
);

To use TNullCorsMiddlewareFactory, just replace TCorsMiddlewareFactory above.

Register dispatcher with middleware support

Because we need to execute middlewares, we cannot use TSimpleDispatcher class which by default is already registered when we use, for example, TBasicAppServiceProvider class.

So we need to use TDispatcher class which support middlewares.

var globalMiddlewares : IMiddlewareLinkList;
...
globalMiddlewares := container.get('globalMiddlewares') as IMiddlewareLinkList;
container.add(
    GuidToString(IDispatcher),
    TDispatcherFactory.create(
        globalMiddlewares,
        container.get(GuidToString(IRouteMatcher)) as IRouteMatcher,
        TRequestResponseFactory.create()
    )
);

Attach CORS middleware to application middleware

Attach CORS middleware instance to application middleware collection to ensure CORS middleware is executed for all application routes.

globalMiddlewares.add(container.get('cors') as IMiddleware);

or with array-like syntax

globalMiddlewares.add(container['cors'] as IMiddleware);

Configure CORS settings

TBaseCorsMiddlewareFactory class provides several methods to help configure CORS settings.

Allowed origins

To allow request from http://cors.fano and http://my.fano.

var factory : TBaseCorsMiddlewareFactory;
...
factory := TCorsMiddlewareFactory.create();
factory.allowedOrigins([ 'http://cors.fano', 'http://my.fano']);

To allow request from any origin

factory.allowedOrigins(['*']);

Allowed origins patterns

You can also configure origin to allow using regex pattern. Following code will cause any request coming from http://localhost:port where port is integer value.

factory.allowedOriginsPatterns(['http\:\/\/localhost\:[0-9]{1,5}']);

So any requests from http://localhost:9000 or http://localhost:9100 are allowed but not http://127.0.0.1:9000.

Allowed methods

To allowed only HTTP GET and POST

factory.allowedMethods(['GET', 'POST']);

To allowed all method

factory.allowedMethods(['*']);

Allowed headers

To allowed custom HTTP header

factory.allowedHeaders(['X-My-Custom-Header']);

To allowed all custom headers

factory.allowedHeaders(['*']);

Exposed headers

To list HTTP headers exposed by application that browser allowed to access.

factory.exposedHeaders(['X-My-Custom-Header']);

To expose all headers

factory.exposedHeaders(['*']);

Credentialed request

To allow credentialed request which aware of HTTP Cookies and HTTP Authentication.

factory.supportCredentials(true);

Max age

To set number of seconds, preflight request can be cached by browser, set it as follows

factory.maxAge(3600);

Testing CORS feature

Create a backend application that handle CORS, for example, you can use Fano Cors example application as base.

Create a simple web page to call our API via ajax, for example

<html>
<head><title>CORS Test</title></head>
<body>
    <div id="content"></div>
    <button id="btnLoad">Load</button>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script>
        $(document).ready(function(){
            $('#btnLoad').click(function(){
                $.ajax({
                    url: "http://fano-cors.fano/",
                    headers: { 'x-my-custom-header': 'some value' }
                }).then(function(resp){
                    $('#content').text(resp.hello);
                });
            });
        });
    </script>
</body>
</html>

Save code above as client.html in your web server document root. It is assumed that our application is at http://fano-cors.fano/.

Open browser and go to http://localhost/client.html, click Load button to execute ajax request to our API.

Browser will send ajax request with header Origin equals to http://localhost/client.html and Host equals http://fano-cors.fano/, so request is a CORS request.

Explore more