Matt Luo
Matt Luo11mo ago

Convex and Clerk integration docs

Hi, I have a new Convex project. What guide/docs/demos would be the best starting place at this point to integrate Convex and Clerk? At this point, would you still recommend to render everything on the client side to start out?
I hit a point confusing experience because it seems I need to render my Next.js page on the client side and call Clerk's useAuth(). But Clerk's useAuth() function's documentation is sparse and has a async/await in its example. https://clerk.com/docs/references/react/use-auth
24 Replies
Michal Srb
Michal Srb11mo ago
Convex Clerk | Convex Developer Hub
Clerk is an authentication platform providing login via
Templates
The backend application platform with everything you need to build your product.
Matt Luo
Matt LuoOP11mo ago
Thanks @Michal Srb - Looks like both of those links are for Vite. I'm surprised Vite is the doc's default choice for https://docs.convex.dev/auth/clerk as opposed to Next.js app router. So now I am trying to find examples for next.js app router. By the way, nice webinar at the Miami React conference!
Matt Luo
Matt LuoOP11mo ago
Thanks https://docs.convex.dev/client/react/nextjs/#adding-authentication is for Auth0, but I will see what I can do in inferring what I need to do to accommodate Clerk. I imagine that Next.js and Clerk is the most popular combination for Convex users, so I'm surprised that there is not a more straightforward walkthrough for this stack @Michal Srb FYI - There's a broken link in the docs https://docs.convex.dev/client/react/nextjs/: display text: Convex Next.js demo link: https://docs.convex.dev/client/react/nextjs/
Matt Luo
Matt LuoOP11mo ago
@Michal Srb - WHen I go here: https://github.com/get-convex/convex-demos/tree/main/nextjs-app-router/app I doesn't seem like this app is following the documentation for step 8 of https://docs.convex.dev/auth/clerk: 8. Configure ConvexProviderWithClerk Now replace your ConvexProvider with ClerkProvider wrapping ConvexProviderWithClerk. Do you know of a code example of documentation/guide on Next.js, Clerk, and Convex?
GitHub
convex-demos/nextjs-app-router/app at main · get-convex/convex-demos
Demo apps built on Convex. Contribute to get-convex/convex-demos development by creating an account on GitHub.
Michal Srb
Michal Srb11mo ago
GitHub
convex-demos/nextjs-pages-router/pages/_app.tsx at main · get-conve...
Demo apps built on Convex. Contribute to get-convex/convex-demos development by creating an account on GitHub.
Michal Srb
Michal Srb11mo ago
This is a template using app router, next.js and clerk: https://www.convex.dev/templates/nextjs-app-router Feel free to search the templates page
Templates
The backend application platform with everything you need to build your product.
Matt Luo
Matt LuoOP11mo ago
@Michal Srb - thanks for pointing to that templates page. I think this will be helpful. Regarding https://github.com/get-convex/convex-nextjs-app-router-demo/blob/main/app/ConvexClientProvider.tsx: I noticed there is an import statement: import { ConvexProviderWithClerk } from "convex/react-clerk"; This didn't work for me because I would get this error on the publishableKey: Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.ts(2322) controlComponents-DPxaqPME.d.mts(19, 5): The expected type comes from property 'publishableKey' which is declared here on type 'IntrinsicAttributes & ClerkProviderProps' ⚠ Error (TS2322) | Type is not assignable to type .
Type is not assignable to type . Codeium: Explain Problem (property) publishableKey: string To resolve that error, I replaced the import statement with: import { ClerkProvider, useAuth } from '@clerk/nextjs'; Is it correct to import from @clerk/nextjs considering that my app is a next.js app?
ballingt
ballingt11mo ago
Are you missing the ! which tells TypeScript to assume the environment variable will not be undefined?
<ClerkProvider
publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}
>
<ClerkProvider
publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}
>
Matt Luo
Matt LuoOP11mo ago
@ballingt - I was, thanks. That ! resolved the issue. @ballingt - Conceptual question: for an end user that has signed in via Clerk, how can I get that user's Clerk userId? Or is it not even the correct approach to get the Clerk user ID, and my app's logic should instead be based on UserIdentity.tokenIdentifier?
I am migrating functionality from my Next.js app router / Neon app. I was using the Clerk auth() function on the server side to accomplish this. Looking at the template, https://www.convex.dev/templates/nextjs-app-router It seems all the hooks are happening on the client side, so I can’t use Clerk’s auth() function. And when I look at the docs for useConvexAuth(), https://docs.convex.dev/api/modules/react#useconvexauth, it is not returning the userId like the respective useAuth() Clerk hook does. https://clerk.com/docs/references/react/use-auth Previously, in my app using Neon, I was calling Clerk's auth() function and storing the Clerk User ID directly in the Postgres database. This app never went live, so I can make changes easily. By my read of https://docs.convex.dev/auth/database-auth, it seems that I am not actually supposed to ever get the Clerk User ID. I should instead use the getUserIdentity() function to ultimately get a UserIdentity.tokenIdentifier https://docs.convex.dev/api/interfaces/server.UserIdentity#tokenidentifier
ballingt
ballingt11mo ago
Making your app logic based on the user identity token identifier is a best practice if you want to be able to use a different auth provider in the future or have multiple auth methods for an app, but you can use the Clerk user ID. The recommended path The deepest integration here is using webhooks to receive events from Clerk when user details change, see https://www.convex.dev/templates/clerk — this way you have a Convex table with all of your Clerk users in it.
Templates
The backend application platform with everything you need to build your product.
ballingt
ballingt11mo ago
If you wire up logic that talks to Convex to run server-side then you can do what you're describing, but for live-updating queries on the client Convex authentication relies on the Clerk JWT. This gives you Clerk info server-side, which you can return in a query if you need it on the client. https://docs.convex.dev/auth/functions-auth#user-identity-fields But you should not trust a Clerk user id send in from the client as proof that that is the current user, as anybody could send in anything from the client.
Matt Luo
Matt LuoOP11mo ago
Thanks @ballingt , this is really helpful. I think I know what to do now: My next.js app should render on the client side. Then, get the userIdentity tokenIdentifier and store that (as opposed to the Clerk userId) in the database. In future scenarios where I want to render a Next.js page on the server side, I'll still be able to get the userIdentity tokenIdentifier somehow, probably by getUserIdentity(). @ballingt - And on the client side of a Next.js app, specifically what function do I use to ultimately get the userIdentity.tokenIdentifier? It does not seem to be useConvexAuth. This is where I am stuck
ballingt
ballingt11mo ago
There's no purely client-side way to get this, you need to get it from Convex. I'd write a Convex query called "getCurrentUser()" that returns whatever portion of ctx.getUserIdentity() that you want to expose to the client.
Matt Luo
Matt LuoOP11mo ago
@ballingt Thanks, I see the getCurrentUser() function you created in https://github.com/thomasballinger/convex-clerk-users-table/blob/a78a9083cede8b834a80e1c6c2d189ee08b490c9/convex/users.ts#L120 I'm confused about why tokenIdentifier doesn't seem to be in the repo. I was thinking the literal value of userIdentity.tokenIdentifier would be stored in the Convex database. For example, a user_settings table would have, say, userIdentityTokenIdentifier as a column. But I don't see 'tokenIdentifier' in your repo. Given a repo for an app built on Convex, how can I infer what the columns for your users table is? In the users.ts file, these functions reference various columns like identity.subject or clerkUser.id. So perhaps the tokenIdentifier is in there somewhere. In schema.ts, https://github.com/thomasballinger/convex-clerk-users-table/blob/a78a9083cede8b834a80e1c6c2d189ee08b490c9/convex/users.ts there is a users table that has a column, clerkUser, as a UserJSON. So, I think this clerkUser JSON, which is @clerk/backend, is where the tokenIdentifier is being stored. If this is correct, for a use case of storing user settings, should I really be storing a JSON type for a clerkUser column, or was this just a quick way to build for demonstration purposes?
Michal Srb
Michal Srb11mo ago
tokenIdentifier is a constructed from the JWT. The repo that used webhooks assumes you only use Clerk, and so it uses the Clerk user ID instead.
Matt Luo
Matt LuoOP11mo ago
Thanks @Michal Srb, for a new Convex project today, would you recommend to store (and base the app's logic on) the tokenIdentifier or the Clerk userId? Seems like if I want to design my app to make it easy to switch authentication vendors in the future, I should store tokenIdentifier
Michal Srb
Michal Srb11mo ago
If you want to make it easy to switch vendors later, I would base logic on Id<"users">. If you use either tokenIdentifier or clerkUser.id, and then switch to another vendor, it will be harder to migrate. Does that make sense? With the webhooks for example, your "users" table can have name, email etc. fields, and you can use the email to allow users to sign in via a different vendor in the future.
Matt Luo
Matt LuoOP11mo ago
@Michal Srb - To clarify, when you say Id<"users">, are you talking the _id column that Convex generates for every table? If so, would that mean I need to: 1) create my own users table in my Convex app. 2) In that users table, in addition to the _id column, add another column such as userIdentityTokenIdentifier for Convex's userIdentity.tokenIdentifier. That way, the app maintains an association between a Clerk user account and its own users table. 3) Whenever I have application logic, like user settings for example, the userSettings table would have a foreign key to users._id as opposed to users.userIdentityTokenIdentifier. That way, if I want to migrate off Clerk in the future, I am simply maintaining users._id for all application logic, and adding a new column to users table for the user identifier from the new authentication vendor.
Michal Srb
Michal Srb11mo ago
Yup, that's correct!
Matt Luo
Matt LuoOP11mo ago
@Michal Srb - Interesting, Would you say that the main challenge of this approach is that I need to think carefully about keeping users data freshly synced? For example, you mentioned that " your users table can have name, email etc. fields." To phrase my question another way, would I need to build webhooks to maintain user business data like name and email, or should I keep the users table very lightweight and essentially only for the purpose of mapping _id to userIdentityTokenIdentifier? Lower priority question: is there any content (docs, templates, examples) that illustrate this approach? I think the convex-clerk-users-table repo is basing the application on the userJSON from Clerk, not the users._id column. Or at least, do you know if there are Convex customers or internal apps within Convex Inc that are successfully using this approach
Michal Srb
Michal Srb11mo ago
This template is using clerk ID only to update the user from the webhook and to retrieve the current user. Otherwise it uses users._id for foreign keys (like in the messages table).
Matt Luo
Matt LuoOP11mo ago
Thanks @Michal Srb - This took me some digging but I think I potentially found a Convex project that uses the approach of referencing users._id for business logic. The webdevcody/file-drive repo: https://github.com/webdevcody/file-drive/blob/0e31fcd44134d65ab95d4de95f3454f1383871b0/convex/schema.ts As demonstrated in his YouTube channel: Build a File Storage App with Role Based Authorization (Next.js, Shadcn, Typescript) https://youtu.be/27hMNWcsa-Y?t=1466 I think this is what I need. Instead of calling Clerk auth() on the server side of a Next.js app, I need to use the Convex getUserIdentity() function within a Convex query. Then store that tokenIdentifier in a users table. This repo has a files table, which has a userId column, which is a reference to the users primary key, not a reference to a tokenIdentifier. Thank you @Web Dev Cody for this great video/repo.
Web Dev Cody
Web Dev Cody11mo ago
sure thing man

Did you find this page helpful?