Justin
Justin
CCConvex Community
Created by Justin on 5/28/2025 in #support-community
Supabase Auth and Convex
Hey everyone! I've been trying to setup Supabase auth to be used with Convex (their free tier is more appealing than Clerk) but I've not been about to get it working, It errors out Uncaught Error: Not authenticated This is for a React Native mobile application. I changed the auth.config.ts to
export default {
providers: [
{
type: 'jwt',
domain: process.env.CONVEX_OIDC_ISSUER,
key: process.env.CONVEX_JWT_KEY!,
applicationID: 'supabase-client',
},
],
};
export default {
providers: [
{
type: 'jwt',
domain: process.env.CONVEX_OIDC_ISSUER,
key: process.env.CONVEX_JWT_KEY!,
applicationID: 'supabase-client',
},
],
};
From there I have a hook useSupabaseConvexAuth
import { supabase } from '@/lib/supabase';
import { useEffect, useState } from 'react';

export function useSupabaseConvexAuth() {
const [accessToken, setAccessToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const fetchSession = async () => {
const { data } = await supabase.auth.getSession();
setAccessToken(data?.session?.access_token ?? null);
setIsLoading(false);
};

fetchSession();

const { data: listener } = supabase.auth.onAuthStateChange(
(_event, session) => {
console.log(session?.access_token);
setAccessToken(session?.access_token ?? null);
}
);

return () => {
listener.subscription.unsubscribe();
};
}, []);

return {
isLoading,
isAuthenticated: !!accessToken,
fetchAccessToken: async () => accessToken,
};
}
import { supabase } from '@/lib/supabase';
import { useEffect, useState } from 'react';

export function useSupabaseConvexAuth() {
const [accessToken, setAccessToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const fetchSession = async () => {
const { data } = await supabase.auth.getSession();
setAccessToken(data?.session?.access_token ?? null);
setIsLoading(false);
};

fetchSession();

const { data: listener } = supabase.auth.onAuthStateChange(
(_event, session) => {
console.log(session?.access_token);
setAccessToken(session?.access_token ?? null);
}
);

return () => {
listener.subscription.unsubscribe();
};
}, []);

return {
isLoading,
isAuthenticated: !!accessToken,
fetchAccessToken: async () => accessToken,
};
}
which is then used in my _layout.tsx
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { SplashScreen, Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';

import { useColorScheme } from '@/hooks/useColorScheme';
import { useSupabaseConvexAuth } from '@/hooks/useSupabaseConvexAuth';
import { Providers } from '@/providers';
import { ConvexProviderWithAuth, ConvexReactClient } from 'convex/react';
import { useEffect } from 'react';

SplashScreen.preventAutoHideAsync();

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

export default function RootLayout() {
const auth = useSupabaseConvexAuth();
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
});

useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);

if (!loaded) {
return null;
}

return (
<Providers>
<ConvexProviderWithAuth client={convex} useAuth={() => auth}>
<ThemeProvider
value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<Stack>
<Stack.Screen name='(tabs)' options={{ headerShown: false }} />
<Stack.Screen name='+not-found' />
</Stack>
<StatusBar style='auto' />
</ThemeProvider>
</ConvexProviderWithAuth>
</Providers>
);
}
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { SplashScreen, Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';

import { useColorScheme } from '@/hooks/useColorScheme';
import { useSupabaseConvexAuth } from '@/hooks/useSupabaseConvexAuth';
import { Providers } from '@/providers';
import { ConvexProviderWithAuth, ConvexReactClient } from 'convex/react';
import { useEffect } from 'react';

SplashScreen.preventAutoHideAsync();

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

export default function RootLayout() {
const auth = useSupabaseConvexAuth();
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
});

useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);

if (!loaded) {
return null;
}

return (
<Providers>
<ConvexProviderWithAuth client={convex} useAuth={() => auth}>
<ThemeProvider
value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<Stack>
<Stack.Screen name='(tabs)' options={{ headerShown: false }} />
<Stack.Screen name='+not-found' />
</Stack>
<StatusBar style='auto' />
</ThemeProvider>
</ConvexProviderWithAuth>
</Providers>
);
}
I've set the env variables on the Convex dashboard and have my Supabase public and private keys. Any suggestions or feedback on how I could get this setup? I can sign into the app and log the access_token, but it appears to not be making it to Convex.
31 replies