Electron React apps with Implicit Grant
Last century, when the web was young, web apps were the new new thing. Instead of running a desktop application that communicated to a back-end server (client-server architecture), the new idea was to use a generic desktop application (a browser) and the back-end server would be responsible for both the application’s user experience (via HTML) and core logic.
Fast-forward to today and now, in addition to web apps, we have single-page apps, which can implement an application’s user experience and core logic in the browser, leading to faster, more responsive and efficient applications. Google’s Gmail single-page application and the React framework are the poster children for this technology.
But desktop applications have not gone the way of the dodo bird. New desktop applications are still being designed and built. They can offer considerable advantages over other application architectures, especially if the application will interact with desktop files.
There are many frameworks for building desktop applications. If you narrow the field to open-source, cross-platform frameworks that enable one application to run on Windows, MacOS, and Linux, the list is short. The Electron framework is on this list, and this post discusses a code example for creating an Electron project that builds desktop applications, integrated with the DocuSign eSignature API for Windows and MacOS.
The Electron framework
The Electron framework is developed and maintained by GitHub. It enables graphical desktop applications to be built using web technologies. The Electron framework is used by many popular desktop applications including Visual Studio Code, WhatsApp, Twitch, and Slack.
Like any software framework, Electron has its critics. But overall, it provides a productive development environment and production applications, especially for developers familiar with building web applications.
Electron includes all of the hard parts of a cross-platform framework for desktop applications: automatic updates, native menus and notifications, crash reporting, debugging, and installers.
For this example, I chose to use the electron-react-boilerplate project. It demonstrates how to use React with Electron. The GitHub Desktop application also uses Electron. It’s an open-source application, and its source files are helpful for understanding how to build a commercial-quality application using Electron.
OAuth and desktop applications
Our goal is to build an example desktop application that uses the eSignature API. We want the application itself to authenticate the user with DocuSign and make the API calls. The first step is authentication. DocuSign uses OAuth for authentication and supports the Authorization Code, JWT, and Implicit grant flows.
Native applications, including desktop applications, are defined as public clients [RFC 8252 §8.4, 8.5]. This means that desktop applications, because they are distributed and downloaded, are assumed to be unable to protect their secrets. Therefore, DocuSign’s Authorization Code and JWT grant flows can’t be used, since they depend on the application protecting the client secret and RSA private key, respectively.
Implicit Grant is a simple request/response authentication flow. But how should the authentication flow (such as password entry) be shown to the user? One method is for the application to implement a web view or similar as a user-agent for the authentication. But this is not recommended, since it makes some types of malicious applications harder to discover. Instead, all OAuth user interaction should be handled via a standalone web browser, with the URL visible. [RFC 8252 §8.11, 8.12]
And how should the OAuth server’s response data be routed to the application? RFC 8252 §7 discusses several options. A popular option used by this example and many Electron apps is Private-Use URI Scheme Redirection [RFC 8252 §7.1].
Here’s how it works in the example application:
- An URI scheme is the part of a URI before the colon (:). For example, operating systems are configured to send URI requests with the http and https schemes to the system’s default web browser. For the desktop application, a private (unique) scheme is used. To maintain uniqueness, the RFC recommends using a DNS domain name owned by the application, but in reverse order. The default for the code example is com.example.electron. The code example’s complete URI for the OAuth response is com.example.electron:/implicit-result (note the single slash as recommended by RFC 8252 §7.1.)
- When the Electron main process starts, it registers (source) its private-use URI scheme with the operating system via Electron’s app.setAsDefaultProtocolClient method. In addition, the renderer process registers to receive
url-actionevents sent by the main process. Note that the renderer process uses the
window.electronobject for communication with the main process. The
window.electronobject is set in the src/main/preload.js file.
- The user clicks the Authenticate button in the application. Because the OAuth Implicit Grant flow directly involves the user, it is implemented in the renderer process using the src/renderer/docusign/OAuthImplicit.ts file.
- The renderer uses the
window.openmethod to open the initial URL for the Implicit Grant flow. For a DocuSign developer account, that’s https://account-d.docusign.com/oauth/auth?xxx. The query parameters include the
redirect_uri. The application’s main process passes the request to the OS, which then opens the URL in the user’s default browser.
- The user authenticates with DocuSign or, if configured, with their upstream SSO IdP. If an active user session exists in the browser, then silent authentication is used and this step is skipped.
- Next, the DocuSign OAuth Service Provider (account-d.docusign.com) redirects the browser to the
redirect_urithat was requested in step 4. The code example enables two options for the
redirect_uri: either redirect directly to the private-use URI scheme or use an intermediate page. Unfortunately, in my testing, the Edge browser on Windows did not reliably redirect to the registered private-use URI. To solve this problem, the example, by default, redirects to an intermediate page. I’ve provided an example of the intermediate page. It redirects to the registered private-use URI and provides extra instructions when an Edge browser is used. The repository’s ReadMe file includes additional information about installing the intermediate thank you page.
- After confirming with the user, the OS sends the URI to the application registered in step 2. For MacOS, the existing main process receives an open-url event. For Windows, it’s more complicated. It appears that Windows always opens a new instance of the application and may send the incoming URI as an argument to the process. I used the code from GitHub Desktop to handle the incoming URI. However, more recent Electron documentation indicates that the process arguments don’t need to be checked. There’s also the related issue of quitting secondary copies of the application.
- The incoming URL is then passed from the main process to the renderer process via a
url-actionevent. It is then sent to the OAuthImplicit.urlActionListener method. The method checks that the URI is as expected and that the
stateparameter matches what was sent, and then reads the incoming access token and calculates the expiration date and time. The method also looks up the user’s name and account details via the oauth/userinfo API method. It further looks up the default account’s external account ID via the Accounts:get API method. The renderer’s React state is then updated by the App.oAuthResults method, and the application’s display is then updated by React.
Sending an envelope request to DocuSign
Once the application has an access token, it displays a form for sending an envelope via the DocuSign SMS Delivery feature. Since the
sendEnvelope method uses the workstation’s file system to obtain the document, I implemented the method in the main process. See the main/docusign/DocuSign.ts file.
The renderer process first checks that the form was filled in by the user, and that the access token is still valid. It then sends all of the needed parameters, including the access token and other API settings, to the sendEnvelope method in the main process via the src/main/preload.js file. The Electron ipcRenderer.invoke interprocess communication method is used. It returns a promise, and the renderer process uses the await operator to wait for the promise to be fulfilled.
sendEnvelope method in the main process can use the Node file system calls and the node-fetch npm module to call the DocuSign eSignature API.
The electron-react-boilerplate-docusign code example demonstrates how to use the Electron framework to build a desktop application that uses Implicit Grant to authenticate with DocuSign and then call the eSignature API to send an envelope. See the repository’s ReadMe file for installation instructions.