K
Kinde10mo ago
NanaGaisie

Integrating Convex with Kinde

Hey, I am trying to implement custom authentication with convex. I have done as their documentation says on implements custom authentication. That being said using the useKindeBrowserClient hook to access the isAuthenticated auth data inside the ConvexProviderWithKinde when signed in returns false. I tried logging to the console the auth data in a different component and got the expected result. The attached files show how I am implementing the custom auth. I would appreciate any explanation of why this happens and possible ways to resolve this issue.
5 Replies
onderay
onderay10mo ago
To address the issue you're encountering with the useKindeBrowserClient hook returning false for isAuthenticated within the ConvexProviderWithKinde but working as expected in a different component, let's consider a few potential causes and solutions: Timing and React's Rendering Cycle: The useKindeBrowserClient hook might be called at a point in the component's lifecycle where the authentication status has not been fully resolved yet. This could be particularly true if there's a delay in fetching or verifying the authentication status from Kinde's servers. Solution: Ensure that any component relying on the isAuthenticated status from useKindeBrowserClient properly handles loading states. This might involve conditional rendering based on the isLoading state or implementing a retry mechanism for checking the isAuthenticated status after a brief delay. Scope and Propagation of Context: If ConvexProviderWithKinde is a context provider that wraps around the useKindeBrowserClient hook, there might be an issue with how the authentication context is being propagated down to child components. Solution: Verify that the ConvexProviderWithKinde component correctly integrates with Kinde's authentication context and that there are no issues with context shadowing or improper context usage. It might be helpful to use React Developer Tools to inspect the component hierarchy and context values. Custom Implementation Specifics: Since you're implementing custom authentication with Convex and integrating it with Kinde, there might be specific nuances or requirements in the integration that are not fully addressed. Solution: Review the implementation details of both the Convex and Kinde integration, focusing on how authentication tokens are managed, refreshed, and passed between the two systems. Ensure that the getToken method from your UseKindeAuth type is correctly implemented and that it properly interacts with Kinde's authentication flow. Debugging and Further Investigation: Since you mentioned that logging the auth data in a different component yields the expected result, it suggests that the issue might be localized to the specific context or component structure you're using with Convex. Solution: Implement additional logging at various points in the authentication flow to pinpoint where the discrepancy occurs. This might involve logging the output of useKindeBrowserClient at different component levels or within different hooks to identify where the isAuthenticated value diverges from expectations. Without access to the full codebase or specific implementation details, these suggestions are based on common patterns and potential issues in React authentication flows. If your issue persists, the team can take a look, but we don't have any previous experience with Convex.
/dev/null
/dev/null10mo ago
Hey @NanaGaisie have you figured out a possible solution ?
NanaGaisie
NanaGaisieOP10mo ago
@/dev/null access the token with kindeBrowserClient in the useAuth. Also wrap all components that needs authentication with convex's Authenticated component. I suggest grouping all components that need authentication and having a common layout file which handles loading, and authentication states using convex's provided components.
/dev/null
/dev/null10mo ago
I have managed to re-use your code:
"use client";
import useStoreUserEffect from "@/lib/useStoreUserEffect";
import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react";
import { useCallback, useMemo } from "react";

type UseKindeAuth = () => {
isLoading: boolean | null;
isAuthenticated: boolean | null;
getToken: () => string | null;
};

function useUseAuthFromKinde(useAuth: UseKindeAuth) {
return useMemo(
() =>
function useAuthFromKinde() {
const { isLoading, isAuthenticated, getToken } = useAuth();
const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
try {
return await getToken();
} catch (error) {
return null;
}
},
[getToken]
);
return useMemo(
() => ({
isLoading: isLoading ?? false,
isAuthenticated: isAuthenticated ?? false,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken]
);
},
[useAuth]
);
}

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

const useCurrentUser = () => {
const { isLoading, isAuthenticated, getToken } = useKindeBrowserClient();

console.log("isAuthenticated", isAuthenticated);

return { isLoading, isAuthenticated, getToken };
};

export function ConvexClientProvider({
children,
}: {
children: React.ReactNode;
}) {
{
const useAuthFromKinde = useUseAuthFromKinde(useCurrentUser);

// useStoreUserEffect();
return (
<ConvexProviderWithAuth client={convex} useAuth={useAuthFromKinde}>
{children}
</ConvexProviderWithAuth>
);
}
}
"use client";
import useStoreUserEffect from "@/lib/useStoreUserEffect";
import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react";
import { useCallback, useMemo } from "react";

