Reading through the Zero docs on authentication, I saw that JWT authentication was all that was supported (for now). I'm building a project using Ruby on Rails as an API and Next.js for the UI. While JWT auth is possible with Rails, most auth implementations are session based, included the built in auth solution to the latest Rails 8 release. Thus I wanted to figure out how to best set up Zero while still using the built in session based auth.
In this reply @aa, outlined how the Zero constructor can take a function for the auth
param https://bsky.app/profile/aaronboodman.com/post/3ldyz5bet3s2s
This all sounds right. The
auth
param to Zero's constructor can be a function. When the token expires, Zero invokes the function to get a new token. It's async so you can call an endpoint or whatever.
and later
To clarify further:
- keep the refresh token in an http-only cookie
- in the
auth
js function invoke an endpoint on your server that converts refresh token into session token- return session token to Zero
- later on, session token expires, zero calls auth function again, rinse repeat
Thinking through this, I didn't think we needed a refresh token, since we have the session cookie. What I ended up with was an authenticated API to generate the JWT.
With that said, I wanted to present this approach and see if anyone had any suggestions to improve it.
In Next, this idea should be able to apply to any UI framework though, I have the following setup
- (app)/
- layout.tsx (further referenced as
server-layout
to disambiguate. Thelayout.tsx
file name is needed for Next)
- layout.tsx (further referenced as
- layout.tsx
- components/
- client-layout.tsx
- providers/
- zero-client-provider.tsx
The server-layout
provides an authentication layer, ensuring that the user is authenticated to the backend; if they aren't, the user is redirected to login.
The client-layout
is rendered as a child of server-layout
and is where we render our zero-client-provider
. In this setup, we know that we'll always have an authenticated user when zero-client-provider
is rendered, giving us access to our backend API.
In zero-client-provider
we can set up our auth function
async function getToken(): Promise<string> {
const response = await apiClient.get("/tokens/new");
return response.data.token;
}
which calls the API in tokens_controller
, which is an authenticated route. Note that the the authentication isn't shown in the code below as it is encapsulated in the parent class ApplicationController
. So it is impossible to get a JWT without being authenticated and our UI won't make the request unless the user is authenticated. The JWT is constructed to last 7 days (could be any length) and adds in the user ID to the sub
attribute as instructed in the Zero docs. In a case where the 7 days pass and the JWT expires, Zero will trigger the function again and we'll get a new token.
With all that in place, I got Zero up and running. It seems like a pretty straight forward approach to combining session based API authentication and JWT authentication for Zero.