#Convex and Clerk integration docs

29 messages · Page 1 of 1 (latest)

pliant vector
#

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

#

Convex and Clerk integration docs

lament crane
pliant vector
#

Thanks @lament crane - 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!

lament crane
pliant vector
#

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

#

@lament crane - 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

Demo apps built on Convex. Contribute to get-convex/convex-demos development by creating an account on GitHub.

lament crane
pliant vector
#

@lament crane - 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?

drifting ore
#

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!}
    >
pliant vector
#

@drifting ore - I was, thanks. That ! resolved the issue.

#

@drifting ore - 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

drifting ore
#

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.

#

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.

pliant vector
#

Thanks @drifting ore , 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().

#

@drifting ore - 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

drifting ore
pliant vector
#

@drifting ore 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?

lament crane
#

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.

pliant vector
#

Thanks @lament crane, 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

lament crane
#

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.

pliant vector
#

@lament crane - 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.
lament crane
#

Yup, that's correct!

pliant vector
#

@lament crane - 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

lament crane
#

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).

pliant vector
#

Thanks @lament crane - 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 @young glacier for this great video/repo.