Running ReactJS application with HTTPS and backend APIs behind a Kubernetes Ingress

8/24/2020

I am developing a ReactJS application that is calling REST APIs running in kubernetes. The setup is as follows:

  • ReactJS being developed/debugged locally and ran with "npm start" because nothing beats how fast the local development server detects changes and reload the browser when changes are detected.
  • ReactJS API requests are done with axios
  • Backend APIs written in GO running as separate deployment/services locally in minikube.
  • There is an Ingress installed locally in minikube to forward requests from urlshortner.local to the respective k8s service.

The basic idea is the following:

ReactJS -> k8s ingress -> GO REST API

Now the problem starts when I try to set secure httpOnly cookies. Because the cookie needs to be secure, I created a self signed ssl certificate and applied it to be used by the ingress. I also enabled CORS settings in the ingress configuration. I also configured axios to not reject self signed certificates.

For some reason that is unknown to me I can't success in making the request.

Below are my relevant config files and code snippets:

k8s ingress:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: url-shortner-backend-services
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://localhost:4000"
    nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
spec:
  tls:
    - secretName: urlshortner-local-tls
      hosts:
        - urlshortner.local
  rules:
    - host: urlshortner.local
      http:
        paths:
          - path: /shortner(/|$)(.*)
            backend:
              serviceName: url-shortener-service
              servicePort: 3000
          - path: /auth(/|$)(.*)
            backend:
              serviceName: auth-service
              servicePort: 3000

The react application start scripts:

PORT=4000 SSL_CRT_FILE=tls.crt SSL_KEY_FILE=tls.key react-scripts start

The axios code snippet that creates an axios instance that is used to issue a POST request

import axios from "axios";
import https from "https";

export default axios.create({
    baseURL: 'https://urlshortner.local',
    withCredentials: true,
    httpsAgent: new https.Agent({
        rejectUnauthorized: false
    })
});

When a POST request is made, I see the following error in the browser console/network tab even though when I first load the page I am accepting the certificate warning and adding it as a trusted certificate: cors error in Firefox

The end result that I would like to achieve is to be able to set a cookie and read the cookie on subsequent requests.

The cookie is being set as follows:

c.SetSameSite(http.SameSiteNoneMode)
c.SetCookie("token", resp.Token, 3600, "/", "localhost:4000", true, true)

What is missing? What am I doing wrong? Thanks in advance

-- Fouad
axios
cors
kubernetes
reactjs
ssl

1 Answer

8/25/2020

I finally managed to fix this issue and the good news is that you don't need to create a self signed certificate.

The steps are the following:

  1. set a HOST environment variable before starting your development react server.
  2. adjust /etc/hosts so that 127.0.0.1 points to the value set in the HOST environment variable
  3. adjust your k8s ingress CORS settings to allow "cors-allow-origin" from the domain set in the HOST environment variable
  4. setting cookies should now work as expected.

Below are the relevant code snippets:

  • npm start script
      "scripts": {
        "start": "PORT=4000 HOST=app.urlshortner.local react-scripts start",
      }

notice the HOST environment variable, the PORT environment variable is optional, I'm using it because the default port 3000 is already taken.

  • /etc/hosts
    127.0.0.1       app.urlshortner.local
    192.168.99.106  urlshortner.local

note that 192.168.99.106 is my local minikube ip address.

  • Kubernetes ingress configuration
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
      name: url-shortner-backend-services
      namespace: default
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /$2
        nginx.ingress.kubernetes.io/enable-cors: "true"
        nginx.ingress.kubernetes.io/cors-allow-origin: "http://app.urlshortner.local:4000"
        nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
    spec:
      rules:
        - host: urlshortner.local
          http:
            paths:
              - path: /shortner(/|$)(.*)
                backend:
                  serviceName: url-shortener-service
                  servicePort: 3000
              - path: /auth(/|$)(.*)
                backend:
                  serviceName: auth-service
                  servicePort: 3000

What matters here is the following:

    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "http://app.urlshortner.local:4000"
    nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
  • axios instance used
    import axios from "axios";
    
    let baseURL = '';
    if (process.env.NODE_ENV === 'development') {
        baseURL = 'http://urlshortner.local';
    }
    
    export default axios.create({
        baseURL,
        withCredentials: true
    });
  • How the cookie is set:
    c.SetCookie("token", resp.Token, 3600, "/", ".urlshortner.local", false, true)

note the domain used. It starts with a "."

I hope this helps someone.

-- Fouad
Source: StackOverflow