40

I'm building an electron app and need to call APIs where the API provider has not enabled CORS. The typically proposed solution is to use a reverse proxy which is trivial to do when running locally by using node and cors-anywhere like this:

let port = (process.argv.length > 2) ? parseInt (process.argv[2]) : 8080; 
require ('cors-anywhere').createServer ().listen (port, 'localhost');

The app can then be configured to proxy all requests through the reverse proxy on localhost:8080.

So, my questions are:

  1. Is it possible to use node and cors-anywhere in an electron app to create a reverse proxy? I don't want to force the app to make calls to a remote server.

  2. Is there a better or standard way of doing this in an Electron app? I'm assuming I'm not the first to run into CORS issues. :)

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Shagymoe
  • 1,296
  • 1
  • 15
  • 22
  • 2
    If you make your web requests in the main process (i.e. not a renderer process), you won’t need to worry about CORS. How and where *are* you making such requests? – Kirk Larkin Jul 09 '18 at 22:05
  • I haven't incorporated my web app into Electron yet. It's currently just an Ember.js app which must use a reverse proxy because it's being used in a browser. – Shagymoe Jul 09 '18 at 22:07
  • 1
    the "browser" in electron can be put in unsafe mode to ignore CORS. see https://stackoverflow.com/questions/30101537/cross-domain-ajax-call-in-atom-shell – 1mike12 Aug 31 '18 at 14:53
  • 1
    If you make web requests in the main process, nodejs does not respect the users proxy settings or support certificates in the Windows Certificate store so your app won't work on some peoples machines. Requesting via the main process is not the answer! – Tim Oct 07 '20 at 21:56
  • Thanks for the hint. That sucks. I thought I had found the ideal solution. Maybe it still is the best way to go because it seems like these things can be retrofitted somewhat easily. – Paul Apr 15 '23 at 23:27

6 Answers6

15

Just overide header before send request using webRequest.onBeforeSendHeaders

const filter = {
  urls: ['*://*.google.com/*']
};
const session = electron.remote.session
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
    details.requestHeaders['Origin'] = null;
    details.headers['Origin'] = null;
    callback({ requestHeaders: details.requestHeaders })
});

put these codes in renderer process

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Tuan Nguyen
  • 151
  • 1
  • 5
6

In my application, it wasn't sufficient to remove the Origin header (by setting it to null) in the request. The server I was passing the request to always provided the Access-Control-Allow-Origin header in the response, regardless of it the Origin header is present in the request. So the embedded instance of Chrome did not like that the ACAO header did not match its understanding of the origin.

Instead, I had to change the Origin header on the request and then restore it on the Access-Control-Allow-Origin header on the response.

app.on('ready', () => {
  // Modify the origin for all requests to the following urls.
  const filter = {
    urls: ['http://example.com/*']
  };

  session.defaultSession.webRequest.onBeforeSendHeaders(
    filter,
    (details, callback) => {
      console.log(details);
      details.requestHeaders['Origin'] = 'http://example.com';
      callback({ requestHeaders: details.requestHeaders });
    }
  );

  session.defaultSession.webRequest.onHeadersReceived(
    filter,
    (details, callback) => {
      console.log(details);
      details.responseHeaders['Access-Control-Allow-Origin'] = [
        'capacitor-electron://-'
      ];
      callback({ responseHeaders: details.responseHeaders });
    }
  );

  myCapacitorApp.init();
});
psyklopz
  • 2,283
  • 4
  • 24
  • 29
3

Try this if you are running web apps in localhost

const filter = {
  urls: ['http://example.com/*'] // Remote API URS for which you are getting CORS error
}

browserWindow.webContents.session.webRequest.onBeforeSendHeaders(
  filter,
  (details, callback) => {
    details.requestHeaders.Origin = `http://example.com/*`
    callback({ requestHeaders: details.requestHeaders })
  }
)

