Fork me on GitHub
#hyperfiddle
<
2023-11-10
>
henrik07:11:20

Here’s the latest speed bump in my adventures of wrangling Auth0 under control: I need to set *ws-server-url* dynamically when reconnecting. If the access token has expired before the next time that Electric reconnects, I effectively need to provide a new URL. I’m currently thinking that I need to copy all code relating to reconnects into our repo, or is there another way to do this? Bonus: the Auth0 access token API returns a promise—it’s not synchronous (fixable with m/observe maybe)

xificurC07:11:57

https://github.com/leonoel/missionary/wiki/Task-interop#futures-promises-1 to wrap a promise into a task The rest I'm missing context to answer. Why do you need to provide a new URL, where is the auth0 access token stored, what happens if electric reconnects and the token expired, how/when do you initialize the auth0 access token

👍 2
henrik07:11:35

The token is stored in indexeddb, and Auth0 appears to have some kind of background task to refresh it just before it expires. There’s a function to retrieve it when needed (everything that manipulates indexeddb returns a promise, hence so does the Auth0 function). The access token is initially set by calling a login function which redirects to a login page if required etc. The URL is set to something-something?access-token=…&HYPERFIDDLE_ELECTRIC_CLIENT_VERSION=… in order to deliver the AT synchronously with the request.

henrik07:11:23

If Electric attempts to reconnect with an expired token, authn/authz middleware rejects the request.

henrik07:11:42

Hence it’s possible to get around the problem by reloading the page, since that’ll set a URL with a new access token. The problem appears specifically in the situation where a) AT is expired, and b) Electric needs to reconnect for whatever reason.

henrik07:11:47

My inclination is to fix this the same way we did in our vanilla websockets: retrieve access token (and CSRF token, which also might have changed) -> construct URL -> attempt to connect. I’m open to suggestions, however.

xificurC08:11:06

> Auth0 appears to have some kind of background task to refresh it just before it expires how does it expire then?

xificurC08:11:45

if you see a clear path forward that meets your requirements I suggest you try it out

henrik08:11:23

I’ve bound *ws-server-url* to the URL something-something?access-token=a1&HYPERFIDDLE_ELECTRIC_CLIENT_VERSION= at app boot. In the meantime, the access token changes to b2, let’s say. Just because it’s b2 in indexeddb doesn’t mean that *ws-server-url* is automatically aware of this, unless I do something. Electric will still try to reconnect on the a1 token, unless I can retrieve the new token inside of the reconnection cycle, and construct a new URL.

henrik08:11:01

I wouldn’t say it’s a good path, since it means copying whatever is necessary from the Electric repo over to our codebase, but clear—sure.

xificurC08:11:54

I see. So you're already constructing your own URL and then what, you use binding or set!?

henrik08:11:39

I’ve copied over do-browser for this purpose.

xificurC08:11:20

Can you watch / get notified when the token changes?

henrik08:11:07

I’m not sure, but let’s say I can. We can poll it, if everything else fails.

avocade08:11:52

Crazy idea: fork the auth0 SPA lib and clojurify it so we can have proper atoms containing the tokens (including refresh token), and hook it in to their existing event/refresh-timer system. Then we could watch the atoms. Haven’t looked at the lib for years though. @U06B8J0AJ polling is probably a good fallback anyhow, so maybe start with that? Just to get it working, and then make it great “later”. Sad but pragmatic.

henrik08:11:55

Not sure we have to: I’m pretty sure we can listen to changes in indexeddb. Ultimately, it would be pefectly fine IMO to just run (.getTokenSilently client) on each reconnection attempt (which will return a new or the current token as necessary, as a promise).

xificurC08:11:50

I was thinking of reading the value and set!ting it into *ws-server-url*, so you don't have to fork anything. But I see boot-with-retry closes over the value, so that probably wouldn't work

henrik08:11:36

Right, I think that would be open to some race conditions in some rare circumstances, but would probably work most of the time.

henrik08:11:56

Modulo the closure of course.

xificurC08:11:16

I'd say just fork the file or redefine the function and get it to work first

👍 1
henrik08:11:56

Great, I will do that. I just wanted to check that I wasn’t immediately picking the path with the most friction.

avocade08:11:23

Thanks Peter for your quick input as always 🙏

👍 1
Geoffrey Gaillard09:11:21

Today, a running electric program will always reconnect to the *ws-server-url* it first connected to. Alternatives to you issue are: 1. Manage Auth token lifecycle outside of electric. 2. On token change, call a home-made variant of https://github.com/hyperfiddle/electric-starter-app/blob/main/src/user.cljs that would : ◦ stops the reactor ◦ rebind *ws-server-url* ◦ restart the reactor 3. fork and adapt electric_client.cljs to add an indirection. Like you and Peter already talked about, I think 3. is the right thing to do

henrik09:11:15

Thanks @U2DART3HA. I’ve thought briefly of 1, and I have a feeling I would be patching race conditions for the foreseeable future, not to mention the complexity involved in adapting the stack of ring middleware to somehow jive with a token coming in via a side channel and so on. 2 would, I think, introduce Electric restarts where none are needed. So 3 seems like the way to go for now.

👍 1
telekid15:11:40

This is probably not all that relevant to your situation, but I'm noting anyway since it might spark a new idea. I've written my own code to https://gist.github.com/telekid/3eff1030756f67237ae1932540f44ef5. My app supports the oauth "authorization code" flow which means I end up storing tokens on the server rather than in the browser, which might not be what you want. But it works well and means that all of my "logged out" logic along with the login flow can also be in electric – being logged in or logged out is just another bit of state within my app.

👍 1
☝️ 1
telekid15:11:02

(I'm by no means an outh expert, please lmk if you see problems with this strategy)

henrik15:11:58

Yeah, if you don’t rely on authentication anywhere in the server router middleware, doing it entirely in Electric would be fine.

henrik15:11:20

Unfortunately, by the time we hit Electric it’s “too late” for us.

👍 1
henrik15:11:54

FWIW, this is what I ended up with.

❤️ 1