MA

· Muhammad Azamuddin

Securing Electron App with Clerk


Hello, this is a serial blogpost about Electron architecture.

I will write about the electron architecture that I choose, but before that, I’d tell you the gist, since it is required to understand why I implement this authentication mechanism.

First, as we know it, Electron has two processes, Main and Renderer. If you’re not familiar with it, I recommend you to read the official documentation.

Essentially, Main is process that running NodeJS in the OS that installed our electron app. Where as Renderer is a process that runs on browser.

Renderer process responsible mainly to handle user interface.

Main and Renderer can communicate to each other using “Inter-Process Communication” a.k.a “IPC”.

It’s not a simple thing to do, so to make the codebase provide maximum productivity, I decided to use TRPC as a layer on top of native Electron IPC.

To do that I use electron-trpc package.

Okay, from this point, you should have everything you should know to follow this writing.

To give a simple visualization, this is how electron processes communicate:

MAIN PROCESS <--->  IPC (TRPC) <----> RENDERER PROCESS

And the overal electron architecture looks like this:

Electron architecture

As you can see, the only way to make requests to internet is in the Main process.

So if I secure my API (which again, using TRPC protocol) using JWT token, I need to have JWT token in the Main process.

That is not a limitation from Electron, it’s a deliberate decision from me, because I want to handle everything in the Main process, the state, the logic, saving to database, connecting to internet, etc.

That way, I think my app will be easier to reason about, and Main process become single source of truth, where the Renderer process only responsible for displaying data and capture user interaction.

(But that might change if I find it inflexible after sometime using this current architecture decision)

Now back to authentication part, since I’m using Clerk in the Renderer process, I could just get it from the Renderer process, which means I can send token to Main process and let it save it somewhere (in the Machine ideally).

Okay, my first intuition is that, it’s simple, I just need to fetch JWT token from Clerk everytime a new Renderer process is created (When new window is created). This looks like this:

Electron Auth Simple Token architecture

The problem with this is that, JWT token ideally (and by default on Clerk) has lifetime of 60seconds. Which means, If the user is opening the window and using it more that 60seconds (1min), and then perform something that make internet authenticated request to my server, the JWT will be expired already.

I do not want to poll every 60s just to get latest token (especially if not needed), and I also do not want to sacrifice the security by increasing token expiration time.

Now, this is clear that I can’t implement above mechanism.

Ok, if the token will expire in 60s, then I guess I can just call the getToken right before I want to make an authentiated request.

And I do it in the Main process. From the Renderer I just need to send sessionId instead of JWT token, then in Main I use that sessionId to fetch the latest JWT token. Like this:

Electron Auth sessionId

But, I realize that when using this mechanism, it means I have to use @clerk/clerk-sdk-node which means I have to supply SECRET_KEY somewhere in the codebase.

Electron app is not the best place to save such sensitive information, eventhough it’s like NodeJS, but it runs on user OS not on our server where we can just use environment variable to store sensitive data.

So, again, this mechanism is not possible.

After some testing, finally, I come to the mechanism that fits my need perfectly.

It’s almost similar to the first mechanism, but instead of fetching JWT Token once, it will fetch it everytime Renderer process wants to call Trpc Main procedure.

And it should happend automatically, such that there’s no difference between calling public procedure and authenticated procedure.

As a developer, I didn’t have to manually fetch the token the supply it as input / argument to the TRPC procedure.

I want the Trpc Client handle that. Like this:

Electron Auth Final

This works really well.

But to make it works, first I have to modify the ipcLink from electron-trpc package, as well as the createIPCHandler.

Those modification is required to be able to “automatically” fetch the JWT Token from Clerk Server then sends it to Main process.

Which then the Main process will store it, and then the TRPC Internet Client will be able to get latest token right before making request to internet server.

With all those sets, I can now call TRPC procedure just like before (seems no change), but under the hood, the request will know if I’m authenticated or not as a long as the ClerkProvider is set up in the React app.

And that’s it.

That’s how I secure my Electron app with Clerk.

And you might be wondering, what’s the server I’m using? Well any server that Clerk supports can handle that.

In my case, I’m using Cloudflare worker. I will make another post about this later.

COMMENTS