Kinde Auth is a pain to set up with Convex right now, but with the code below you can get setup quick and easily. If you aren't using TanStack router, you can still use this - you'll just need to slightly modify which Kinde and Convex APIs you are using - but the concepts are all the same :)
First, you need to replace your current Convex Provider with the provider below. This will allow you to:
a) authenticate your Convex requests properly with Kinde Auth b) use the useConvexAuth hook to read your authentication state
// ConvexKinde.provider.ts
import { useCallback, useMemo } from "react";
import type { ReactNode } from "react";
import { useKindeAuth } from "@kinde-oss/kinde-auth-react";
import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
// Custom hook that adapts Kinde auth to Convex auth interface
function useKindeConvexAuth() {
const { getToken, isAuthenticated, isLoading } = useKindeAuth();
const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
console.log("π fetchAccessToken called:", { isAuthenticated, isLoading, forceRefreshToken });
if (!isAuthenticated || isLoading) {
console.log("β Not authenticated or still loading, returning null");
return null;
}
try {
const token = await getToken();
console.log("π― Raw token from Kinde:", token ? "β Got token" : "β No token");
if (token) {
// Decode JWT to see its contents (for debugging)
try {
const payload = JSON.parse(atob(token.split('.')[1]));
console.log("π JWT Payload:", payload);
console.log("π JWT iss (issuer):", payload.iss);
console.log("π JWT aud (audience):", payload.aud);
console.log("π JWT exp (expires):", new Date(payload.exp * 1000));
} catch (e) {
console.log("β Failed to decode JWT:", e);
}
}
return token || null;
} catch (error) {
console.error("β Failed to fetch token:", error);
return null;
}
},
[getToken, isAuthenticated, isLoading]
);
return useMemo(
() => ({
isLoading: isLoading ?? false,
isAuthenticated: isAuthenticated ?? false,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken]
);
}
const ConvexAuthProvider = ({ children }: { children: ReactNode }) => {
return (
<ConvexProviderWithAuth client={convex} useAuth={useKindeConvexAuth}>
{children}
</ConvexProviderWithAuth>
);
};
export default ConvexAuthProvider;Your Kinde Auth provider stays mostly the same, however you need to explitly specify your JWT Audience as "convex"
Here is my main.tsx file, however this may be entirely different for you depending on your framework and stack, but the main idea is you set audience="convex" in the KindeProvider props
// main.tsx
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import {
RouterProvider,
createRouter as createTanStackRouter,
} from "@tanstack/react-router";
import { routerWithQueryClient } from "@tanstack/react-router-with-query";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { CatchBoundary } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import "./styles.css";
import reportWebVitals from "./reportWebVitals.ts";
import { QueryClient } from "@tanstack/react-query";
import { KindeProvider } from "@kinde-oss/kinde-auth-react";
import ConvexAuthProvider from "./components/providers/ConvexKinde.provider.tsx";
export function createRouter() {
const CONVEX_URL = (import.meta as any).env.VITE_CONVEX_URL!;
if (!CONVEX_URL) {
console.error("missing envar VITE_CONVEX_URL");
}
const convexQueryClient = new ConvexQueryClient(CONVEX_URL);
const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
});
convexQueryClient.connect(queryClient);
const router = routerWithQueryClient(
createTanStackRouter({
routeTree,
defaultPreload: "intent",
context: { queryClient },
Wrap: ({ children }) => (
<KindeProvider
domain={import.meta.env.VITE_KINDE_DOMAIN as string}
clientId={import.meta.env.VITE_KINDE_CLIENT_ID as string}
redirectUri={import.meta.env.VITE_KINDE_REDIRECT_URI as string}
audience="convex"
>
<CatchBoundary
errorComponent={(e: any) => <div>Error: {JSON.stringify(e)}</div>}
getResetKey={() => "error"}
>
<ConvexAuthProvider>{children}</ConvexAuthProvider>
</CatchBoundary>
</KindeProvider>
),
}),
queryClient
);
return router;
}
// Register the router instance for type safety
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
// Render the app
const rootElement = document.getElementById("app");
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<RouterProvider router={createRouter()} />
</StrictMode>
);
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();You need to setup your convex/auth.config.ts file so that your audience name and issuer URL match up and your Kinde dashboard reflects this.
In the Convex Dashboard, or by running npx convex env set KINDE_ISSUER_URL <your url>, ensure you set your issuer URL to your "https://.kinde.com" URL that Kinde provides to you.
// convex/auth.config.ts
export default {
providers: [
{
domain: process.env.KINDE_ISSUER_URL as string,
applicationID: "convex",
},
],
};Now here's the extremely crucial part, this is the last piece that if you forget, nothing will work and you will wonder why everything is broken :(
Visit your Kinde Auth dashboard, press Settings, then APIs, then Add API. You MUST set your audience correctly to "convex" here otherwise it will not link properly in Convex. If you do not wish to name your audience convex, perhaps api.yourdomain.com instead, reflect this in both your KindeProvider audience prop and also your convex/auth.config.ts applicationID value, but this isn't that important. I'd stick with convex.
Once you have created your API in Kinde Auth, select your frontend and backend applications and authorize them by pressing the 3 dot menu:
Lastly, when you are testing make sure to log out and log back in once if you've just added your convex audience for the first time, from then on it should work as smooth as butter!
Well done! You've successfully configured your TanStack Router app to use Convex and Kinde Auth.
Happy Hacking!
Really clean guide. This setup makes Kinde Auth work smoothly with Convex and TanStack Router.
Thanks for publishing this @SirTenzin