Enabling Cross-Origin Resource Sharing (CORS)

duration 15 minutes

Prerequisites:

Learn how to enable Cross-Origin Resource Sharing (CORS) in Open Liberty without writing Java code.

What you’ll learn

You will learn how to add two Liberty configurations to enable CORS. Next, you will write and run tests to validate that the CORS configurations work. These tests send two different CORS requests to a REST service that has two different endpoints.

CORS and its purpose

Cross-Origin Resource Sharing (CORS) is a W3C specification and mechanism that you can use to request restricted resources from a domain outside the current domain. In other words, CORS is a technique for consuming an API served from an origin different than yours.

CORS is useful for requesting different kinds of data from websites that aren’t your own. These types of data might include images, videos, scripts, stylesheets, iFrames, or web fonts.

However, you cannot request resources from another website domain without proper permission. In JavaScript, cross-origin requests with an XMLHttpRequest API and Ajax cannot happen unless CORS is enabled on the server that receives the request. Otherwise, same-origin security policy prevents the requests. For example, a web page that is served from the http://aboutcors.com server sends a request to get data to the http://openliberty.io server. Because of security concerns, browsers block the server response unless the server adds HTTP response headers to allow the web page to consume the data.

Different ports and different protocols also trigger CORS. For example, the http://abc.xyz:1234 domain is considered to be different from the https://abc.xyz:4321 domain.

Open Liberty has built-in support for CORS that gives you an easy and powerful way to configure the runtime to handle CORS requests without the need to write Java code.

Types of CORS requests

Familiarize yourself with two kinds of CORS requests to understand the attributes that you will add in the two CORS configurations.

Simple CORS request

According to the CORS specification, an HTTP request is a simple CORS request if the request method is GET, HEAD, or POST. The header fields are any one of the Accept, Accept-Language, Content-Language, or Content-Type headers. The Content-Type header has a value of application/x-www-form-urlencoded, multipart/form-data, or text/plain.

When clients, such as browsers, send simple CORS requests to servers on different domains, the clients include an Origin header with the original (referring) host name as the value. If the server allows the origin, the server includes an Access-Control-Allow-Origin header with a list of allowed origins or an asterisk (*) in the response back to the client. The asterisk indicates that all origins are allowed to access the endpoint on the server.

Preflight CORS request

A CORS request is not a simple CORS request if a client first sends a preflight CORS request before it sends the actual request. For example, the client sends a preflight request before it sends a DELETE HTTP request. To determine whether the request is safe to send, the client sends a preflight request, which is an OPTIONS HTTP request, to gather more information about the server. This preflight request has the Origin header and other headers to indicate the HTTP method and headers of the actual request to be sent after the preflight request.

Once the server receives the preflight request, if the origin is allowed, the server responds with headers that indicate the HTTP methods and headers that are allowed in the actual requests. The response might include more CORS-related headers.

Next, the client sends the actual request, and the server responds.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-cors.git
cd guide-cors

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Enabling CORS

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

You will use a REST service that is already provided for you to test your CORS configurations. You can find this service in the src/main/java/io/openliberty/guides/cors/ directory.

You will send a simple request to the /configurations/simple endpoint and the preflight request to the /configurations/preflight endpoint.

Enabling a simple CORS configuration

Configure the Liberty to allow the /configurations/simple endpoint to accept a simple CORS request. Add a simple CORS configuration to the Liberty server.xml configuration file:

Replace the Liberty server.xml configuration file.
src/main/liberty/config/server.xml

server.xml

 1<server description="Sample Liberty server">
 2
 3<featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonb-3.0</feature>
 6</featureManager>
 7
 8<variable name="http.port" defaultValue="9080"/>
 9<variable name="https.port" defaultValue="9443"/>
10
11<httpEndpoint host="*" httpPort="${http.port}" httpsPort="${https.port}"
12    id="defaultHttpEndpoint"/>
13
14<webApplication location="guide-cors.war" contextRoot="/"/>
15
16<!-- tag::simple-config[] -->
17<cors domain="/configurations/simple"
18    allowedOrigins="http://openliberty.io"
19    allowedMethods="GET"
20    allowCredentials="true"
21    exposeHeaders="MyHeader"/>
22<!-- end::simple-config[] -->
23
24<!-- tag::preflight-config[] -->
25<cors domain="/configurations/preflight"
26    allowedOrigins="*"
27    allowedMethods="OPTIONS, DELETE"
28    allowCredentials="true"
29    allowedHeaders="MyOwnHeader1, MyOwnHeader2"
30    maxAge="10"/>
31<!-- end::preflight-config[] -->
32</server>

