zor
zor8mo ago

Syncing data with Clerk

Which way is a better approach: Fetching from Convex(Clerk data is synced with Convex). Or fetching from Clerk with useUser(); hook for all user data like phone number(user.primaryPhoneNumber?.phoneNumber), isSignedIn, isLoaded, and user object
25 Replies
Michal Srb
Michal Srb8mo ago
You can only rely on the useUser hook on the client, for the current user. You cannot rely on this information in any of your backend logic (since a malicious client could spoof it). For displaying the current user's information on the frontend, useUser is OK, and might be simpler than roundtripping through Convex.
zor
zorOP8mo ago
Oh that's confusing here I thought I should only fetch and post from Convex DB Single source of truth
Michal Srb
Michal Srb8mo ago
Certainly going through your Convex backend will ensure consistency with other data on the backend.
zor
zorOP8mo ago
I thought posting all huge user object to the Convex everytime with webhook sync And doing all stuff with query would work but I didn't know if it doesn't work like that please feel free to brighten me
Michal Srb
Michal Srb8mo ago
Webhook sync is totally fine.
zor
zorOP8mo ago
If I should use half Clerk and half Convex, in which cases I'll use Convex? Or should I not at all? Then why is the sync for? Will Convex post data to Clerk and Convex is actually syncing the Clerk?
Michal Srb
Michal Srb8mo ago
And it might be simpler to do things one way, as opposed to combining Clerk hooks and Convex data fetching hooks.
zor
zorOP8mo ago
🤔 If that's not bothering you I would love to hear some more explaining If you are not comfortable, we can do it later any day later. I don't like bothering anyone
Michal Srb
Michal Srb8mo ago
Like I wrote above, you cannot rely on useUser in any of your backend logic. You cannot pass the current user's email to the Convex backend and make sure it really is their email. This example is NOT GOOD:
const {email} = useUser();
const doSomething = useMutation(api.foo.bla);
const <button onClick={() => doSomething({email})} />;
const {email} = useUser();
const doSomething = useMutation(api.foo.bla);
const <button onClick={() => doSomething({email})} />;
zor
zorOP8mo ago
Oh very interesting What's the big great approach in that case? I didn't plan such approach actually I didn't think such case. I don't have anything specific in my mind I know hooks are from client and we can not trust anything from client Let's say I'm creating my own user data changer UI aka. normal user settings menu It will show the current email and user can change theirs. Well, in that case what is the scalable good approach the example is about recreating this page Since Convex doesn't offer any out-of-the-box feature - I love it's not offering, so we are clearly free to do anything - I will recreate every Clerk feature and try to be independent from Clerk since once I've been told to it's a good idea(https://discord.com/channels/1019350475847499849/1234191302338412655/1234664350665412638)
zor
zorOP8mo ago
No description
zor
zorOP8mo ago
It even has the data isPrimary of the email
Gorka Cesium
Gorka Cesium8mo ago
once Convex Auth is ready for prime time I'll migrate from Clerk
zor
zorOP8mo ago
Who would say no to a good existing ecosystem oppourtunity Hi, anyone has an idea can reply this ☝️ Well, I guess I will need to store a very detailed user object as representing Clerk's user object(https://clerk.com/docs/references/javascript/user/user) And instead of useUser, henceforth I will need to create my own useUser I guess which fetches from my Convex? Will that be server-side? Is GPT being wrong here:
You're right. Client-side data can be tampered with, and it's essential to ensure that the backend independently verifies the identity and permissions of the user making the request. To address this, you should rely on server-side authentication and data fetching. Here's how you can adjust the architecture to ensure security and reliability:


To replace useUser with a secure server-side approach, we need to fetch user data on the server side and pass it to the client components. Here's how you can translate the code:
You're right. Client-side data can be tampered with, and it's essential to ensure that the backend independently verifies the identity and permissions of the user making the request. To address this, you should rely on server-side authentication and data fetching. Here's how you can adjust the architecture to ensure security and reliability:


To replace useUser with a secure server-side approach, we need to fetch user data on the server side and pass it to the client components. Here's how you can translate the code:
You can take a look on the only last two outputs: https://chatgpt.com/share/606da0a4-650b-4a61-822b-b85f69b05713
Michal Srb
Michal Srb8mo ago
henceforth I will need to create my own useUser I guess which fetches from my Convex?
Yes, this works.
zor
zorOP8mo ago
Then, should I have all the UserJSON in my Convex database like if that's how Clerk do?
export interface UserJSON extends ClerkResourceJSON {
object: typeof ObjectType.User;
username: string | null;
first_name: string | null;
last_name: string | null;
image_url: string;
has_image: boolean;
primary_email_address_id: string | null;
primary_phone_number_id: string | null;
primary_web3_wallet_id: string | null;
password_enabled: boolean;
two_factor_enabled: boolean;
totp_enabled: boolean;
backup_code_enabled: boolean;
email_addresses: EmailAddressJSON[];
phone_numbers: PhoneNumberJSON[];
web3_wallets: Web3WalletJSON[];
organization_memberships: OrganizationMembershipJSON[] | null;
external_accounts: ExternalAccountJSON[];
saml_accounts: SamlAccountJSON[];
password_last_updated_at: number | null;
public_metadata: UserPublicMetadata;
private_metadata: UserPrivateMetadata;
unsafe_metadata: UserUnsafeMetadata;
external_id: string | null;
last_sign_in_at: number | null;
banned: boolean;
locked: boolean;
lockout_expires_in_seconds: number | null;
verification_attempts_remaining: number | null;
created_at: number;
updated_at: number;
last_active_at: number | null;
create_organization_enabled: boolean;
}
export interface UserJSON extends ClerkResourceJSON {
object: typeof ObjectType.User;
username: string | null;
first_name: string | null;
last_name: string | null;
image_url: string;
has_image: boolean;
primary_email_address_id: string | null;
primary_phone_number_id: string | null;
primary_web3_wallet_id: string | null;
password_enabled: boolean;
two_factor_enabled: boolean;
totp_enabled: boolean;
backup_code_enabled: boolean;
email_addresses: EmailAddressJSON[];
phone_numbers: PhoneNumberJSON[];
web3_wallets: Web3WalletJSON[];
organization_memberships: OrganizationMembershipJSON[] | null;
external_accounts: ExternalAccountJSON[];
saml_accounts: SamlAccountJSON[];
password_last_updated_at: number | null;
public_metadata: UserPublicMetadata;
private_metadata: UserPrivateMetadata;
unsafe_metadata: UserUnsafeMetadata;
external_id: string | null;
last_sign_in_at: number | null;
banned: boolean;
locked: boolean;
lockout_expires_in_seconds: number | null;
verification_attempts_remaining: number | null;
created_at: number;
updated_at: number;
last_active_at: number | null;
create_organization_enabled: boolean;
}
export const upsertFromClerk = internalMutation({
args: { data: v.any() as Validator<UserJSON> }, // no runtime validation, trust Clerk
async handler(ctx, { data }) {
const userAttributes = {
firstName: data.first_name,
externalId: data.id,
// Should all UserJSON attributes exist here?
};

const user = await userByExternalId(ctx, data.id);
if (user === null) {
await ctx.db.insert("users", userAttributes);
} else {
await ctx.db.patch(user._id, userAttributes);
}
},
});
export const upsertFromClerk = internalMutation({
args: { data: v.any() as Validator<UserJSON> }, // no runtime validation, trust Clerk
async handler(ctx, { data }) {
const userAttributes = {
firstName: data.first_name,
externalId: data.id,
// Should all UserJSON attributes exist here?
};

const user = await userByExternalId(ctx, data.id);
if (user === null) {
await ctx.db.insert("users", userAttributes);
} else {
await ctx.db.patch(user._id, userAttributes);
}
},
});
Will it interact with Clerk only once and then process everything inside, between Convex and Convex DB? How will the Clerk database be in sync? Or is it unnecessary to Clerk to be out of sync? Will the Clerk's JWT authentication be affected from this approach? Will authentication work fine I am trying to have GPT's help but I think it is not capable of answering such questions. It seems confused
Michal Srb
Michal Srb8mo ago
Hmm, a lot of these questions are really question for you and your app, what your app's need are. Have you gone through the Convex tutorial btw? It might help to solidify what's happening when.
zor
zorOP8mo ago
Yes, I've already set my webhook and Convex - Clerk JWT setup but I am not sure if my question is written on a tutorial
Hmm, a lot of these questions are really question for you and your app, what your app's need are.
I would appreciate any guidance, insights or thoughts Let's say it is B2B dashboard app Or you can let me know any other case and I can reason it Because I am not able to find any other resource related to my situation. If there are, please let me know I'm very willing to take a look
zor
zorOP8mo ago
Convex & Clerk | Convex Developer Hub
Clerk is an authentication platform providing login via
Michal Srb
Michal Srb8mo ago
GitHub
convex-demos/users-and-clerk-webhooks at main · get-convex/convex-d...
Demo apps built on Convex. Contribute to get-convex/convex-demos development by creating an account on GitHub.
zor
zorOP8mo ago
As I said, I have the webhook setup alreadty I think my question differs from that. Actually I think it is about what's the next step from this setup and how to approach further I wonder about it
Michal Srb
Michal Srb8mo ago
What are you trying to achieve in terms of user experience that the demo doesn't include? (it is a pretty simple demo)
zor
zorOP8mo ago
In this demo, Clerk handles all the user management. I'm told to do as minimal as possible in Clerk but mostly Convex. And Convex is expected to be single source of truth e.g. how would a multistep 2FA work in a user sign in. Or how would Convex tell Clerk to revocate the device since I'll be building the device revocation feature from scratch in Convex
You would be building all of the device tracking, revocation, etc yourself. Convex doesn't have any out-of-the-box features for these things, so if you want them soon I'd start with Clerk, and if you later want to build a more custom version of them you can build them from scratch later (or maybe find some library / component that works with Convex) re: performance, your user queries will be alongside your other Convex data, so it'll be faster in that regard. However, there is a bit of a waterfall of requests: Clerk says you're signed in and the client gets the token Convex sends the token up, then calls something like storeUser and a user is created / verified that it exists Your functions that depend on auth run My personl preference is to keep as little in Clerk as possible, so all convex query/mutations have access to the user data. However, if you want to use all the Clerk pre-built UI to manage user info, you need to keep them in sync
I don't want any pre-built Clerk UI but I want Clerk to run authentication and session management securely I think @ian could help me so well in this case since we've already discussed about it earlier. I really have a flood of questions and I am really so sorry to be bothering so I don't want to ask anything or keep Convex team busy but I'm stuck. I think I need to be told to what to do step by step for this specific case. Would you mind it? Please feel free to express yourself and thoughts honestly, I am fine with anything.
ian
ian8mo ago
Let's move this to a #support-community thread so it's searchable for others who want to know, since this indeed is a common flow: use Clerk to sign in but don't show their account UI, and maybe use some headless APIs for other features like session revocation
zor
zorOP8mo ago
Your guidance gives me confidence and a sense of control. I appreciate it. Sure thing. Let me open a new thread in #support-community

Did you find this page helpful?