TL;DR: We dig into the different types of code signing certificates, coding the updater, configuring your package.json, and how to initiate a build/deployment when using Electron with Electron Forge.

Have you found yourself signing an Electron app lately? The process isn’t very well documented. I ran into this problem on a recent project. It was the first time I had to set up signing for an installable application myself from scratch and it took me a bit to get everything working. Due to the lack of lucid documentation, I’m here to explain the process and make it easier for others.

Procuring a code signing certificate

A code signing certificate can be obtained from a few different places, like SSL certificates, and a list of possible sources is located on Microsoft’s site. When you visit these certificate issuers, you will notice there are two types of certificates available for purchase: Code Signing Certificates and EV Code Signing Certificates.

Code Signing Cert Compare

The major difference is, with a base level Code Signing Certificate, your clients may still get a warning that the application can’t be verified as coming from a trusted source during download.

This Stack Overflow question shows the three possible states and their associated warnings. To sum it up, if you’re representing a business, you likely want to go with an EV Code Signing Certificate. These do, however, require a bit more work to get. The issuer is going to ask for a lot of company information like an HR contact to verify you work with the business, business existence, business physical address, business telephone number, and more. The exact scenario can vary and it may even require a letter from your bank stating there has been a business account open under the business’s name.

Once you’ve purchased your EV Code Signing Certificate, your issuer will mail it to you on a flash drive. We got ours from Sectigo. But surprise! The flash drive does not work if you just plug it in. Sectigo/Comodo provides an application called SafeNet Authentication Client to use the certificate on the drive. Once you’ve installed SafeNet, you’re ready to begin setting up your signing. You’ll probably want to Enable Single Logon to avoid entering your password for every signed file. Just go to: Gear icon -> Client settings -> Advanced tab (on right) -> check Enable single logon -> Save.

Setting up Electron / Electron Forge to sign

You can set up your Electron and Electron Forge application to sign while building for Windows. I was using version 4.0.0 of Electron, version ^5.2.4 of Electron Forge, and version 1.0.0 of Electron Squirrel Setup.

To start, create a .js file to contain all your updater setup code. I called mine updater.js. Using Electron Squirrel Setup will prevent multiple startups during installation.

import isSquirrelStartup from 'electron-squirrel-startup';

export default class Updater {
  constructor() {
  }

  setupAll() {
  	if (isSquirrelStartup) app.quit();
  }
}

Call this new class in your index.js so it is set up on application startup.

import './updates/updater';

const startUp = async () => {
  createWindow();

  const updater = new Updater();
  updater.setupAll();
};

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', startUp);

Now add code to the method to periodically check for updates from GitHub. You can use other places to host updates, but GitHub is supported by default.

import isSquirrelStartup from 'electron-squirrel-startup';
import updateElectron from 'update-electron-app';
import log from 'electron-log';

export default class Updater {
  constructor() {
  }

  setupAll() {
    if (isSquirrelStartup) app.quit();

    // Setup update checks
    updateElectron({
      repo: 'm4tt1mus/appname', // GitHub repo to check
      updateInterval: '10 minutes',
      logger: log,
      notifyUser: true,
    });
  }
}

The code is good to go, so let’s move onto the package.json. In preparation, you’ll need to do two things:

  1. Procure an installing screen gif and a logo.ico.
  2. Setup a github repository to publish to. You can host the code here too if you would like.

To begin configuration set the name, productName, version, and author in your package.json. I used semantic (major.minor.patch) versioning.

{
  ... 
  "name": "App Name",
  "productName": "App Name",
  "version": "1.1.0",
  "author": "Matt Seigel",
  ...
}

Next, we want to set up the configuration for building the windows application. You will need to update the following areas of the snippet below and integrate them with your package.json:

  • config->electronPackagerConfig->Icon – The icon that will show in add/remove programs, on the taskbar, and in shortcuts.
  • config->electronPackagerConfig->appCopyright – The company that owns the copyright for the application.
  • config->electronPackagerConfig->ignore – The files that are present during development but not needed in the packaged version.
  • config->electronWinstallerConfig>name – The application’s name
  • config->electronWinstallerConfig>signWithParams – Directions to find the certificate used for code signing.
    •  For this line, you just need to change the EV Code Signing certificate’s thumbprint. The thumbprint can be found in your certificate snap in MMC. If you don’t have the snap in, here’s how you can get it: How to: View Certificates with the MMC snap-in. The instructions for finding the thumbprint in the snap in are found here: How to: Retrieve the Thumbprint of a Certificate. If you’re using DigiCert, you can also find it in their certificate management software. There are instructions that show you how to get a thumbprint here.
  • config->electronWinstallerConfig>setupIcon – This is the same as
    config->electronPackagerConfig->Icon.
  • config->electronWinstallerConfig>loadingGif – The animated .gif you want to show during installation.
  • config->github_repository->owner – The GitHub account you want to upload your application with.
  • config->github_repository->name – The repository to upload to. This should be created beforehand. We used a separate Git repository for development.
{
  ...
  "scripts": {
    ...
    "package": "electron-forge package",
    "make": "electron-forge make",
    "publish": "electron-forge publish",
    ...
  },
  ...
  "config": {
    "forge": {
      "make_targets": {
        "win32": [
          "squirrel"
        ]
      },
      "electronPackagerConfig": {
        "packageManager": "yarn",
        "icon": "src/img/app-icon.ico",
        "appCopyright": "Company Name Here",
        "ignore": [
          ".+.test.js",
          ".*.env",
          ".eslintrc",
          ".gitignore",
          "README.md",
          "yarn.lock",
          ".jshintrc",
          ".babelrc"
        ]
      },
      "electronWinstallerConfig": {
        "name": "App Name",
        "signWithParams": "/tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 99c6ca99d9bb5222688a6de194ddd85b1205",
        "setupIcon": "src/img/app-icon.ico",
        "loadingGif": "src/img/installation-animation.gif"
      },
      "electronInstallerDebian": {},
      "electronInstallerRedhat": {},
      "github_repository": {
        "owner": "m4tt1mus",
        "name": "App Name"
      },
      "windowsStoreConfig": {
        "packageName": "",
        "name": "App Name"
      }
    },
    "jest": {}
  },
  ...
}