The CORS configuration contains the following attributes:

Configuration Attribute Value

domain

The endpoint to be configured for CORS requests. The value is set to /configurations/simple.

allowedOrigins

Origins that are allowed to access the endpoint. The value is set to http://openliberty.io.

allowedMethods

HTTP methods that a client is allowed to use when it makes requests to the endpoint. The value is set to GET.

allowCredentials

A boolean that indicates whether the user credentials can be included in the request. The value is set to true.

exposeHeaders

Headers that are safe to expose to clients. The value is set to MyHeader.

For more information about these and other CORS attributes, see the cors element documentation.

Save the changes to the server.xml configuration file. The /configurations/simple endpoint is now ready to be tested with a simple CORS request.

The Open Liberty instance was started in dev mode at the beginning of the guide and all the changes were automatically picked up.

Now, test the simple CORS configuration that you added. Add the testSimpleCorsRequest method to the CorsIT class.

Replace the CorsIT class.
src/test/java/it/io/openliberty/guides/cors/CorsIT.java

CorsIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2024 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package it.io.openliberty.guides.cors;
13
14import static org.junit.jupiter.api.Assertions.assertEquals;
15
16import java.io.IOException;
17import java.net.HttpURLConnection;
18import java.util.Map;
19import java.util.Map.Entry;
20
21import org.junit.jupiter.api.BeforeEach;
22import org.junit.jupiter.api.Test;
23
24public class CorsIT {
25
26    String port = System.getProperty("http.port");
27    String pathToHost = "http://localhost:" + port + "/";
28
29    @BeforeEach
30    public void setUp() {
31        // JVM does not allow restricted headers by default
32        // Set to true for CORS testing
33        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
34    }
35
36    // tag::testSimpleCorsRequest[]
37    @Test
38    public void testSimpleCorsRequest() throws IOException {
39        HttpURLConnection connection = HttpUtils.sendRequest(
40                        // tag::get[]
41                        pathToHost + "configurations/simple", "GET",
42                        // end::get[]
43                        TestData.simpleRequestHeaders);
44        checkCorsResponse(connection, TestData.simpleResponseHeaders);
45
46        printResponseHeaders(connection, "Simple CORS Request");
47    }
48    // end::testSimpleCorsRequest[]
49
50    // tag::testPreflightCorsRequest[]
51    @Test
52    public void testPreflightCorsRequest() throws IOException {
53        HttpURLConnection connection = HttpUtils.sendRequest(
54                        // tag::options[]
55                        pathToHost + "configurations/preflight", "OPTIONS",
56                        // end::options[]
57                        TestData.preflightRequestHeaders);
58        checkCorsResponse(connection, TestData.preflightResponseHeaders);
59
60        printResponseHeaders(connection, "Preflight CORS Request");
61    }
62    // end::testPreflightCorsRequest[]
63
64    public void checkCorsResponse(HttpURLConnection connection,
65                    Map<String, String> expectedHeaders) throws IOException {
66        assertEquals(200, connection.getResponseCode(), "Invalid HTTP response code");
67        expectedHeaders.forEach((responseHeader, value) -> {
68            assertEquals(value, connection.getHeaderField(responseHeader),
69                            "Unexpected value for " + responseHeader + " header");
70        });
71    }
72
73    public static void printResponseHeaders(HttpURLConnection connection,
74                    String label) {
75        System.out.println("--- " + label + " ---");
76        Map<String, java.util.List<String>> map = connection.getHeaderFields();
77        for (Entry<String, java.util.List<String>> entry : map.entrySet()) {
78            System.out.println("Header " + entry.getKey() + " = " + entry.getValue());
79        }
80        System.out.println();
81    }
82
83}

The testSimpleCorsRequest test simulates a client. It first sends a simple CORS request to the /configurations/simple endpoint, and then it checks for a valid response and expected headers. Lastly, it prints the response headers for you to inspect.

The request is a GET HTTP request with the following header:

Request Header Request Value

Origin

The value is set to http://openliberty.io. Indicates that the request originates from http://openliberty.io.

Expect the following response headers and values if the simple CORS request is successful, and the Liberty instance is correctly configured:

Response Header Response Value

Access-Control-Allow-Origin

The expected value is http://openliberty.io. Indicates whether a resource can be shared based on the returning value of the Origin request header http://openliberty.io.