type UseKindeAuth = () => {
isLoading: boolean | null;
isAuthenticated: boolean | null;
getToken: () => string | null;
};

function useUseAuthFromKinde(useAuth: UseKindeAuth) {
return useMemo(
() =>
function useAuthFromKinde() {
const { isLoading, isAuthenticated, getToken } = useAuth();
const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
try {
return await getToken();
} catch (error) {
return null;
}
},
[getToken]
);
return useMemo(
() => ({
isLoading: isLoading ?? false,
isAuthenticated: isAuthenticated ?? false,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken]
);
},
[useAuth]
);
}

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

const useCurrentUser = () => {
const { isLoading, isAuthenticated, getToken } = useKindeBrowserClient();

console.log("isAuthenticated", isAuthenticated);

return { isLoading, isAuthenticated, getToken };
};

export function ConvexClientProvider({
children,
}: {
children: React.ReactNode;
}) {
{
const useAuthFromKinde = useUseAuthFromKinde(useCurrentUser);

// useStoreUserEffect();
return (
<ConvexProviderWithAuth client={convex} useAuth={useAuthFromKinde}>
{children}
</ConvexProviderWithAuth>
);
}
}
Having everything needed for the authentication on a single file. Then using like this on my layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ConvexClientProvider>{children}</ConvexClientProvider>
</body>
</html>
);
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ConvexClientProvider>{children}</ConvexClientProvider>
</body>
</html>
);
}
I tried the two components from kinde, the register seems to be working fine, but the <LoginLink> is not working. I tried to then login through the register but the state is just false when logging the authentication bool state you've inserted. page.tsx ⬇️
mport { LoginLink, RegisterLink } from "@kinde-oss/kinde-auth-nextjs";

export default function Home() {
return (
<main className="">
<LoginLink>Login</LoginLink>
<RegisterLink>Register</RegisterLink>
</main>
);
}
mport { LoginLink, RegisterLink } from "@kinde-oss/kinde-auth-nextjs";

export default function Home() {
return (
<main className="">
<LoginLink>Login</LoginLink>
<RegisterLink>Register</RegisterLink>
</main>
);
}
NanaGaisie
NanaGaisieOP9mo ago
import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
import { useCallback, useMemo } from "react";

interface UseAuthReturn {
isLoading: boolean;
isAuthenticated: boolean;
fetchAccessToken: (args: {
forceRefreshToken: boolean;
}) => Promise<string | null>;
}

export function useAuthFromKinde(): UseAuthReturn {
const { isLoading, isAuthenticated, getAccessTokenRaw, accessToken } =
useKindeBrowserClient();
const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
if (forceRefreshToken) {
try {
const response = await getAccessTokenRaw();
// Returns the token as string
return response;
} catch (error) {
return null;
}
}
// Add this line to ensure the function always returns a string or null
return null;
},
[accessToken],
);

return useMemo(
() => ({
isLoading: isLoading ?? false,
isAuthenticated: isAuthenticated ?? false,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken],
);
}
import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
import { useCallback, useMemo } from "react";

interface UseAuthReturn {
isLoading: boolean;
isAuthenticated: boolean;
fetchAccessToken: (args: {
forceRefreshToken: boolean;
}) => Promise<string | null>;
}

export function useAuthFromKinde(): UseAuthReturn {
const { isLoading, isAuthenticated, getAccessTokenRaw, accessToken } =
useKindeBrowserClient();
const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
if (forceRefreshToken) {
try {
const response = await getAccessTokenRaw();
// Returns the token as string
return response;
} catch (error) {
return null;
}
}
// Add this line to ensure the function always returns a string or null
return null;
},
[accessToken],
);

return useMemo(
() => ({
isLoading: isLoading ?? false,
isAuthenticated: isAuthenticated ?? false,
fetchAccessToken,
}),
[isLoading, isAuthenticated, fetchAccessToken],
);
}
This is my useAuth function. I tried using getToken to access the token. This does not work as the values returned does not have the AUD. Using the getAccesToken returns the AUD. Also the dependency array of the use Callback should not be set to the function to avoid re-rendering of the component. Use the accessToken instead from the useKindeBrowserClient. Also wrap all children that require authentication in the Authenticated provider from convex/react.
Want results from more Discord servers?
Add your server