Contents

Overview

The cross-origin resource sharing (CORS) protocol helps developers control if and how REST resources served by their applications can be shared across origins. Helidon SE includes an implementation of CORS that you can use to add CORS behavior to the services you develop. You can define your application’s CORS behavior programmatically using the Helidon CORS API alone, or together with configuration.

Helidon also provides three built-in services that add their own endpoints to your application — health, metrics, and OpenAPI — that have integrated CORS support. By adding very little code to your application, you control how all the resources in your application — the ones you write and the ones provided by the Helidon built-in services — can be shared across origins.

Before You Begin

Before you revise your application to add CORS support, you need to decide what type of cross-origin sharing you want to allow for each resource your application exposes. For example, suppose for a given resource you want to allow unrestricted sharing for GET, HEAD, and POST requests (what CORS refers to as "simple" requests), but permit other types of requests only from the two origins foo.com and there.com. Your application would implement two types of CORS sharing: more relaxed for the simple requests and stricter for others.

Once you know the type of sharing you want to allow for each of your resources — including any from built-in services — you can change your application accordingly.

Maven Coordinates

To enable CORS add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver-cors</artifactId>
</dependency>
Copied

API

Every Helidon SE application explicitly creates routing rules that govern how Helidon delivers each incoming request to the code that needs to respond. To add CORS behavior to endpoints, you need to make only minimal changes to how you set up the routing for those endpoints. Using the Helidon SE CORS API, you define the CORS behavior that you want and then include that behavior as you build the routing rules for the services in your application.

The Helidon SE CORS API provides two key classes that you use in your application:

  • CorsSupport - Represents information about resource sharing for a single resource. Typically, you create one CorsSupport instance for each distinct resource in your application (such as the /greet resource in the QuickStart greeting application) that should participate in CORS.

  • CrossOriginConfig - Represents the details for a particular type of sharing, such as which origins are allowed to have access using which HTTP methods, etc. Create one instance of CrossOriginConfig for each different type of sharing you need.

You associate one or more CrossOriginConfig objects with each CorsSupport object. You use the CorsSupport object when you construct the routing rules for the service. When your application is running and requests arrive, the Helidon CORS implementation enforces the CORS behavior represented by the CorsSupport object before routing the request to your endpoint code for the resource.

Because Helidon SE does not use annotation processing to identify endpoints, you need to provide the CORS information for your application another way — by including CORS into the routing you construct for your application.

For each distinct resource or subresource your application exposes:

  1. Create a CorsSupport instance corresponding to the resource.
  2. For each different type of sharing you want to provide for that resource:
    1. Create a CrossOriginConfig instance.
      The CrossOriginConfig Java class represents the details for a particular type of sharing, such as which origins are allowed to share via which HTTP methods, etc.
    2. Add the CrossOriginConfig to the CorsSupport instance for this resource.
  3. Use the resource’s CorsSupport object in setting up the routing rules for that resource.

Each of these classes has an associated builder that you use in constructing instances of the class.

The table below describes the methods on the CrossOriginConfig.Builder class and the configuration keys that map to the headers defined in the CORS protocol. (A later section discusses configuration.)

builder methodconfig keytypedefaultdescriptionCORS header name
allowCredentialsallow-credentialsbooleanfalseSets the allow credentials flag.Access-Control-Allow-Credentials
allowHeadersallow-headersstring[]*Sets the allowed headers.Access-Control-Allow-Headers
allowMethodsallow-methodsstring[]*Sets the allowed methods.Access-Control-Allow-Methods
allowOriginsallow-originsstring[]*Sets the allowed origins.Access-Control-Allow-Origins
exposeHeadersexpose-headersstring[] Sets the expose headers.Access-Control-Expose-Headers
maxAgeSecondsmax-age-secondslong3600Sets the maximum age.Access-Control-Max-Age
enabledenabledbooleantrueSets whether this config should be enabled or not.n/a

If the cross-origin configuration is disabled (enabled = false), then the Helidon CORS implementation ignores the cross-origin configuration entry.

Sample Routing Setup Using the CrossOriginConfig API

The Helidon SE Quickstart application lets you change the greeting by sending a PUT request to the /greet/greeting resource.

This example, based on the QuickStart greeting app, uses the low-level CrossOriginConfig API and the CorsSupport API to influence the routing, thereby determining how that resource is shared. (If desired, you can use configuration instead of the low-level API.)

The following code shows how to prepare your application’s routing to support metrics and health support, as well as CORS.

