Skip to content

CORS Best Practices

Ricardo Campos edited this page Jul 26, 2024 · 1 revision

CORS

This document aims to clarify how CORS (Cross-Origin Resource Sharing) is handled between SPAR Client (frontend) and SPAR REST services (backend).

In simple words:

It enables JavaScripts running in browsers to connect to APIs and other web resources like fonts, and stylesheets from multiple different providers. [1]

Best Practices

CORS best practices says only allowed origins should have access to services. This is exactly what SPAR has currently implemented.

TL;DR;

  • Frontend: can receive requests from the public internet, from anywhere.
  • Backend services (Postgres and Oracle): can receive requests as follows:
    • Locally: From http://localhost:3000 only
    • DEV namespace: From both http://localhost:3000 and OpenShift at https://*.apps.silver.devops.gov.bc.ca
    • TEST and PROD namespaces: From OpenShift only, at https://apps.silver.devops.gov.bc.ca

OpenShift

All these applications are being deployed on OpenShift. This means a NetworkPolicy is required to manage communication between them. Currently, there's an Ingress policy in place along with a second policy allowing communication from a specific namespace (namespace selector). This setup is sufficient to cover the running application. Take a look at this piece of code to learn more.

Communication with the internet

Each application holds its own configuration, which allow external communication with the public internet. They are:

  • Frontend: The client-side application is served through Caddy with static HTML, JavaScript and styling assets. For this app, all the rules can be found in the Caddyfile, which allows incoming requests from the public internet.
  • Postgres Backend: This is one of the REST API services responsible for providing Postgres database access and storage for the frontend client, written in Java on top of Spring Boot. For this application, there's a configuration allowing incoming requests from specifics origins, set previously. If the origin is not in the whitelist, the request will be blocked, resulting in CORS errors. The config file can be found here.
  • Oracle Backend: This is another one of the REST API services responsible for providing Oracle database access and storage for the client, also written in Java with Spring Boot. The configuration is the same as the Postgres backend, meaning that both backend services can receive requests from the same origins. The config file for this one can be found here.

Lessons learned

When working with CORS, there's must be a whitelist in place of all allowed origins. For new SPAR, with new URLs in place, before making the proper configuration, here's how the issue ca be seeing:

cors

Code Samples

Kubernetes Network Policy template

- apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-from-openshift-ingress
      labels:
        template: openshift-name
    spec:
      ingress:
        - from:
            - namespaceSelector:
                matchLabels:
                  network.openshift.io/policy-group: ingress
      policyTypes:
        - Ingress
  - apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-same-namespace
      labels:
        template: nr-spar-backend-network-security-policy
    spec:
      policyTypes:
        - Ingress

Caddyfile header configuration for the client app

header {
		X-Frame-Options "SAMEORIGIN"
		X-XSS-Protection "1;mode=block"
		Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate"
		X-Content-Type-Options "nosniff"
		Strict-Transport-Security "max-age=111"
		Content-Security-Policy "base-uri 'self'; ...
		Referrer-Policy "same-origin"
	}

Class configuration in Java to handle CORS

@Configuration
public class CorsConfig implements WebMvcConfigurer {

  @Value("${server.allowed.cors.origins}")
  private String[] allowedOrigins;

  @Override
  public void addCorsMappings(@NonNull CorsRegistry registry) {
    if (allowedOrigins != null && allowedOrigins.length != 0) {
      SparLog.info("allowedOrigins: {}", Arrays.asList(allowedOrigins));

      registry
          .addMapping("/**")
          .allowedOriginPatterns(allowedOrigins)
          .allowedMethods("GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS", "HEAD");
    }
    WebMvcConfigurer.super.addCorsMappings(registry);
  }
}

References

Clone this wiki locally