David
David•12mo ago

Auth with Clerk and Convex

I'm very new to Convex and am just going through the tutorials and documentation. But from what I've seen so far it really is the missing piece I've been looking for. Really excited to test this fully on some projects. However, it still seems like there's a little bit of friction when setting up auth. From reading some of the support threads it seems that, if you're using Clerk, then you should almost certainly have a users table with a Clerk ID so that when a user logs in you can grab the Convex user via the Clerk ID and then base all Convex queries and mutations on the Convex user ID (hope I got that right). While there is a template, and docs for how to implement this it feels perhaps a little too manual when creating a new app. From a very new users perspective, I think that if Convex were to give a bit of attention to this area and make it super easy to add auth so that you almost didn't even have to think about it that would be amazing and you could effectively just spin up a new app with a db (via Convex) and have auth pretty much just work and you could just start to query users in the Convex table. I think if this was the case it would make Convex even better (it's pretty incredible already!). In summary, at the moment I can create a Next.js app with Convex really easily, so I have a deployment and db support (yay!). But setting up auth still takes a little effort. I'd love to be able to spin up a Next.js app, add Convex, and auth (Clerk) and just have it all work as quickly as possible, so I can focus on building the app. Would love to get feedback from developers who have been using Convex with Clerk for some time to know if this problem effectively disappears once you're super familiar with all the API's etc.
38 Replies
erquhart
erquhart•12mo ago
You're right about needing to exchange the Clerk id for a Convex id. It's not a hassle though, I do it in a single, very small function that's reused in each query/mutation/action. A query in my app will generally start like this:
const someQuery = query({
args: { someArg: v.string() },
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx)
const someQuery = query({
args: { someArg: v.string() },
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx)
I was using Clerk before I migrated to Convex, so authentication was very quick to set up and move on. Authorization was more involved because it's tied to how your app works. There are solutions, articles, and tools for this, and more coming soon. But I can say that, once set up, auth and authz is not a problem I've had to think about since.
David
DavidOP•12mo ago
Thanks for that. For me, auth is the missing piece to get right and then I can fully focus on the app itself. Just need more practice and experimentation I guess.
erquhart
erquhart•12mo ago
I'm assuming you've already seen the Clerk guide in the Convex docs - is there anything in particular that feels unsure going in?
David
DavidOP•12mo ago
I started with the Next.js quick start guide and got Clerk set up but not the bit with adding a users table in Convex and having that updated by Clerk via a webhook. I guess I'm confused a little by what the optimum set up is? Is it having Clerk update the users table via the webhook? Is that the recommended standard approach, because the Convex docs don't mention webhooks in the Clerk auth. https://docs.convex.dev/auth/clerk
Convex Clerk | Convex Developer Hub
Clerk is an authentication platform providing login via
David
DavidOP•12mo ago
Maybe both approaches are perfectly valid but is a source of confusion at the moment.
David
DavidOP•12mo ago
I also saw these articles: https://stack.convex.dev/row-level-security https://stack.convex.dev/custom-functions But is a little over my head right now. 🙂
Row Level Security: Wrappers as "Middleware"
Implementing row-level security on Convex as a library. Wrap access to the database with access checks written in plain old JS / TS.
Customizing serverless functions without middleware
Re-use code and centralize request handler definitions with discoverability and type safety and without the indirection of middleware or nesting of wr...
erquhart
erquhart•12mo ago
I'm not sure if the webhooks approach is in the docs, but yeah it's the recommended best practice and probably should be covered in docs. Clerk supports calling a webhook whenever user data changes. You would set up an http action in Convex to accept the webhook and record any relevant changes.
erquhart
erquhart•12mo ago
HTTP Actions | Convex Developer Hub
HTTP actions allow you to build an HTTP API right in Convex!
David
DavidOP•12mo ago
So, to clarify. The approach you use yourself is to use a Clerk webhook to update the users table in Convex (via http action) whenever a user logs in/out? And then inside the app you grab the Convex users ID as early as possible from the user session and use that only in your app rather than query Clerk? If so, have you found this a robust, stable approach? i.e. no issues with syncronisation?
erquhart
erquhart•12mo ago
The webhook fires when user data changes only, so not that often per user. I haven't actually gone fully live yet, so I can't speak to issues at scale. But I will say to keep in mind that the Clerk id is what you're really relying in, and that doesn't change. I'm currently setup to update the user email if Clerk has something different than Convex when they authenticate, to be doubly sure things are synced. And I'm avoiding keeping any other data with Clerk beyond email - anything else the user enters directly to my app and is stored in the Convex table only.
David
DavidOP•12mo ago
How do you avoid keeping other data in Clerk, doesn't that store name etc. by default too?
erquhart
erquhart•12mo ago
Probably depends on how you're using it - I use email/password only, and I'm not using their components. But even if you use OAuth and Clerk automatically gets the user's name, etc. via the authorizing service, you could very well just use that info initially (or not at all), store it in your user table, and allow your user data to be separate from Clerk's moving forward. If you're using their components, I'm not sure whether they make it more difficult to do this. It does look like you can turn off first name, last name, username, etc in the Clerk dashboard, even if you're using their components.
David
DavidOP•12mo ago
Hmm, see what I mean. There are quite a few considerations when setting up auth. On the surface it seems quite simple but in practice, for new Convex users, is a little challenging. No doubt it will eventually make sense but is a bit of a blocker to get this right first before focusing on app specific features. If Convex could streamline this and make it super easy to implement that would be a huge win for the platform IMO.
erquhart
erquhart•12mo ago
Oh I totally agree! I just think that the complexity you're referring to also exists in any other solution you might use.
David
DavidOP•12mo ago
Exactly, which is why if Convex could make this a smoother process it would be awesome for all concerned.
erquhart
erquhart•12mo ago
For sure. They've actually talked a lot about it - I think their upcoming Convex Components will help too: https://stack.convex.dev/the-software-defined-database#introducing-convex-components
David
DavidOP•12mo ago
Looks pretty sweet, thanks for the link. What does your users table schema look like by the way. In the Convex/Clerk template they just store the whole Clerk object pretty much.
export default defineSchema({
...
users: defineTable({
// this is UserJSON from @clerk/backend
clerkUser: v.any(),
color: v.string(),
}).index("by_clerk_id", ["clerkUser.id"]),
});
export default defineSchema({
...
users: defineTable({
// this is UserJSON from @clerk/backend
clerkUser: v.any(),
color: v.string(),
}).index("by_clerk_id", ["clerkUser.id"]),
});
erquhart
erquhart•12mo ago
export const fields = {
...defaultFields,
clerkId: v.optional(v.string()),
email: v.string(),
lastUsedBudgetId: v.optional(v.id('budgets')),
}
export const fields = {
...defaultFields,
clerkId: v.optional(v.string()),
email: v.string(),
lastUsedBudgetId: v.optional(v.id('budgets')),
}
Just storing the email and some application stuff. Default fields is stuff like updatedAt.
David
DavidOP•12mo ago
Ok, so you just store the Clerk ID and not the whole Clerk object?
erquhart
erquhart•12mo ago
Yep, that's all I do keep the email synced with Clerk as well
David
DavidOP•12mo ago
I just forked the Clerk template example app and got it working. What I like about storing the Clerk object as they do in the template is that if the Clerk info (any field) is changed on Clerk it filters through and updates it on Convex.
erquhart
erquhart•12mo ago
Totally - there's no problem with doing that
David
DavidOP•12mo ago
Phew. 😂 In Clerk when creating the webhook, what events do you subscribe too? I just checked everything but maybe just the three user events for user.created, user.deleted, user.updated?
erquhart
erquhart•12mo ago
I haven't actually set up webhooks for this, I just update when the user logs in currently, but I'll probably implement webhooks soon. I would run mutations on the spot when the user is created, so I'd personally just have webhooks for update and delete. I wouldn't want my user sign up experience to be at the mercy of a webhook lol But you could also just create the Convex user with minimal data and get the rest through the user.created webhook, so there's definitely a case for using it. So much of this is preference, seriously.
David
DavidOP•12mo ago
Yes, mind boggling the set up variations. Probably why it's so confusing initially.
erquhart
erquhart•12mo ago
I personally sleep better when I know I'm doing things The Right Way, so I had a lot of the feels that you do coming in.
David
DavidOP•12mo ago
Glad it's not just me! I think for the sake of simplicity I'll follow along with the Clerk template way of handling auth until I have more confidence to customize things. At least it gives me a basis for moving forward with further exploration.
erquhart
erquhart•12mo ago
You can go really far before you truly need to nail things down. My encouragement would be to give as little time as you can to concerns of scale until you actually need to worry about them, just cut straight to the good stuff (your actual app).
David
DavidOP•12mo ago
I'm hopefully fairly close to doing that, thanks for the insights. Much appreciated. I think the only query I have right now is the webhook domain URL. I can't ind a reference to that anywhere. In the Clerk template readme: https://github.com/thomasballinger/convex-clerk-users-table It says: Set the url to something like this (replace ardent-mammoth-732 with your own) https://ardent-mammoth-732.convex.site/clerk-users-webhook Is the convex.site arbitrary, or is that fixed and needs to be the same for every Convex webhook you might create. This might be in the docs, not dug that deep yet.
GitHub
GitHub - thomasballinger/convex-clerk-users-table
Contribute to thomasballinger/convex-clerk-users-table development by creating an account on GitHub.
erquhart
erquhart•12mo ago
HTTP Actions | Convex Developer Hub
HTTP actions allow you to build an HTTP API right in Convex!
erquhart
erquhart•12mo ago
3rd line:
HTTP actions are exposed at https://<your deployment name>.convex.site (e.g. https://happy-animal-123.convex.site).
Rather than thinking in terms of "Convex webhooks", think in terms of the http.(js|ts) file being an HTTP API into your Convex deployment. You can create whatever endpoints you like there, and access them through http requests however you want - including via webhook.
David
DavidOP•12mo ago
Ah yes, duh. Thanks. Bit too much info to absorb in one session. 😂
erquhart
erquhart•12mo ago
lol np, it's not super obvious if you don't know Happy to help if you have any other thoughts/questions
David
DavidOP•12mo ago
Great! Thanks so much.
hyperzone
hyperzone•12mo ago
can you point me to that part in the template? I assume it means the template README tells you to setup webhooks that do this right?
Michal Srb
Michal Srb•12mo ago
We'll work on simplifying the auth setup soon. There's so much to do! 😇
erquhart
erquhart•12mo ago
@hyperzone I'm actually not seeing it either, they insert just two fields: https://github.com/get-convex/convex-demos/blob/d4b6b97ce839ade881109653a6ffe8ea5ff4d082/users-and-clerk/convex/users.ts#L37-L41 I may be looking at a different template than the one @David used. Ah! Found it. It's linked in a readme, it's an example for users table syncing specifically: https://github.com/thomasballinger/convex-clerk-users-table/ Here's the code in question: https://github.com/thomasballinger/convex-clerk-users-table/blob/a78a9083cede8b834a80e1c6c2d189ee08b490c9/convex/http.ts#L26-L38
hyperzone
hyperzone•12mo ago
thanks!

Did you find this page helpful?