private static Routing createRouting(Config config) {
    MetricsSupport metrics = MetricsSupport.create();
    GreetService greetService = new GreetService(config);
    HealthSupport health = HealthSupport.builder()
            .addLiveness(HealthChecks.healthChecks())   // Adds a convenient set of checks
            .build();
    CorsSupport corsSupport = CorsSupport.builder()  
            .addCrossOriginConfig(CrossOriginConfig.builder() 
                        .allowOrigins("http://foo.com", "http://there.com") 
                        .allowMethods("PUT", "DELETE") 
                        .build()) 
            .addCrossOriginConfig(CrossOriginConfig.create()) 
            .build(); 

    // Note: Add the CORS routing *before* registering the GreetService routing.
    return Routing.builder()
            .register(JsonSupport.create())
            .register(health)                   // Health at "/health"
            .register(metrics)                 // Metrics at "/metrics"
            .register("/greet", corsSupport, greetService) 
            .build();
}
Copied
  • Create a CorsSupport.Builder instance.
  • Add a CrossOriginSupport instance (using its builder) to constrain resource sharing.
  • List the origins (sites) allowed to share resources from this app.
  • List the HTTP methods the constraint applies to.
  • Build the CrossOriginSupport instance.
  • Add a CrossOriginSupport instance that permits all sharing (the default).
  • Build the CorsSupport instance.
  • Register the new CorsSupport instance with — but in front of — the service which implements the business logic.

The order of steps 2 and 6 above is important. When processing an incoming request, the Helidon CORS implementation scans the CrossOriginConfig instances in the order they were added to the CorsSupport object, stopping as soon as it finds a CrossOriginConfig instance for which allowMethods matches the HTTP method of the request.

The few additional lines described above allow the greeting application to participate in CORS.

Configuration

You can use configuration in combination with the Helidon CORS SE API to add CORS support to your resources by replacing some Java code with declarative configuration. This also gives your users a way to override the CORS behavior of your services without requiring the code to change.

Understanding the CORS Configuration Formats

Support in Helidon for CORS configuration uses two closely-related cross-origin configuration formats: basic and mapped. Each format corresponds to a class in the Helidon CORS library. The basic format corresponds to the CrossOriginConfig class, and the mapped format corresponds to the MappedCrossOriginConfig class.

Basic Cross-Origin Configuration

In configuration, Helidon represents basic CORS information as a section, identified by a configuration key of your choosing, that contains one or more key/value pairs. Each key-value pair assigns one characteristic of CORS behavior.

The table below lists the configuration keys that identify the CORS characteristics.

include::[tag=cors-config-table]

The following example of basic cross-origin configuration, when loaded and used by the application, limits cross-origin resource sharing for PUT and DELETE operations to only foo.com and there.com:

restrictive-cors:
  allow-origins: ["http://foo.com", "http://there.com"]
  allow-methods: ["PUT", "DELETE"]
Copied

Mapped Cross-Origin Configuration

In some cases, you or your users might want to configure CORS behavior based on URL path matching. Helidon represents mapped CORS information as a section, identified by a configuration key of your choosing, that contains:

  • An optional enabled setting which defaults to true and applies to the whole mapped CORS config section, and

  • An optional paths subsection containing zero or more entries, each of which contains:

    • a basic CORS config section, and

    • a path-pattern path pattern that maps that basic CORS config section to the resource(s) it affects.

You can use mapped configuration to your advantage if you want to allow your users to override the CORS behavior set up in the application code.

The following example illustrates the mapped cross-origin configuration format.

my-cors: 
  paths: 
    - path-pattern: /greeting 
      allow-origins: ["http://foo.com", "http://there.com", "http://other.com"] 
      allow-methods: ["PUT", "DELETE"]
    - path-pattern: / 
      allow-methods: ["GET", "HEAD", "OPTIONS", "POST"] 
Copied
  • Assigns a unique identifier for this mapped CORS config section.
  • Collects the sequence of entries, each of which maps a basic CORS config to a path pattern.
  • Marks the beginning of an entry (the - character) and maps the associated basic CORS config to the /greeting subresource (the path-pattern key and value).
  • Begins the basic CORS config section for /greeting; it restricts sharing via PUT and DELETE to the listed origins.
  • Marks the beginning of the next entry (the - character) and maps the associated basic CORS config to the top-level resource in the app (the path-pattern key and value).
  • Begins the basic CORS config section for /; it permits sharing of resources at the top-level path with all origins for the indicated HTTP methods.

Path patterns can be any expression accepted by the PathMatcher class.

Be sure to arrange the entries in the order that you want Helidon to check them. Helidon CORS support searches the cross-origin entries in the order you define them until it finds an entry that matches an incoming request’s path pattern and HTTP method.

Using CORS Configuration in the Application

