Suntex
Suntex

Reputation: 1

Auth0 with Electron working in development but not when packaged

Im using Auth0 in an electron app to manage a log-in system. I referenced this tutorial here: https://auth0.com/blog/securing-electron-applications-with-openid-connect-and-oauth-2/ to get started with using it.

Auth0 has been working great when I've been working in development but for some reason fails after I call "yarn package" to build the app. My electron app used electron-react-boilerplate (https://github.com/electron-react-boilerplate/electron-react-boilerplate).

Here are the important files:

// imports
...
import {
  getAuthenticationURL,
  refreshTokens,
  loadTokens,
  logout,
  getLogOutUrl,
  getProfile,
  getResponse,
} from './services/authservice';

export default class AppUpdater {
  constructor() {
    ...
  }
}

let mainWindow: BrowserWindow | null = null;

if (process.env.NODE_ENV === 'production') {
  const sourceMapSupport = require('source-map-support');
  sourceMapSupport.install();
}

if (
  process.env.NODE_ENV === 'development' ||
  process.env.DEBUG_PROD === 'true'
) {
  require('electron-debug')();
}

const installExtensions = async () => {
  ...
};

const createWindow = async () => {
  console.log('now starting the main process');
  if (
    process.env.NODE_ENV === 'development' ||
    process.env.DEBUG_PROD === 'true'
  ) {
    await installExtensions();
  }

  const RESOURCES_PATH = app.isPackaged
    ? path.join(process.resourcesPath, 'assets')
    : path.join(__dirname, '../assets');

  const getAssetPath = (...paths: string[]): string => {
    return path.join(RESOURCES_PATH, ...paths);
  };

  mainWindow = new BrowserWindow({
    show: true,
    width: 1024,
    height: 728,
    titleBarStyle: 'hidden', // add this line
    frame: false,
    //icon: getAssetPath('icon.png'),
    webPreferences: {
      nodeIntegration: true,
    },
  });
  mainWindow.loadURL(`file://${__dirname}/index.html`);
  const devtools = new BrowserWindow();

  mainWindow.webContents.setDevToolsWebContents(devtools.webContents);
  mainWindow.webContents.openDevTools({ mode: 'detach' });

};

/**
 * Add event listeners...
 */

let win = null;

function createAuthWindow() {
  destroyAuthWin();

  win = new BrowserWindow({
    width: 1000,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      enableRemoteModule: false,
    },
  });
  console.log(getAuthenticationURL());
  win.loadURL(getAuthenticationURL());

  const {
    session: { webRequest },
  } = win.webContents;

  const filter = {
    urls: [
      'file:///callback*',
    ],
  };

  webRequest.onBeforeRequest(filter, async ({ url }) => {
    console.log(url);
    await loadTokens(url)
      .then((res) => {
        console.log(res);
      })
      .catch(console.log);
    console.log('from web request');
    createWindow();
    return destroyAuthWin();
  });

  win.on('authenticated', () => {
    console.log('WE HAVE AUTHENTICATED');
    destroyAuthWin();
  });

  win.on('closed', () => {
    win = null;
  });
}

function destroyAuthWin() {
  if (!win) return;
  win.close();
  win = null;
}

// logout logic: removed for simplicity 

ipcMain.on('profileRequest', (event, arg) => {
  //event.reply('profileResponse', getProfile());
  event.returnValue = getProfile();
});

const showWindow = async () => {
  try {
    await refreshTokens();
    return createWindow();
  } catch (err) {
    createAuthWindow();
  }
};


this is my auth services file:


let accessToken = null;
let profile = {};
let refreshToken = null;

export function getAccessToken() {
  return accessToken;
}
export function getProfile() {
  return profile;
}

export function getAuthenticationURL() {
  return (
    'https://' +
    auth0Domain +
    '/authorize?' +
    'scope=openid%20profile%20offline_access&' +
    'response_type=code&' +
    'client_id=' +
    clientId +
    '&' +
    'redirect_uri=' +
    redirectUri
  );
}