browserWindow.webContents.session.webRequest.onHeadersReceived(
  filter,
  (details, callback) => {
    details.responseHeaders['access-control-allow-origin'] = [
      'capacitor-electron://-',
      'http://localhost:3000' // URL your local electron app hosted
    ]
    callback({ responseHeaders: details.responseHeaders })
  }
)
  • 6
    Chrome tells me this: `The 'Access-Control-Allow-Origin' header contains multiple values 'capacitor-electron://-, http://localhost:3000', but only one is allowed.` – serg06 Aug 05 '21 at 06:55
2

Just had this issue today API calls with axios inside a React app bundled in Electron is returning 400

From what I can see Electron calls act as normal calls to the API urls meaning they are not affected by CORS.

Now when you wrap your calls with a CORS proxy and make a regular call to the proxy, it should error with a 400 error because it's not a CORS call. This thread explains why cors-anywhere responds like that => https://github.com/Rob--W/cors-anywhere/issues/39

I actually removed my CORS proxies from the app before the Electron build. I still need the CORS proxy for development since I'm testing in the browser.

Hope this helps.

alanionita
  • 1,272
  • 1
  • 16
  • 34
2

You can have the main process, the NodeJS server running Electron, send the request. This avoids CORS because this is a server-to-server request. You can send an event from the frontend (the render process) to the main process using IPC. In the main process you can listen to this event, send the HTTP request, and return a promise to the frontend.

In main.js (the script where the Electron window is created):

import { app, protocol, BrowserWindow, ipcMain } from ‘electron’
import axios from 'axios'

ipcMain.handle('auth', async (event, ...args) => {
  console.log('main: auth', event, args)  const result = await axios.post(
    'https://api.com/auth',
    {
      username: args[0].username,
      password: args[0].password,
      auth_type: args[1],
    },
  )  console.log('main: auth result', result)
  console.log('main: auth result.data', result.data)  return result.data
})

In your frontend JS:

import { ipcRenderer } from 'electron'

sendAuthRequestUsingIpc() {
  return ipcRenderer.invoke('auth',
    {
      username: AuthService.username,
      password: AuthService.password,
    },
    'password',
  ).then((data) => {
    AuthService.AUTH_TOKEN = data['access_token']
    return true
  }).catch((resp) => console.warn(resp))
}

I wrote an article that goes into more depth here.

mand
  • 395
  • 2
  • 13
  • Do you experience delays in processing of axios request? I find that in main process the request is detected quickly and axios function called but it takes several seconds for axios call to complete. If I call from within renderer process (disabling web security) then axios call responses is pretty much instantaneous. – colinbes Apr 07 '21 at 21:01
1

While I have struggled a while with the existing answers I will provide here the solution that finally worked for me, assuming that you are on the main process.

Here are the steps involved:

  • You need to have access to the session object which can be obtained by one of two ways:

    A) via the global session.defaultSession which is available after the app is ready.

     const { session } = require('electron');
     const curSession = session.defaultSession;
    

    B) The other method is via the session on the BrowserWindow, this assumes that the windnows is already created.

     win = new BrowserWindow({});
     const curSession = win.webContents.session;
    

  • Once you have the session object you set the response header to the site you are sending the request from.

    For example, let's say your electron BrowserWindow is loaded from http://localhost:3000 and you are making a request to example.com, here would be some sample code:

     const { app, BrowserWindow, session } = require('electron');
     app.whenReady().then(_ => {
          // If using method B for the session you should first construct the BrowserWindow
          const filter = { urls: ['*://*.example.com/*'] };
    
          session.defaultSession.webRequest.onHeadersReceived(filter, (details, callback) => {
    
              details.responseHeaders['Access-Control-Allow-Origin'] = [ 'http://localhost:3000' ];
              callback({ responseHeaders: details.responseHeaders });
          }
          // Construct the BrowserWindow if haven't done so yet...
      }); 
    
yoel halb
  • 12,188
  • 3
  • 57
  • 52