Here is the full, generalized version of our package.json for reference:

{
  "name": "App Name",
  "productName": "App Name",
  "version": "1.1.25",
  "description": "SMART Connector",
  "main": "src/index.js",
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make",
    "publish": "electron-forge publish",
    "lint": "eslint --cache --color --ext .jsx,.js src",
    "test": "jest"
  },
  "keywords": [],
  "author": "Company Name Here",
  "license": "MIT",
  "config": {
    "forge": {
      "make_targets": {
        "win32": [
          "squirrel"
        ]
      },
      "electronPackagerConfig": {
        "packageManager": "yarn",
        "icon": "src/img/app-icon.ico",
        "appCopyright": "Company Name Here",
        "ignore": [
          ".+.test.js",
          ".*.env",
          ".eslintrc",
          ".gitignore",
          "README.md",
          "yarn.lock",
          ".jshintrc",
          ".babelrc"
        ]
      },
      "electronWinstallerConfig": {
        "name": "App Name",
        "signWithParams": "/tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 99c6ca99d9bb5222688a6de194ddd85b1205",
        "setupIcon": "src/img/app-icon.ico",
        "loadingGif": "src/img/installation-animation.gif"
      },
      "electronInstallerDebian": {},
      "electronInstallerRedhat": {},
      "github_repository": {
        "owner": "m4tt1mus",
        "name": "App Name"
      }
    },
    "jest": {}
  },
  "dependencies": {
    "bootstrap": "^4.3.1",
    "classnames": "^2.2.5",
    "core-js": "^3.2.1",
    "dotenv": "^8.1.0",
    "electron-compile": "^6.4.4",
    "electron-devtools-installer": "^2.2.4",
    "electron-is-dev": "^1.1.0",
    "electron-log": "^3.0.8",
    "electron-settings": "^3.2.0",
    "electron-squirrel-startup": "^1.0.0",
    "griddle-react": "1.13.1",
    "jquery": "3.1.0",
    "lodash": "^4.17.11",
    "msnodesqlv8": "0.8.13",
    "mssql": "^5.1.0",
    "needle": "^2.4.0",
    "object-hash": "^2.0.1",
    "prop-types": "^15.6.1",
    "react": "^16.3",
    "react-dom": "^16.3",
    "react-hot-loader": "^3.0.0-beta.6",
    "react-icons": "^3.2.2",
    "react-redux": "^5.1.0",
    "react-select": "^3.0.4",
    "reactstrap": "^8.0.0",
    "semver": "^7.3.2",
    "update-electron-app": "^1.5.0",
    "xmlbuilder": "^13.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.6.3",
    "@babel/plugin-proposal-class-properties": "^7.10.1",
    "@babel/preset-env": "^7.6.3",
    "babel-eslint": "^7",
    "babel-jest": "^24.9.0",
    "babel-plugin-transform-async-to-generator": "^6.24.1",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-es2015-classes": "^6.24.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "devtron": "^1.4.0",
    "electron-forge": "^5.2.4",
    "electron-prebuilt-compile": "4.0.0",
    "eslint": "^3",
    "eslint-config-airbnb": "^15",
    "eslint-config-prettier": "^6.10.0",
    "eslint-plugin-import": "^2",
    "eslint-plugin-jsx-a11y": "^5",
    "eslint-plugin-prettier": "^3.1.2",
    "eslint-plugin-react": "^7",
    "jest": "^24.9.0"
  }
}

Build and deploy with versions

Congratulations! You now should be able to run a build and deploy. You’ll need to update the version number each time you’re going to deploy a signed build. You’ll also need to have the thumb drive plugged in, SafeNet Authentication Client open, and the password for the certificate that was supplied by your issuer available before starting your build.

I created a README, in my project, that lists the steps needed to create a new version

  1. First, you will need to generate an access token and place it in an environment variable GITHUB_TOKEN. Make sure your token has access to the GitHub repository you want to publish to.
  2. If this is not the very first release on GitHub, make sure the version in package.json is newer than the last release.
  3. Finally, run the following command: ‘yarn run publish’ This will build, package, and publish your application, so there is no need to run any other commands. While the application is building the SafeNet Authentication Client will pop up and ask you for the certificate’s password.

Once built and published, it will show up on GitHub in draft mode. Log in to GitHub, then go to the releases of the repository. There will be a release in draft mode corresponding to the build and publish you just completed. Click the draft release and there will be a button to publish it. This will make it public and also allow existing installations to update to that version.

These steps should get you up and running to the point that you can publish your application for use and allow application updates. Hopefully, with all this information in one place, you won’t have to spend time researching each individual piece. If you’re interested in reading about solving some other hiccups with Electron and Electron Forge head over to my other article, Crash Prevention: Successful updates with Electron and Electron Forge.

 

Let's Talk

Have a tech-oriented question? We'd love to talk.