machi
machi4w ago

Roast of the convex client and authentication.

What I want to achieve without crying out loud: 1. Call an endpoint that checks the cookie and returns the session 2. Use that session cookie when calling fetchAccessToken 3. Authenticate into convex server using the JWT so that i can use ctx.auth. Simple right? It's all that really matters when building auth and connecting to convex server. In the current state STEP 3 is terror. Who's the culprit? Convex Client - a nice block of concrete with cracks. First of all... It doesn't clear the scheduled fetchAccessToken calls when transition to unauthenticated state, but you probably didn't notice that, because who's testing 10s access tokens or waits after signing out. Ok I will opt-out from refreshing and add it myself. Yeah you bet it's baked in. You have no access to the internal state and you cannot observe and react to events like authentication.sent authentication.success and so on. I mean you have onChange callback right? Yes and no. You are signing out clearing the auth, but you won't receive the callback... It just sends the message over websocket and doesn't notify you back when it's done, if it's done. In the current state you cannot treat client as a source of truth as you should be! This is the root cause of component unmounting hacks and layers of setState, useEffect everywhere. Great example of that is FirstEffect and LastEffect in one of the convex providers. You can follow the provider guide and just use tanstack suspense query in the authenticated part, sign out and you will find another one despite all of the efforts. The more you dig in the more problems you find. Integrating custom provider and extending auth should be simple and reliable as the rest of the convex environment but it is a nightmare. I do really love the rest of the product but it is super frustrating when you are unable to work around the problems because of the software limits. Are there any improvement plans?
12 Replies
Convex Bot
Convex Bot4w ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
ballingt
ballingt4w ago
@machi now that you've pointed out these issues, yes (I didn't know about these) 1. let's make clearAuth should cancel scheduled token refreshing, 100% 2. a different api for auth that lets you refresh the auth instead of doing it automatically; that's possibly but ugly today, you can make the scheduled one a no-op and call setAuth(() => yourTokenYouRefreshedSomeOtherWay) - curious what api you want here 3. sounds like clearing auth doesn't call the callback, yuck, that's wrong; wonder what onAuthChange was added for then, we'll fix. Re confirm from the server, yeah this is a limitation, I'm not quite following what you need this part for but it should be there, auth state needs to share more. Some related work described in https://github.com/get-convex/convex-react-query/issues/19, you should be able to e.g. set auth up front and prevent running any queries until it's been confirmed, although whether that coordination should be internal or external to the client I don't know re 'sign out and you will find another one' bit are you describing another issue or this whole mess? re firstEffect/LastEffect, this is complicated and sticking the state machine in React makes this read worse, effect order in components is specced but sure not easy to read, but seems like what's necessary while this logic lives in React A big rework is not planned soon, but for specific use cases that are important (e.g. SSR, suspense) we'll do whatever it takes to make these work. One planned change is the guarantee that auth is received before queries are evaluated; that's mostly an ordering thing on the client
machi
machiOP4w ago
I found out that even with all those preventions to unmount queries somehow I receive the query call after the wrongly scheduled fetchTokenAccess is called. This one is very wierd and I don’t know why it happens I checked and I do not rerender
machi
machiOP4w ago
The component that holds this suspense query
ballingt
ballingt4w ago
We need an example with TanStack + Auth, it doesn't exist yet but it's something we should (make work first, and then) be testing there's an example with Clerk but we're not doing authed SSR with it
machi
machiOP4w ago
It’s all client side
ballingt
ballingt4w ago
TanStack stuff keeps getting delayed waiting for stability but it's time
machi
machiOP4w ago
Ok There is a helper for better use query in helpers any chance that we get same for mutations and actions I think that would reduce the need of tanstack in general Regarding server confirmation it is just a general concept of being able to hook into them as a developer. if you have granular events access I can code around them create my own state and stop bothering you haha Regarding that. What if I don't want to use ConvexWithAuthProvider? I know how the convex auth works and I want to unmount views myself without hacking around with useEffects. Example: 1. Press the sign out button and call clear auth 2. Now I would like to receive notification send_authenticate "none" before sending message in order to set the state to signout-pending 3. I unmount authenticated view when state === signout_pending and render placeholder loading view until I receive some event with authenticated: false. 4. Now I can remove the session and perform my session signout implementation. 5. I have a consistent convex connection and I have consistent session state. In this flow I would not need any of that useEffect stuff. I simply maintain the correct unmounting order myself I think lower level of onChange would be great because most of these kind of problem would be possible to solve and you could work them around yourself Here are some LOGS from convex and the last one is basically an event that could be exposed.
sent message with type Authenticate: {"type":"Authenticate","value":"...50HEbSg"}
received ws message with type Transition
server confirmed new auth token is valid [v7]
sent message with type Authenticate: {"type":"Authenticate","value":"...50HEbSg"}
received ws message with type Transition
server confirmed new auth token is valid [v7]
This could also potentially allow me to work around the tanstack problems where needed based on the convex state. I don't think that re-exposing the ws messages makes sense but important auth state before/after transitions Regarding the tanstack query here is an example reproduction of previously mentioned bug.
import { useSuspenseQuery } from "@tanstack/react-query";

const convexQueryClient = new ConvexQueryClient(convex);

const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
});
convexQueryClient.connect(queryClient);


const useUserQuery = () => {
const { data: user } = useSuspenseQuery({
...convexQuery(api.users.me, {}),
});
}

const Root = () => {
const isAuthenticated = useConvexAuth();

if (!isAuthenticated) {
return "whatever";
}

return <ComponentWithUseSuspenseQuery />
}
import { useSuspenseQuery } from "@tanstack/react-query";

const convexQueryClient = new ConvexQueryClient(convex);

const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
});
convexQueryClient.connect(queryClient);


const useUserQuery = () => {
const { data: user } = useSuspenseQuery({
...convexQuery(api.users.me, {}),
});
}

const Root = () => {
const isAuthenticated = useConvexAuth();

if (!isAuthenticated) {
return "whatever";
}

return <ComponentWithUseSuspenseQuery />
}
⏰ Now when you sign out isAuthenticated is false and this query is not being properly unmounted and you receive the query call on the server. There is no mutation to the user. SUPER WIERD.
ballingt
ballingt4w ago
This on is interesting, we stay subscribed to queries for a while when using the TanStack Query integration. When we setAuth or clearAuth that causes all of these to refresh. That's desirable when it's carrying new information from the JWT, but not when logging out.
adam
adam4w ago
One planned change is the guarantee that auth is received before queries are evaluated; that's mostly an ordering thing on the client
I am very happy to hear that this is planned. At the moment, all my Convex queries have an initial auth check and return null if it's not set (rather than throwing). This is because when I throw it seems the Convex query subscription isn't automatically retried when the auth becomes available. This results in needing to handle null for all queries on the frontend at the moment.
ballingt
ballingt4w ago
The not retrying behavior sounds like a bug, could this be because the component unmounts if an error is thrown? On receiving auth every query that checks auth should rerun a repro would be helpful here if these aren't rerunning but regardless agree that needing to write queries so that they work without auth is frustrating

Did you find this page helpful?