How do you handle CORS in an electron app?
Asked Answered
H

7

48

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. :)

Homecoming answered 9/7, 2018 at 22:0 Comment(5)
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?Projective
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.Homecoming
the "browser" in electron can be put in unsafe mode to ignore CORS. see #30102037Mello
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!Smashing
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.Middleman
C
17

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

Coster answered 30/5, 2019 at 10:49 Comment(1)
Not working for me :( Or I put it in the wrong place ?Floccose
C
7

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();
});
Calica answered 29/10, 2020 at 15:39 Comment(3)
Changing response headers works.Bandurria
How/where did you define session here?Solus
import { app, session } from 'electron'; will import it, nothing more is needed.Calica
O
5

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.

Oenomel answered 18/2, 2021 at 20:38 Comment(1)
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.Gayden
S
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 })
  }
)
Secretion answered 23/2, 2021 at 2:14 Comment(1)
Chrome tells me this: The 'Access-Control-Allow-Origin' header contains multiple values 'capacitor-electron://-, http://localhost:3000', but only one is allowed.Desuetude
P
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.

Preoccupation answered 31/8, 2018 at 10:46 Comment(0)
C
2

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...
      }); 
    
Corn answered 29/10, 2021 at 1:56 Comment(1)
This is the only solution here that worked for me, thanks!Groundling
P
0

After struggling with these CORS issues and Electron and my Phaser Game, my issues were fixed when I removed the service worker that I had in my index.html. This service worker was caching files and when loading from cache, it violated the CORS and then stopped my app from loading. Make sure to unregister the service worker under the devtools of chrome if you have already registered it.

Then I no longer needed any security disabling features or request/response header rewriting. Hope this helps!

Presentational answered 8/3 at 17:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.