Using axios to solve cors problem in electron

scene

After integrating axios in electron, it is found that axios cannot read the set Cookie response header, and axios cannot write the Cookie response header.

reason

Chromium does not support cross site access to HttpOnly cookies. electron has two processes: one is the main process and the other is the rendering process. Its rendering process is chromium's process and the main process is NodeJS's process. electron uses chromium's process to render the interface, so axios cannot modify and read cookies in chromium because HttpOnly is set on the server.

thinking

The IPC message mechanism of electron is used to trigger the network request event in the rendering process (Chromium process), and the event is sent to the main process of electron (NodeJS process). The axios network request is made in the main process to call the remote server API, because the main process is a NodeJS environment, not a Chromium browser environment, and is not limited by CORS. Finally, after processing the remote server API data, the IPC message mechanism of electron will be used again to send the data back to the rendering process for UI interface rendering.

electron

It is assumed that the electron ic quick tutorial has been completed, namely:
https://www.electronjs.org/docs/latest/tutorial/quick-start
In addition, the axios library has been installed, that is:

npm install axios

The following modifications are made to the basic project code of Electro:

main.js

const {
  app,
  BrowserWindow,
  BrowserView,
  session,
  ipcMain,
  WebContents,
} = require("electron");
const axios = require("axios").default;
// include the Node.js 'path' module at the top of your file
const path = require("path");

const baseURL = "https://www.xxxx.com";
var myCookie;
const axiosClient = axios.create({
  baseURL,
  headers: {
    "Session-Access-Origin": "xxx",
    "Content-Type": "application/json",
  },
});
axiosClient.interceptors.request.use(
  (config) => {
    if (myCookie !== undefined) {
    // Add Cookie request header
      config.headers["Cookie"] = myCookie;
    }
    return config;
  },
  (error) => Promise.reject(error.request.data.err)
);

function createWindow() {
  const win = new BrowserWindow({
    width: 1920,
    height: 1080,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      nodeIntegration: true,
      contextIsolation: false,
    },
  });

  win.webContents.openDevTools();
  win.loadFile("index.html");
}

app.whenReady().then(() => {
  createWindow();

  app.on("activate", function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on("window-all-closed", function () {
  if (process.platform !== "darwin") app.quit();
});

//ipcMain.on will accept the "btnclick" event sent from the rendering process
ipcMain.on("btnclick", function (event, arg) {
  var data = JSON.stringify({
    account: "xxx",
    password: "xxxx",
  });

  axiosClient
    .post("/api/user/userLogin", data)
    .then((res) => {
    // After successful login, get the set cookie
      myCookie = res.headers["set-cookie"][0].split(";")[0];
      console.log(myCookie);
		// Send login task finished event
		// Send the data to the rendering process to refresh the UI interface
      event.sender.send("login-task-finished", myCookie);
    })
    .catch((err) => console.log(err));
});

//ipcMain.on will accept the "getTime" event sent from the rendering process
ipcMain.on("getTime", function (event, arg) {
// Test again whether the login is successful. This interface can be called normally only after the login is successful
  axiosClient
    .get("/api/common/getNow")
    .then((res) => {
      const data = res.data;
      console.log(data);
      // Send gettime task finished event
		// Send the data to the rendering process to refresh the UI interface
      event.sender.send("getTime-task-finished", data.data);
    })
    .catch((err) => console.log(err));
});

renderer.js

// IPC message module introducing electron
const ipcRenderer = require("electron").ipcRenderer;

const btnclick = document.getElementById("loadnewwindow");
btnclick.addEventListener("click", function () {
  var arg = "secondparam";

  //Send btnclick event to main thread
  ipcRenderer.send("btnclick", arg); // ipcRender.send will pass the information to main process
});

const getTimeclick = document.getElementById("get_time");
getTimeclick.addEventListener("click", function () {
  var arg = "gettime";

  //Send getTime event to main thread
  ipcRenderer.send("getTime", arg); // ipcRender.send will pass the information to main process
});
// Receive the login task finished event of the main thread
ipcRenderer.on("login-task-finished", (event, param) => {
  var div = document.getElementById("session");
  div.innerHTML = param;
});
// Receive the gettime task finished event of the main thread
ipcRenderer.on("getTime-task-finished", (event, param) => {
  var div = document.getElementById("time");
  div.innerHTML = param;
});

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using Node.js <span id="node-version"></span>, Chromium
    <span id="chrome-version"></span>, and Electron
    <span id="electron-version"></span>.
    <button id="loadnewwindow">login</button>
    <br />
    SESSION:
    <label id="session"></label>
    <br />
    <button id="get_time">get time</button>
    <br />
    Time:
    <label id="time"></label>
    <br />
<!-- Rendering process script -->
    <script src="./renderer.js"></script>
  </body>
</html>

peload.js

window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const dependency of ['chrome', 'node', 'electron']) {
    replaceText(`${dependency}-version`, process.versions[dependency])
  }
})

summary

The main electron process is a NodeJS process, not a Chromium process, and is not restricted by CORS; The rendering process is a Chromium process, which is restricted by CORS. By running axios in the main process, you can break through CORS and read and modify cookies instead. The main process here is equivalent to the background main process of electron. Advanced rendering is used to render UI. To some extent, it is separated from the front end. The main process concept of electron is different from that of Android main thread. The main thread of Android cannot do time-consuming tasks. The main process of electron can do time-consuming operations without affecting the rendering process.
In addition, Daneng proposes to use the axios cookie jar support library to enhance axios. I haven't tried it here, so that's all.
Finally, the source code reference is given.
https://github.com/fxtxz2/electron_axios_cors_cookie

reference resources:

Posted on Sun, 24 Oct 2021 04:15:57 -0400 by elim