Riki
Riki3mo ago

Convex Auth with Expo and Native Apple and Google Sign In

Hello, I am currently following the documentation for using Convex Auth with Expo. However, the documentation provides an example to sign up with the in app web-browser when used with Apple or Google Sign In. Although I get why it's done that way, as Convex Auth is in an early stage and still in beta, but displaying a webview to sign in with Apple/Google in an app is not the perfect experience for users. I would like to use Convex Auth but with expo-apple-authentication and @react-native-google-signin/google-signin that allows to provide native experience sign-in. Question: How should I do to implement it? I guess something like I should code the isLoading, isAuthenticated, and fetchAccessToken within a useAuthXXX hook? But it seems refreshing the access token with Apple is not straight-forward, so not sure if I am not digging into a hole that is going to take me a large amount of time.
9 Replies
Convex Bot
Convex Bot3mo 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
ballingt3mo ago
I haven't done this so I don't know, but please leave a note about it in the convex auth GitHub repo issues so other people who are interested can find it. You might want to try Clerk instead of Convex Auth if there are features you need that aren't there and are difficult to implement. If you get this set up it'd be great to publish a guide for it or at least describe what you did in a Convex Auth GitHub issue.
Sronds
Sronds5w ago
+1 @Tom can you point me in the general right direction to attempt to get this setup? I would like to try and make this work but I'm not even sure where to start to implement this. Switching to Clerk is not a realistic option for my project so i'm looking for a Convex Auth solution here
ballingt
ballingt5w ago
I don't know how to do this, maybe you could scope it out for a day to figure out whether it's less work than switching to Clerk. Curious about what makes Clerk not realistic for this. I'd start with trying to understand what expo-apple-authentication and friends are and how they work. Then I'd look at the Convex Auth codebase, https://github.com/get-convex/convex-auth, and learn about how the initial login interaction works. From what I read of the op, it sounds like kicking to a webview to log into google etc. is annoying and there's some other experience that's better? If you look into it please open an issue on the repo with your research.
Sronds
Sronds3w ago
Mainly pricing. Plus i've already implemented Convex Auth in production with hundreds of users so i don't think i can switch easily even if i wanted to. But mainly not wanting to pay a monthly fee for just storing users in a database. I've started looking into this today, will see if i can find a solution
Sronds
Sronds3w ago
btw as a reference for a potential implementation, this is how Supabase does this https://supabase.com/docs/guides/auth/social-login/auth-apple?queryGroups=platform&platform=react-native
Login with Apple | Supabase Docs
Use Sign in with Apple with Supabase
Sronds
Sronds3w ago
@Tom
const credentials = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
})
await signIn('native-apple', credentials)
const credentials = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
})
await signIn('native-apple', credentials)
ConvexCredentials({
id: 'native-apple',
authorize: async (credentials, ctx) => {
// Extract the identity token from the credentials
const { identityToken, user: clientAppleUserId } = credentials

if (!identityToken || !clientAppleUserId) {
throw new Error('Invalid Apple credentials')
}

const validatedTokenData = await ctx.runAction(internal.node.verifyToken, {
identityToken: identityToken as string,
})

// Verify that the user ID from the token matches the one from the client
if (validatedTokenData.appleUserId !== clientAppleUserId) {
throw new Error('User ID mismatch')
}

try {
// Check if user already exists
const existingAccount = await retrieveAccount(ctx, {
provider: 'native-apple',
account: { id: validatedTokenData.email },
})

if (existingAccount) {
// User exists, return their ID
console.log('User exists, returning their ID')
return { userId: existingAccount.user._id }
}
} catch (error) {
console.log('User does not exist, creating user')
}

const createdAccount = await createAccount(ctx, {
provider: 'native-apple',
account: { id: validatedTokenData.email },
profile: {
email: validatedTokenData.email,
name: 'User',
emailVerificationTime: Date.now(),
},
shouldLinkViaEmail: true,
})

return { userId: createdAccount.user._id }
},
}),
],
ConvexCredentials({
id: 'native-apple',
authorize: async (credentials, ctx) => {
// Extract the identity token from the credentials
const { identityToken, user: clientAppleUserId } = credentials

if (!identityToken || !clientAppleUserId) {
throw new Error('Invalid Apple credentials')
}

const validatedTokenData = await ctx.runAction(internal.node.verifyToken, {
identityToken: identityToken as string,
})

// Verify that the user ID from the token matches the one from the client
if (validatedTokenData.appleUserId !== clientAppleUserId) {
throw new Error('User ID mismatch')
}

try {
// Check if user already exists
const existingAccount = await retrieveAccount(ctx, {
provider: 'native-apple',
account: { id: validatedTokenData.email },
})

if (existingAccount) {
// User exists, return their ID
console.log('User exists, returning their ID')
return { userId: existingAccount.user._id }
}
} catch (error) {
console.log('User does not exist, creating user')
}

const createdAccount = await createAccount(ctx, {
provider: 'native-apple',
account: { id: validatedTokenData.email },
profile: {
email: validatedTokenData.email,
name: 'User',
emailVerificationTime: Date.now(),
},
shouldLinkViaEmail: true,
})

return { userId: createdAccount.user._id }
},
}),
],
managed to get a working version. this is what it looks like
Sronds
Sronds3w ago
Sronds
Sronds3w ago
Instead of this

Did you find this page helpful?