Access-Control-Allow-Credentials

The expected value is true. Indicates that the user credentials can be included in the request.

Access-Control-Expose-Headers

The expected value is MyHeader. Indicates that the header MyHeader is safe to expose.

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

If the testSimpleCorsRequest test passes, the response headers with their values from the endpoint are printed. The /configurations/simple endpoint now accepts simple CORS requests.

Response headers with their values from the endpoint:

--- Simple CORS Request ---
Header null = [HTTP/1.1 200 OK]
Header Access-Control-Expose-Headers = [MyHeader]
Header Access-Control-Allow-Origin = [http://openliberty.io]
Header Access-Control-Allow-Credentials = [true]
Header Content-Length = [22]
Header Content-Language = [en-CA]
Header Date = [Thu, 21 Mar 2019 17:50:09 GMT]
Header Content-Type = [text/plain]
Header X-Powered-By = [Servlet/4.0]

Enabling a preflight CORS configuration

Configure the Liberty to allow the /configurations/preflight endpoint to accept a preflight CORS request. Add another CORS configuration in the Liberty server.xml configuration file:

Replace the Liberty server.xml configuration file.
src/main/liberty/config/server.xml

server.xml

 1<server description="Sample Liberty server">
 2
 3<featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonb-3.0</feature>
 6</featureManager>
 7
 8<variable name="http.port" defaultValue="9080"/>
 9<variable name="https.port" defaultValue="9443"/>
10
11<httpEndpoint host="*" httpPort="${http.port}" httpsPort="${https.port}"
12    id="defaultHttpEndpoint"/>
13
14<webApplication location="guide-cors.war" contextRoot="/"/>
15
16<!-- tag::simple-config[] -->
17<cors domain="/configurations/simple"
18    allowedOrigins="http://openliberty.io"
19    allowedMethods="GET"
20    allowCredentials="true"
21    exposeHeaders="MyHeader"/>
22<!-- end::simple-config[] -->
23
24<!-- tag::preflight-config[] -->
25<cors domain="/configurations/preflight"
26    allowedOrigins="*"
27    allowedMethods="OPTIONS, DELETE"
28    allowCredentials="true"
29    allowedHeaders="MyOwnHeader1, MyOwnHeader2"
30    maxAge="10"/>
31<!-- end::preflight-config[] -->
32</server>

The preflight CORS configuration has different values than the simple CORS configuration.

Configuration Attribute Value

domain

The value is set to /configurations/preflight because the domain is a different endpoint.

allowedOrigins

Origins that are allowed to access the endpoint. The value is set to an asterisk (*) to allow requests from all origins.

allowedMethods

HTTP methods that a client is allowed to use when it makes requests to the endpoint. The value is set to OPTIONS, DELETE.

allowCredentials

A boolean that indicates whether the user credentials can be included in the request. The value is set to true.

The following attributes were added:

  • allowedHeaders: Headers that a client can use in requests. Set the value to MyOwnHeader1, MyOwnHeader2.

  • maxAge: The number of seconds that a client can cache a response to a preflight request. Set the value to 10.

Save the changes to the server.xml configuration file. The /configurations/preflight endpoint is now ready to be tested with a preflight CORS request.

Add another test to the CorsIT.java file to test the preflight CORS configuration that you just added:

Replace the CorsIT class.
src/test/java/it/io/openliberty/guides/cors/CorsIT.java

CorsIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2024 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package it.io.openliberty.guides.cors;
13
14import static org.junit.jupiter.api.Assertions.assertEquals;
15
16import java.io.IOException;
17import java.net.HttpURLConnection;
18import java.util.Map;
19import java.util.Map.Entry;
20
21import org.junit.jupiter.api.BeforeEach;
22import org.junit.jupiter.api.Test;
23
24public class CorsIT {
25
26    String port = System.getProperty("http.port");
27    String pathToHost = "http://localhost:" + port + "/";
28
29    @BeforeEach
30    public void setUp() {
31        // JVM does not allow restricted headers by default
32        // Set to true for CORS testing
33        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
34    }
35
36    // tag::testSimpleCorsRequest[]
37    @Test
38    public void testSimpleCorsRequest() throws IOException {
39        HttpURLConnection connection = HttpUtils.sendRequest(
40                        // tag::get[]
41                        pathToHost + "configurations/simple", "GET",
42                        // end::get[]
43                        TestData.simpleRequestHeaders);
44        checkCorsResponse(connection, TestData.simpleResponseHeaders);
45
46        printResponseHeaders(connection, "Simple CORS Request");
47    }
48    // end::testSimpleCorsRequest[]
49
50    // tag::testPreflightCorsRequest[]
51    @Test
52    public void testPreflightCorsRequest() throws IOException {
53        HttpURLConnection connection = HttpUtils.sendRequest(
54                        // tag::options[]
55                        pathToHost + "configurations/preflight", "OPTIONS",
56                        // end::options[]
57                        TestData.preflightRequestHeaders);
58        checkCorsResponse(connection, TestData.preflightResponseHeaders);
59
60        printResponseHeaders(connection, "Preflight CORS Request");
61    }
62    // end::testPreflightCorsRequest[]
63
64    public void checkCorsResponse(HttpURLConnection connection,
65                    Map<String, String> expectedHeaders) throws IOException {
66        assertEquals(200, connection.getResponseCode(), "Invalid HTTP response code");
67        expectedHeaders.forEach((responseHeader, value) -> {
68            assertEquals(value, connection.getHeaderField(responseHeader),
69                            "Unexpected value for " + responseHeader + " header");
70        });
71    }
72
73    public static void printResponseHeaders(HttpURLConnection connection,
74                    String label) {
75        System.out.println("--- " + label + " ---");
76        Map<String, java.util.List<String>> map = connection.getHeaderFields();
77        for (Entry<String, java.util.List<String>> entry : map.entrySet()) {
78            System.out.println("Header " + entry.getKey() + " = " + entry.getValue());
79        }
80        System.out.println();
81    }
82
83}

The testPreflightCorsRequest test simulates a client sending a preflight CORS request. It first sends the request to the /configurations/preflight endpoint, and then it checks for a valid response and expected headers. Lastly, it prints the response headers for you to inspect.

The request is an OPTIONS HTTP request with the following headers:

Request Header Request Value

Origin

The value is set to anywebsiteyoulike.com. Indicates that the request originates from anywebsiteyoulike.com.

Access-Control-Request-Method

The value is set to DELETE. Indicates that the HTTP DELETE method will be used in the actual request.

Access-Control-Request-Headers

The value is set to MyOwnHeader2. Indicates the header MyOwnHeader2 will be used in the actual request.

Expect the following response headers and values if the preflight CORS request is successful, and the Liberty instance is correctly configured:

Response Header Response Value

Access-Control-Max-Age

The expected value is 10. Indicates that the preflight request can be cached within 10 seconds.

Access-Control-Allow-Origin

The expected value is anywebsiteyoulike.com. Indicates whether a resource can be shared based on the returning value of the Origin request header anywebsiteyoulike.com.

Access-Control-Allow-Methods

The expected value is OPTIONS, DELETE. Indicates that HTTP OPTIONS and DELETE methods can be used in the actual request.

Access-Control-Allow-Credentials

The expected value is true. Indicates that the user credentials can be included in the request.

Access-Control-Allow-Headers

The expected value is MyOwnHeader1, MyOwnHeader2. Indicates that the header MyOwnHeader1 and MyOwnHeader2 are safe to expose.

The Access-Control-Allow-Origin header has a value of anywebsiteyoulike.com because the Liberty is configured to allow all origins, and the request came with an origin of anywebsiteyoulike.com.

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

If the testPreflightCorsRequest test passes, the response headers with their values from the endpoint are printed. The /configurations/preflight endpoint now allows preflight CORS requests.

Response headers with their values from the endpoint:

--- Preflight CORS Request ---
Header null = [HTTP/1.1 200 OK]
Header Access-Control-Allow-Origin = [anywebsiteyoulike.com]
Header Access-Control-Allow-Methods = [OPTIONS, DELETE]
Header Access-Control-Allow-Credentials = [true]
Header Content-Length = [0]
Header Access-Control-Max-Age = [10]
Header Date = [Thu, 21 Mar 2019 18:21:13 GMT]
Header Content-Language = [en-CA]
Header Access-Control-Allow-Headers = [MyOwnHeader1, MyOwnHeader2]
Header X-Powered-By = [Servlet/4.0]

You can modify the Liberty configuration and the test code to experiment with the various CORS configuration attributes.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

Great work! You’re done!

You enabled CORS support in Open Liberty. You added two different CORS configurations to allow two kinds of CORS requests in the Liberty server.xml configuration file.

Guide Attribution

Enabling Cross-Origin Resource Sharing (CORS) by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star