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,
Renderer. If you’re not familiar with it, I recommend you to read the official documentation.
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.
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
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:
As you can see, the only way to make requests to internet is in the
So if I secure my API (which again, using TRPC protocol) using JWT token, I need to have JWT token in the
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
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:
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:
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:
This works really well.
But to make it works, first I have to modify the
electron-trpc package, as well as the
Those modification is required to be able to “automatically” fetch the
JWT Token from
Clerk Server then sends it to
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
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.