You use configuration in combination with the Helidon CORS SE API to add CORS support to your resources. The example in Sample Routing Setup Using the CrossOriginConfig API uses the low-level Helidon CORS SE API to create a CrossOriginConfig instance that is then used as part of a CorsSupport instance to create the routing rules. As an alternative to using the low-level API, this example uses config to create the CrossOriginConfig instance instead.

private static Routing createRouting(Config config) {

    MetricsSupport metrics = MetricsSupport.create();
    GreetService greetService = new GreetService(config);
    HealthSupport health = HealthSupport.builder()
            .addLiveness(HealthChecks.healthChecks())   // Adds a convenient set of checks
            .build();
    CorsSupport.Builder builder = CorsSupport.builder();

    Config config = Config.create(); // Created from the current config sources
    config.get("my-cors") 
            .ifExists(builder::mappedConfig);
    config.get("restrictive-cors") 
            .ifExists(builder::config);
    builder.addCrossOriginConfig(CrossOriginConfig.create()); 

    CorsSupport corsSupport = builder.build(); 

    // Note: Add the CORS routing *before* registering the GreetService routing.
    return Routing.builder()
            .register(JsonSupport.create())
            .register(health)                   // Health at "/health"
            .register(metrics)                 // Metrics at "/metrics"
            .register("/greet", corsSupport, greetService) 
            .build();
}
Copied
  • If my-cors exists in the configuration, use it to add mapped CORS config to the CorsSupport builder.
  • If restrictive-cors exists in the configuration, use it to add basic (not mapped) config to the builder.
  • Provide default CORS handling for requests that do not match earlier entries.
  • Obtain the finished CorsSupport instance.
  • Use corsSupport in constructing the routing rules.

As each request arrives, Helidon checks it against the cross-origin config instances in the order that your application added them to the CorsSupport.Builder. The my-cors mapped configuration acts as an override because the application added it to the builder first.

If the my-cors config key does not appear in the configuration, then the code skips creating a CrossOriginConfig instance based on that configuration, and no overriding occurs. The CORS behavior that is established by the other CrossOriginConfig instance based on the restrictive-cors config (if present) prevails.

Remember that if you set configuration in a file that you include as part of your application JAR file, then you need to rebuild and restart your application for any changes to take effect.

Examples

For a complete example, see Helidon SE CORS Example.

Additional Information

Using CORS Support in Built-in Helidon Services

Several built-in Helidon services—​health, metrics, and OpenAPI--have integrated CORS support. You can include these services in your application and control how those resources can be shared across origins.

For example, several websites related to OpenAPI run a web application in your browser. You provide the URL for your application to the browser application. The browser application uses the URL to retrieve the OpenAPI document that describes the application’s endpoints directly from your application. The browser application then displays a user interface that you use to "drive" your application. That is, you provide input, have the web application send requests to your application endpoints, and then view the responses. This scenario is exactly the situation CORS addresses: an application in the browser from one origin — the user interface downloaded from the website — requests a resource from another origin — the /openapi endpoint which Helidon’s OpenAPI built-in service automatically adds to your application.

Integrating CORS support into these built-in services allows such third-party web sites and their browser applications — or more generally, apps from any other origin — to work with your Helidon application.

Because all three of these built-in Helidon services serve only GET endpoints, by default the integrated CORS support in all three services permits any origin to share their resources using GET, HEAD, and OPTIONS HTTP requests. You can customize the CORS set-up for these built-in services independently from each other using either the Helidon API, configuration, or both. You can use this override feature to control the CORS behavior of the built-in services even if you do not add CORS behavior to your own endpoints.

Built-in Services with CORS

To use built-in services with CORS support and customize the CORS behavior:

  1. Add the built-in service or services to your application. The health, metrics, and OpenAPI services automatically include default CORS support.
  2. Add a dependency on the Helidon SE CORS artifact to your Maven pom.xml file.

    If you want the built-in services to support CORS, then you need to add the CORS dependency even if your own endpoints do not use CORS.

  3. Use the Helidon API or configuration to customize the CORS behavior as needed.

The documentation for the individual built-in services describes how to add each service to your application, including adding a Maven dependency and including the service in your application’s routing rules. In your application’s configuration file, the configuration for each service appears under its own key.

Helidon Service DocumentationConfiguration Key
healthhealth
metricsmetrics
OpenAPIopenapi

The Helidon SE QuickStart example uses these services, so you can use that as a template for your own application, or use the example project itself to experiment with customizing the CORS behavior in the built-in services.

Controlling CORS for Built-in Services

Using the API

Although services such as health, metrics, and OpenAPI are built into Helidon, to use them your application must create instances of the services and then use those instances in building your application’s routing rules.

