#My queries useQuery are ran before the session is setted up.
19 messages · Page 1 of 1 (latest)
Hi @kind vale. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
Your observation matches a known behavior: Convex queries can run before the Convex client has finished authenticating, even if your auth provider (Clerk, AuthKit, Better Auth, etc.) already considers the user signed in. That’s why you see 401 Unauthorized / “Unauthenticated” on first load or right after sign‑in. [Auth debug; Auth best practices]
There are two main ways to fix this.
1. Use Convex’s auth state (<Authenticated> / useConvexAuth)
Wrap components that run authenticated queries in Convex’s <Authenticated> (and optionally <Unauthenticated> / <AuthLoading>). Convex guarantees that children of <Authenticated> only render after the Convex client has a valid, validated token, so queries like api.queries.listOrganizations can safely require auth. [Clerk + React auth; AuthKit + React auth; Better Auth authorization; Ensuring auth in client components]
Example:
import { Authenticated, Unauthenticated, AuthLoading } from "convex/react";
import { OrganizationsList } from "./OrganizationsList";
export function Page() {
return (
<>
<Authenticated>
<OrganizationsList />
</Authenticated>
<Unauthenticated>
<div>Please sign in</div>
</Unauthenticated>
<AuthLoading>
<div>Loading auth…</div>
</AuthLoading>
</>
);
}
// OrganizationsList.tsx
"use client";
import { api } from "../convex/_generated/api";
import { useQuery } from "convex/react";
export function OrganizationsList() {
const orgs = useQuery(api.queries.listOrganizations, {});
return <div>{JSON.stringify(orgs)}</div>;
}
Because `OrganizationsList` is only mounted inside `<Authenticated>`, `listOrganizations` won’t run until Convex auth is ready.
If you prefer hooks, you can gate rendering with `useConvexAuth`:
```tsx
"use client";
import { api } from "../convex/_generated/api";
import { useConvexAuth, useQuery } from "convex/react";
export function OrganizationsList() {
const { isLoading, isAuthenticated } = useConvexAuth();
const orgs = useQuery(
api.queries.listOrganizations,
isAuthenticated ? {} : "skip"
); // prevents the query from running until authed [[Ensuring auth in client components](<https://stack.convex.dev/authentication-best-practices-convex-clerk-and-nextjs#ensuring-authentication-in-client-components>)]
if (isLoading) return <div>Loading…</div>;
if (!isAuthenticated) return <div>Not allowed</div>;
return <div>{JSON.stringify(orgs)}</div>;
}
2. Explicitly skip queries until auth is ready
Even inside authenticated UI, you can protect specific queries with "skip" or other conditions, like checking the user object, route params, etc. This is exactly how one user fixed an “Unauthenticated at handler” issue with Clerk: they passed "skip" until they had both routeId and user. [Discord: Unauthenticated at handler; Skipping queries]
Example patterned for your case:
const { isLoading, isAuthenticated } = useConvexAuth();
const organizations = useQuery(
api.queries.listOrganizations,
isAuthenticated ? {} : "skip"
);
This stops Convex from sending the query at all until the client is authenticated, so you no longer get a 401 from `listOrganizations`.
---
## If you’re doing server‑side rendering (`fetchQuery` / `preloadQuery`)
On the server you must explicitly pass a token; middleware/proxy (e.g. AuthKit) does not automatically forward it. [[Auth debug](<https://docs.convex.dev/auth/debug>); [AuthKit + Next.js discussion](<https://discord.com/channels/1019350475847499849/1432093342979850281>)]
For example (AuthKit):
```ts
import { fetchQuery } from "convex/nextjs";
import { withAuth } from "@workos-inc/authkit-nextjs";
import { api } from "../convex/_generated/api";
export async function getOrganizations() {
const { accessToken } = await withAuth({ ensureSignedIn: true });
return fetchQuery(api.queries.listOrganizations, {}, { token: accessToken });
}
On the client, you still want <Authenticated> / useConvexAuth so that the re‑subscribed live query after hydration doesn’t race ahead of Convex auth and fail. [AuthKit + Next.js discussion]
Summary
To stop useQuery(api.queries.listOrganizations) from running “too early” and returning 401s:
- Wrap the component in
<Authenticated>/<Unauthenticated>/<AuthLoading>, or - Gate the query with
useConvexAuth()and"skip"untilisAuthenticatedis true.
Both patterns are documented and recommended for avoiding this exact race condition.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@kind vale Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Let me check the knowledge base for you again. One moment! :books:
The 401s mean Convex is still seeing the client as unauthenticated when listOrganizations runs. The docs and threads you shared all point to the same root cause and solutions:
1. Use Convex’s auth state, not your auth provider’s
Auth providers (Clerk, Better Auth, AuthKit, etc.) will say “signed in” before Convex has validated the token over its WebSocket. You must gate your Convex queries on Convex’s auth state (useConvexAuth, <Authenticated>), not your provider’s. [Clerk React guide; AuthKit React guide; Better Auth authorization]
You should not rely on useAuth() / useSession() / getSession() alone.
2. Confirm your component is really under <Authenticated>
Convex guarantees queries can require auth only for components that are children of <Authenticated> from convex/react. [Clerk React guide; AuthKit React guide; Better Auth authorization]
Minimal working pattern:
// App / layout
import { Authenticated, Unauthenticated, AuthLoading } from "convex/react";
export default function App() {
return (
<main>
<Authenticated>
<OrganizationsPage />
</Authenticated>
<Unauthenticated>
<p>Please sign in</p>
</Unauthenticated>
<AuthLoading>
<p>Loading auth…</p>
</AuthLoading>
</main>
);
}
// OrganizationsPage.tsx
"use client";
import { api } from "../convex/_generated/api";
import { useQuery } from "convex/react";
export function OrganizationsPage() {
const orgs = useQuery(api.queries.listOrganizations, {});
if (!orgs) return <p>Loading…</p>;
return <pre>{JSON.stringify(orgs, null, 2)}</pre>;
}
If `OrganizationsPage` is not actually under `<Authenticated>` (e.g. different layout, wrong tree, SSR wrapper, etc.), the query will still fire before auth.
### 3. Or: explicitly skip the query until Convex auth is ready
Even with `<Authenticated>`, you can also gate `useQuery` directly:
```tsx
"use client";
import { api } from "../convex/_generated/api";
import { useConvexAuth, useQuery } from "convex/react";
export function OrganizationsPage() {
const { isLoading, isAuthenticated } = useConvexAuth();
const orgs = useQuery(
api.queries.listOrganizations,
isAuthenticated ? {} : "skip"
); // prevents early 401s [[Ensuring auth in client components](<https://stack.convex.dev/authentication-best-practices-convex-clerk-and-nextjs#ensuring-authentication-in-client-components>)]
if (isLoading) return <p>Loading auth…</p>;
if (!isAuthenticated) return <p>Not allowed</p>;
return <pre>{JSON.stringify(orgs, null, 2)}</pre>;
}
This pattern is the same as in the Convex best‑practices article and Better Auth docs: use "skip" until isAuthenticated is true to avoid race conditions. [Ensuring auth in client components; Why this matters]
You can also wrap this into a reusable hook as shown in the article:
import {
OptionalRestArgsOrSkip,
useConvexAuth,
useQueryWithStatus,
} from "convex/react";
import { FunctionReference } from "convex/server";
export function useAuthenticatedQueryWithStatus<
Query extends FunctionReference<"query">,
(query: Query, args: OptionalRestArgsOrSkip<Query>[0] | "skip") {
const { isAuthenticated } = useConvexAuth();
return useQueryWithStatus(query, isAuthenticated ? args : "skip");
}
[[Authenticated query hooks](<https://stack.convex.dev/authentication-best-practices-convex-clerk-and-nextjs#authenticated-query-hooks-simplifying-authentication-logic>)]
### 4. If you depend on a user row in Convex, wait for it too
If `listOrganizations` assumes the user document already exists, you must also wait until that row is stored, not just until auth is true. The docs show two patterns:
- `useStoreUserEffect` (Clerk) which returns an auth state that’s only `isAuthenticated` after the `users.store` mutation has run. [[Calling store user from React](<https://docs.convex.dev/auth/database-auth#calling-the-store-user-mutation-from-react>)]
- `useCurrentUser` that combines `useConvexAuth()` with a `users.current` query: [[Waiting for current user](<https://docs.convex.dev/auth/database-auth#waiting-for-current-user-to-be-stored>)]
```ts
export function useCurrentUser() {
const { isLoading, isAuthenticated } = useConvexAuth();
const user = useQuery(api.users.current);
return {
isLoading: isLoading || (isAuthenticated && user === null),
isAuthenticated: isAuthenticated && user !== null,
};
}
Use that hook to decide when to render your organizations page.
5. Verify that the 401 is really from Convex auth, not from your handler
As a sanity check, log ctx.auth.getUserIdentity() at the top of listOrganizations while testing: [Debugging auth]
export const listOrganizations = query({
args: {},
handler: async (ctx) => {
console.log("server identity", await ctx.auth.getUserIdentity());
// ...
},
});
If you see "server identity null" for the early calls, your client still sent the query before Convex auth completed, meaning your gating logic isn’t wired correctly yet.
If you can paste the exact code where you:
- wrap your app in the Convex provider,
- define the layout containing
<Authenticated>, - and call
useQuery(api.queries.listOrganizations, …),
I can map these patterns directly onto your structure. Based on the docs and support threads, though, the fix has always been: drive rendering and query execution from useConvexAuth / <Authenticated>, not from your auth provider’s session, and use "skip" where needed to avoid the race.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@kind vale Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@hollow lynx I use better auth:
args: {},
handler: async (ctx) => {
const { auth, headers } = await authComponent.getAuth(createAuth, ctx);
Let me check the knowledge base for you again. One moment! :books:
With Better Auth + Convex, “Better Auth session is ready” and “Convex sees you as authenticated” are two separate steps. Your handler code:
args: {},
handler: async (ctx) => {
const { auth, headers } = await authComponent.getAuth(createAuth, ctx);
// ...
}
only says “build a Better Auth instance for this request,” it does not by itself guarantee that Convex has an authenticated identity yet.
A few key points from the Better Auth + Convex docs:
-
Convex auth state comes from
ctx.auth, not from Better Auth.
authComponent.getAuth(createAuth, ctx)is for calling Better Auth’sauth.api.*methods with the right headers (cookies / tokens) in Convex functions. [Component client; Server methods with auth.api]
Whether the Convex request is authenticated is still determined byctx.auth.getUserIdentity(). -
Better Auth can be “logged in” before Convex is.
The docs explicitly warn about this: Better Auth will reflect an authenticated user before Convex does, because the Convex client must then validate the token. If you call functions that require auth too early, they can 401/throw. [Authorization docs] -
You must gate queries/mutations on Convex auth (
useConvexAuth/<Authenticated>).
Better Auth’suseSession()orgetSession()are not sufficient to decide whether to run Convex queries. Instead:
-
Show UI with Convex’s
<Authenticated>,<Unauthenticated>,<AuthLoading>, or- Use
useConvexAuth()and"skip"for queries untilisAuthenticatedis true. [Authorization docs; Ensuring auth in client components]
Example for your
listOrganizationsquery:"use client"; import { api } from "../convex/_generated/api"; import { useConvexAuth, useQuery } from "convex/react"; export function OrganizationsList() { const { isLoading, isAuthenticated } = useConvexAuth(); const organizations = useQuery( api.queries.listOrganizations, isAuthenticated ? {} : "skip" ); if (isLoading) return <p>Loading auth…</p>; if (!isAuthenticated) return <p>Please sign in</p>; return <pre>{JSON.stringify(organizations, null, 2)}</pre>; } - Use
-
Auth‑requiring Convex functions should check
ctx.auth(or useauthComponent.getAuthUser).
For example:import { query } from "./_generated/server"; import { authComponent } from "./auth"; export const getCurrentUser = query({ args: {}, handler: async (ctx) => { return await authComponent.getAuthUser(ctx); }, }); export const listOrganizations = query({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (identity === null) throw new Error("Not authenticated"); // … now read orgs for this user }, }); -
Debug from the backend side to confirm the race.
Add this at the top oflistOrganizationstemporarily:
console.log("server identity", await ctx.auth.getUserIdentity());
Then call it from your client and check the Convex dashboard logs. If you see server identity null on the failing calls, you are indeed hitting the function before Convex has authenticated the connection. [Debugging auth]
So even though authComponent.getAuth(createAuth, ctx) works, you still need to:
- Use
<Authenticated>oruseConvexAuth()on the client to avoid callinglistOrganizationsuntil Convex auth is ready, and - Check
ctx.auth.getUserIdentity()(orauthComponent.getAuthUser(ctx)) inside the query if it requires authentication.