I've written an electron application on my linux machine, and I'm encountering an error when trying to run it on my windows machine.
The error is:
[16100:0412/130215.483:ERROR:cert_issuer_source_aia.cc(34)] Error parsing cert retrieved from AIA (as DER): ERROR: Couldn't read tbsCertificate as SEQUENCE ERROR: Failed parsing Certificate
I am launching two windows, one auth window, then one for the main process, to handle authentication via OAuth I followed this article: https://auth0.com/blog/securing-electron-applications-with-openid-connect-and-oauth-2, adapting it to my use-case with Keycloak.
When I run on my Linux machine (debian) everything works fine. When I run using my windows machine, I'm getting the error.
The issue I'm having, is I cannot find where the error is coming from. I'm not getting any logs anywhere, except for what I've already provided.
I've tried adding app.commandLine.appendSwitch('ignore-certificate-errors')
per this answer: Selenium Issue : ERROR: Couldn't read tbsCertificate (originally intended for selenium).
I can post code as necessary, but will be unable to provide access to the full repo as it is private.
Main Process:
import {createMainWindow} from "./windows/main.window";
import {app, BrowserWindow, protocol} from "electron";
import {createAuthWindow} from "./windows/auth.window";
import * as authService from './services/auth.service'
import {hasConfig} from "./services/config.service";
import {registerConfigAPIHandlers} from "./apis/config/config-api.main";
import {registerAuthAPIHandlers} from "./apis/auth/auth-api.main";
if (require('electron-squirrel-startup')) {
app.quit();
}
export async function showWindow() {
if(hasConfig()) {
try {
await authService.refreshTokens();
createMainWindow();
} catch (err) {
createAuthWindow();
}
} else {
createMainWindow();
}
}
app.on('ready', () => {
registerAuthAPIHandlers();
registerConfigAPIHandlers();
showWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
showWindow()
}
});
protocol.registerSchemesAsPrivileged([
{ scheme: 'http', privileges: { standard: true, bypassCSP: true, corsEnabled: true} },
{ scheme: 'https', privileges: { standard: true, bypassCSP: true, corsEnabled: true} },
{ scheme: 'mailto', privileges: { standard: true } },
]);
Preload Process:
import {exposeAuthAPI} from "./apis/auth/auth-api.renderer";
import {exposeConfigAPI} from "./apis/config/config-api.renderer";
// Register the API with the contextBridge
process.once("loaded", () => {
exposeAuthAPI();
exposeConfigAPI();
});
main.window.ts
import {BrowserWindow} from "electron";
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
let mainWindow: BrowserWindow;
export const createMainWindow = (): void => {
destroyMainWindow();
const width = 1000;
const height = 700;
mainWindow = new BrowserWindow({
height,
width,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
},
});
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
(details, callback) => {
callback({ requestHeaders: { Origin: '*', ...details.requestHeaders } });
},
);
mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
'Access-Control-Allow-Origin': ['*'],
'Access-Control-Allow-Headers': ["Authorization", "realm"],
...details.responseHeaders,
},
});
});
if (process.env.NODE_ENV === "development") {
mainWindow.webContents.openDevTools();
}
};
export const destroyMainWindow = (): void => {
if(mainWindow != null) {
mainWindow.close();
}
mainWindow = null;
}
auth.window.ts
import {BrowserWindow} from "electron";
import * as authService from '../services/auth.service'
import {createMainWindow, destroyMainWindow} from "./main.window";
import {getConfig} from "../services/config.service";
let authWindow: BrowserWindow
export function createAuthWindow() {
destroyAuthWin();
authWindow = new BrowserWindow({
width: 1000,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
});
authWindow.loadURL(authService.getAuthenticationURL());
const {session: {webRequest}} = authWindow.webContents;
const filter = {
urls: [
`${getConfig().redirectUri}*`
]
};
webRequest.onBeforeRequest(filter, async ({url}) => {
await authService.loadTokens(url);
createMainWindow();
return destroyAuthWin();
});
authWindow.on("close", () => {
authWindow = null
})
}
export function destroyAuthWin() {
if (!authWindow) return;
authWindow.close();
authWindow = null;
}
export async function logoutAndLaunchAuthWindow() {
await authService.logout();
destroyMainWindow();
createAuthWindow();
}
auth.service.ts
import jwtDecode from "jwt-decode";
import axios from "axios"
import url from 'url'
import keytar from 'keytar'
import os from 'os'
import {getConfig} from "./config.service";
const keytarService = 'electron-openid-oauth';
const keytarAccount = os.userInfo().username;
let accessToken: string = null;
let identity: string = null;
let refreshToken: string = null;
export function getAccessToken() {
return accessToken;
}
export function getIdentity() {
return identity;
}
export function getBaseUrl() {
const {authHost, tenant} = getConfig();
return `${authHost}/realms/${tenant}/protocol/openid-connect`
}
export function getAuthenticationURL() {
const {redirectUri, authClient} = getConfig();
return (
`${getBaseUrl()}/auth?client_id=${authClient}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=openid`
);
}
export async function refreshTokens() {
const refreshToken = await keytar.getPassword(keytarService, keytarAccount);
if (refreshToken) {
const refreshOptions = {
method: 'POST',
url: `${getBaseUrl()}/token`,
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: {
grant_type: 'refresh_token',
client_id: getConfig().authClient,
refresh_token: refreshToken,
}
};
try {
const response = await axios(refreshOptions);
accessToken = response.data.access_token;
console.log("before decode")
identity = jwtDecode(response.data.id_token);
console.log("after decode")
} catch (error) {
await logout();
throw error;
}
} else {
throw new Error("No available refresh token.");
}
}
export async function loadTokens(callbackURL: string) {
const urlParts = url.parse(callbackURL, true);
const query = urlParts.query;
const {redirectUri, authClient} = getConfig();
const exchangeOptions = {
'grant_type': 'authorization_code',
'client_id': authClient,
'code': query.code,
'redirect_uri': redirectUri,
};
const options = {
method: 'POST',
url: `${getBaseUrl()}/token`,
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: exchangeOptions,
};
try {
const response = await axios(options);
accessToken = response.data.access_token;
console.log("beforeDecode")
identity = jwtDecode(response.data.id_token);
console.log("afterDecode")
refreshToken = response.data.refresh_token;
console.log(accessToken)
if (refreshToken) {
console.log("before keytar")
await keytar.setPassword(keytarService, keytarAccount, refreshToken);
console.log("after keytar")
}
} catch (error) {
console.log("before logout")
await logout();
console.log("inside catch");
console.error(error)
throw error;
}
}
export async function logout() {
const {authClient} = getConfig();
console.log("logout")
if (refreshToken) {
const refreshOptions = {
method: 'POST',
url: getLogOutUrl(),
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: {
grant_type: 'refresh_token',
client_id: authClient,
refresh_token: refreshToken,
}
};
try {
await axios(refreshOptions);
} catch (error) {
console.error(error)
}
}
await keytar.deletePassword(keytarService, keytarAccount);
accessToken = null;
identity = null;
refreshToken = null;
}
export function getLogOutUrl() {
return `${getBaseUrl()}/logout`;
}
Any help would be massively appreciated!