export async function refreshTokens() {
  const refreshToken = await keytar.getPassword(keytarService, keytarAccount);

  if (refreshToken) {
    const refreshOptions = {
      method: 'POST',
      url: `https://${auth0Domain}/oauth/token`,
      headers: { 'content-type': 'application/json' },
      data: {
        grant_type: 'refresh_token',
        client_id: clientId,
        refresh_token: refreshToken,
      },
    };

    try {
      const response = await axios(refreshOptions);
      res = response;

      accessToken = response.data.access_token;
      profile = jwtDecode(response.data.id_token);
    } catch (error) {
      await logout();

      throw error;
    }
  } else {
    throw new Error('No available refresh token.');
  }
}

export async function loadTokens(callbackURL) {
  console.log('loading tokens:');
  console.log(callbackURL);
  res = callbackURL;

  const urlParts = url.parse(callbackURL, true);
  const query = urlParts.query;

  console.log(query);

  const exchangeOptions = {
    grant_type: 'authorization_code',
    client_id: clientId,
    code: query.code,
    redirect_uri: redirectUri,
  };

  const options = {
    method: 'POST',
    url: `https://${auth0Domain}/oauth/token`,
    headers: {
      'content-type': 'application/json',
    },
    data: JSON.stringify(exchangeOptions),
  };

  try {
    const response = await axios(options);

    console.log('from token:');
    console.log(response);
    res = response;

    accessToken = response.data.access_token;
    profile = jwtDecode(response.data.id_token);
    refreshToken = response.data.refresh_token;

    console.log(getProfile());

    if (refreshToken) {
      await keytar.setPassword(keytarService, keytarAccount, refreshToken);
    }
  } catch (error) {
    await logout();

    throw error;
  }
}

I have a file in my components folder called "Auth.jsx" which has a "get profile" methods which interacts with the main process to get the profile

const getProfile = () => {
  return ipcRenderer.sendSync('profileRequest', true);
};

After I package the electron app, the getProfile method always returns null/undefined.

Here are the auth0 logs: Auth0 Logs It shows that there is a successful Login and Exchange.

Finally, here's my webpack file: "webpack.config.main.prod.babel"

/**
 * Webpack config for production electron main process
 */

import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import TerserPlugin from 'terser-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import CheckNodeEnv from '../scripts/CheckNodeEnv';
import DeleteSourceMaps from '../scripts/DeleteSourceMaps';

import dotenv from 'dotenv';

CheckNodeEnv('production');
DeleteSourceMaps();

const devtoolsConfig =
  process.env.DEBUG_PROD === 'true'
    ? {
        devtool: 'source-map',
      }
    : {};

export default merge(baseConfig, {
  ...devtoolsConfig,

  mode: 'production',

  target: 'electron-main',

  entry: './src/main.dev.ts',

  output: {
    path: path.join(__dirname, '../../'),
    filename: './src/main.prod.js',
  },

  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
    ],
  },

  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode:
        process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
      openAnalyzer: process.env.OPEN_ANALYZER === 'true',
    }),

    new webpack.EnvironmentPlugin({
      NODE_ENV: 'production',
      DEBUG_PROD: true,
      START_MINIMIZED: false,
      /*
          other environment variables, including auth0 domain name and clientID
      */
    }),
  ],

  /**
   * Disables webpack processing of __dirname and __filename.
   * If you run the bundle in node.js it falls back to these values of node.js.
   * https://github.com/webpack/webpack/issues/2010
   */
  node: {
    __dirname: false,
    __filename: false,
  },
});

Im suspecting the problem might have something to do with webpack since it's only the packaged version of the application that doesn't work properly. Im not sure exactly what the problem is, whether is a problem in the code or if I need to specifically change something within my Auth0 dashboard. If you have any suggestions or any ideas on how to debug let me know!

Upvotes: 0

Views: 623

Answers (1)

Jack
Jack

Reputation: 1

I had the exact same issue! I fixed it by changing the import method for jwt-decode from require to import

import jwtDecode from 'jwt-decode'

Upvotes: 0

Related Questions