Tiago Freitas
Tiago Freitas2mo ago

nextauth auth with convex tokens

Hi I'm trying to integrate nextauth with convex using jwt tokens, will not be using convex auth. everything looks correct, but useConvexAuth always returns isAuthenticated=false, even though it is passed as true to ConvexProviderWithAuth
6 Replies
Convex Bot
Convex Bot2mo 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
ballingt2mo ago
@Tiago Freitas by everything looks correct, what do you mean, what looks correct? Can you read the tokens on the convex backend with ctx.auth.getUserIdentity()? What does one of your jwt tokens look like? Here's a debugging guide https://docs.convex.dev/auth/debug
Tiago Freitas
Tiago FreitasOP2mo ago
that helped got it to work, now just trying to solve a nextauth loop issue generating tokens on all pages. just to be sure, your guides are meant to use convex auth as a provider with users saved in convex. if I want to keep using mextauth db for users and pages auth, i should not use the convex adapter and just save the tokens in the session to send to convex
ballingt
ballingt2mo ago
That sounds right, you want to use https://docs.convex.dev/auth/advanced/custom-auth if you're sending oidc identity tokens you get from nextauth
Custom Auth Integration | Convex Developer Hub
Note: This is an advanced feature! We recommend sticking with the
Tiago Freitas
Tiago FreitasOP2mo ago
@ballingt I still have an issue with convex calliing forceRefreshToken and that forces a nextauth session update which causes a loop. I have to debounce a second to stop the loop in my providers.tsx:
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

function useAuth() {
const { data: session, status, update } = useSession();
const typedSession = session as SessionWithConvex | null;
const isLoading = status === "loading";
const isAuthenticated = !!typedSession?.user;

const lastRefreshTime = useRef(0);
const debounceMs = 1000; // 1-second debounce

const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
const now = Date.now();
if (forceRefreshToken && now - lastRefreshTime.current < debounceMs) {
console.log("Debouncing token refresh, returning current token");
return typedSession?.convexToken || null;
}

if (forceRefreshToken) {
lastRefreshTime.current = now;
const newSession = await update();
return (newSession as SessionWithConvex)?.convexToken || null;
}
return typedSession?.convexToken || null;
},
[typedSession, update]
);

return useMemo(
() => ({
isLoading,
isAuthenticated,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken]
);
}

export function Providers({ children }: { children: ReactNode }) {
return (
<SessionProvider>
<ConvexProviderWithAuth client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithAuth>
</SessionProvider>
);
}
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

function useAuth() {
const { data: session, status, update } = useSession();
const typedSession = session as SessionWithConvex | null;
const isLoading = status === "loading";
const isAuthenticated = !!typedSession?.user;

const lastRefreshTime = useRef(0);
const debounceMs = 1000; // 1-second debounce

const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
const now = Date.now();
if (forceRefreshToken && now - lastRefreshTime.current < debounceMs) {
console.log("Debouncing token refresh, returning current token");
return typedSession?.convexToken || null;
}

if (forceRefreshToken) {
lastRefreshTime.current = now;
const newSession = await update();
return (newSession as SessionWithConvex)?.convexToken || null;
}
return typedSession?.convexToken || null;
},
[typedSession, update]
);

return useMemo(
() => ({
isLoading,
isAuthenticated,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken]
);
}

export function Providers({ children }: { children: ReactNode }) {
return (
<SessionProvider>
<ConvexProviderWithAuth client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithAuth>
</SessionProvider>
);
}
if I don't add the debouce to forceRefreshToken I get into a loop
ballingt
ballingt2w ago
@Tiago Freitas hm is one of typedSession or update changing here, causing fetchAccessToken to have a new identity, causing the useMemo'd value to change? When a new useAuth is passed to ConvexProviderWithAuth, it reruns it.

Did you find this page helpful?