Recall that each service type has a Builder class. To control the CORS behavior of a built-in service using the API, follow these steps:

  1. Create a Builder for the type of service of interest.
  2. Build an instance of CrossOriginConfig with the settings you want.
  3. Invoke the builder.crossOriginConfig method, passing that CrossOriginConfig instance.
  4. Invoke the builder’s build method to initialize the service instance.
  5. Use the service instance in preparing the routing rules.

The following excerpt shows changes to the Helidon SE QuickStart example which limit sharing of the /metrics endpoint to http://foo.com.

private static Routing createRouting(Config config) {
    CrossOriginConfig.Builder metricsCrossOriginConfigBuilder = CrossOriginConfig.builder() 
            .allowOrigins("http://foo.com");
    RestServiceSettings.Builder restServiceSettingsBuilder = RestServiceSettings.builder()
             .crossOriginConfig(metricsCrossOriginConfigBuilder); 
    MetricsSupport metrics = MetricsSupport.builder()
            .restServiceSettings(restServiceSettingsBuilder) 
            .build();
    GreetService greetService = new GreetService(config);
    HealthSupport health = HealthSupport.builder()
            .addLiveness(HealthChecks.healthChecks())   // Adds a convenient set of checks
            .build();

    return Routing.builder()
            .register(health)                   // Health at "/health"
            .register(metrics)                  // Metrics at "/metrics" 
            .register("/greet", greetService)
            .build();
}
Copied
  • Create the CrossOriginConfig.Builder for metrics, limiting sharing to http://foo.com.
  • Use the CrossOriginConfig.Builder instance in constructing the RestServiceSetting.Builder (which assigns common settings such as the CORS configuration and the web context for the service endpoint).
  • Use the RestServiceSetting.Builder in preparing the MetricsSupport service.
  • Use the MetricsSupport object in creating the routing rules.
Configuring CORS for Built-in Services

You can also use configuration to control whether and how each of the built-in services works with CORS.

Your application can pass configuration to the builder for each built-in service. In the configuration for the health, metrics, and OpenAPI services, you can add a section for CORS.

The following example restricts sharing of the /health resource, provided by the health built-in service, to only the origin http://there.com.

health:
  cors:
    allow-origins: [http://there.com]
Copied

Modify your application to load the health config node and use it to construct the HealthSupport service. The following code shows this change in the the QuickStart SE example.

HealthSupport health = HealthSupport.builder()
        .config(config.get("health")) 
        .addLiveness(HealthChecks.healthChecks())   // Adds a convenient set of checks
        .build();
Copied
  • Use the health config section (if present) to configure the health service.

You have full control over the CORS configuration for a built-in Helidon service. Use a CORS config section as described in Using Configuration for CORS.

Accessing the Shared Resources

If you have edited the Helidon SE QuickStart application as described in the previous topics and saved your changes, you can build and run the application. Once you do so you can execute curl commands to demonstrate the behavior changes in the metric and health services with the addition of the CORS functionality. Note the addition of the Origin header value in the curl commands, and the Access-Control-Allow-Origin in the successful responses.

Build and Run the Application

Build and run the QuickStart application as usual.

mvn package
java -jar target/helidon-quickstart-se.jar
Copied
WEB server is up! http://localhost:8080/greet
Copied
Retrieve Metrics

The metrics service rejects attempts to access metrics on behalf of a disallowed origin.

curl -i -H "Origin: http://other.com" http://localhost:8080/metrics
Copied
HTTP/1.1 403 Forbidden
Date: Mon, 11 May 2020 11:08:09 -0500
transfer-encoding: chunked
connection: keep-alive
Copied

But accesses from foo.com succeed.

curl -i -H "Origin: http://foo.com" http://localhost:8080/metrics
Copied
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.com
Content-Type: text/plain
Date: Mon, 11 May 2020 11:08:16 -0500
Vary: Origin
connection: keep-alive
content-length: 6065

# TYPE base_classloader_loadedClasses_count gauge
# HELP base_classloader_loadedClasses_count Displays the number of classes that are currently loaded in the Java virtual machine.
base_classloader_loadedClasses_count 3568
Copied
Retrieve Health

The health service rejects requests from origins not specifically approved.

curl -i -H "Origin: http://foo.com" http://localhost:8080/health
Copied
HTTP/1.1 403 Forbidden
Date: Mon, 11 May 2020 12:06:55 -0500
transfer-encoding: chunked
connection: keep-alive
Copied

And responds successfully only to cross-origin requests from http://there.com.

curl -i -H "Origin: http://there.com" http://localhost:8080/health
Copied
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://there.com
Content-Type: application/json
Date: Mon, 11 May 2020 12:07:32 -0500
Vary: Origin
connection: keep-alive
content-length: 461

{"outcome":"UP",...}
Copied