#how to block account creation for a specific email ?
25 messages · Page 1 of 1 (latest)
Blocking Account Creation for a Specific Email
The right place to block account creation is the createOrUpdateUser callback, not beforeSessionCreation or afterUserCreatedOrUpdated. Here's why:
beforeSessionCreationruns after the user already exists — it blocks sign-in, not account creation.afterUserCreatedOrUpdatedis called after the user is already created/updated, so it's too late to prevent the write.createOrUpdateUseris called before the user is created, giving you full control. Throwing an error here aborts the entire operation. [Controlling user creation]
Example: Block a specific email (or pattern)
// convex/auth.ts
import { convexAuth } from "@convex-dev/auth/server";
import GitHub from "@auth/core/providers/github";
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [GitHub],
callbacks: {
async createOrUpdateUser(ctx, args) {
const email = args.profile.email as string | undefined;
// Block a specific email or use a regex
if (email && /blocked@example\.com/.test(email)) {
throw new Error("This email address is not allowed to register.");
}
if (args.existingUserId) {
return args.existingUserId;
}
// Your normal user creation logic
return ctx.db.insert("users", {
email,
// ...other fields
});
},
},
});
Key points
- Throw an error inside
createOrUpdateUserto abort the mutation transaction and prevent the user from being created. [Controlling user creation] - You can use a regex, a blocklist, or even query your database (e.g., an allowlist table) inside this callback since you have access to
ctx. - If you want to pass structured error info to the frontend, use
ConvexErrorinstead of a plainError. [Application Errors] - Note: when you provide
createOrUpdateUser, the library no longer handles user creation automatically — you must implement all creation/update logic yourself for all providers. [Controlling user creation]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@worthy kettle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@mint arch also I have #sym:afterUserCreatedOrUpdated.
But it seems it wont be called if I provied a createOrUpdateUser * This callback is only called if createOrUpdateUser
* is not specified. If createOrUpdateUser is specified,
* you can perform any additional writes in that callback.
Let me check the knowledge base for you again. One moment! :books:
You're exactly right. The docs confirm this behavior explicitly:
This callback is only called if
createOrUpdateUseris not specified. IfcreateOrUpdateUseris specified, you can perform any additional writes in that callback.
Solution: Move your afterUserCreatedOrUpdated logic into createOrUpdateUser
Since you now need createOrUpdateUser for the email blocking, simply consolidate both pieces of logic into that single callback:
// convex/auth.ts
import { convexAuth } from "@convex-dev/auth/server";
import GitHub from "@auth/core/providers/github";
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [GitHub],
callbacks: {
async createOrUpdateUser(ctx, args) {
const email = args.profile.email as string | undefined;
// 1. Block specific emails
if (email && /blocked@example\.com/.test(email)) {
throw new Error("This email address is not allowed to register.");
}
// 2. Handle existing user (previously in afterUserCreatedOrUpdated)
if (args.existingUserId) {
// Optionally update fields on the existing user
await ctx.db.patch(args.existingUserId, { /* updated fields */ });
return args.existingUserId;
}
// 3. Create new user + any extra writes you had in afterUserCreatedOrUpdated
const userId = await ctx.db.insert("users", {
email,
// ...other fields
});
// Extra writes that were previously in afterUserCreatedOrUpdated
await ctx.db.insert("someTable", { userId, data: "some data" });
return userId;
},
},
});
Summary
| Callback | Called when createOrUpdateUser is set? |
|---|---|
afterUserCreatedOrUpdated |
❌ No |
createOrUpdateUser |
✅ Yes — do everything here |
[Advanced Auth]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@worthy kettle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@mint arch the solution you provided extended the original createOrUpdateUser function or the original is more complex ?
Let me check the knowledge base for you again. One moment! :books:
The knowledge sources only show a simplified example of createOrUpdateUser. The full default implementation inside the library is more complex — it handles things like account linking logic, email/phone verification tracking, and updating existing user fields automatically.
When you provide createOrUpdateUser, the library's default implementation is completely replaced — you own all of it:
"When you provide this callback, the library doesn't create or update users at all. It is up to you to implement all the necessary logic for all providers you use."
What this means in practice
The example I provided was a minimal starting point, not a drop-in replacement for a complex existing implementation. If your current createOrUpdateUser already has custom logic, you should add the email blocking and the afterUserCreatedOrUpdated logic into your existing callback, rather than replacing it with my simplified example.
So the pattern is:
async createOrUpdateUser(ctx, args) {
// ADD: email blocking
if (/* blocked email */) throw new Error("...");
// KEEP: your existing createOrUpdateUser logic here
// ...
// MOVE: logic from your afterUserCreatedOrUpdated here
// ...
}
I'd recommend reviewing your current createOrUpdateUser implementation carefully and only adding the new pieces to it, rather than replacing it wholesale.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@worthy kettle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@mint arch can I still call the original one ? inside my custom build ? like is there a way to import it ?
Let me check the knowledge base for you again. One moment! :books:
The knowledge sources do not contain enough information to confirm whether the library's default createOrUpdateUser implementation is exported or importable from @convex-dev/auth/server.
The knowledge sources only show the public API surface — convexAuth, createAccount, getAuthUserId, etc. — and there is no mention of an exported default user creation function that you could call internally.
What I'd suggest
-
Check the library's source code directly on GitHub (github.com/get-convex/convex-auth) to see if any internal user creation helper is exported.
-
Ask in the Convex Discord (convex.dev/community) — this is a specific enough question that the maintainers would give a definitive answer quickly.
These are the most reliable ways to confirm whether you can compose with the default behavior rather than fully replacing it.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@worthy kettle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@mint arch what's the purpose of authVerifies ?
Let me check the knowledge base for you again. One moment! :books:
The knowledge sources only briefly mention authVerifiers without much detail:
authVerifiers
PKCE verifiers for OAuth.
What this means
PKCE (Proof Key for Code Exchange) is a security extension to the OAuth 2.0 authorization flow. The authVerifiers table stores temporary PKCE verifier values used during OAuth sign-in to prevent authorization code interception attacks.
However, the knowledge sources don't go into further detail about this table's internals. For a deeper explanation, I'd recommend checking the Convex Auth GitHub source or asking on the Convex Discord.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@worthy kettle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@mint arch is there a way to quickly delete user and auth information from a user ?
Let me check the knowledge base for you again. One moment! :books:
The knowledge sources do not contain enough information about a built-in "delete user" helper specifically for Convex Auth (@convex-dev/auth).
However, based on what the knowledge sources do show, here are the relevant approaches:
1. Manual deletion in a mutation
You would need to delete the user and all related auth tables (sessions, accounts, verificationTokens, authVerifiers, etc.) manually:
export const deleteUser = internalMutation({
args: { userId: v.id("users") },
handler: async (ctx, { userId }) => {
// Delete your app user
await ctx.db.delete(userId);
// Delete related auth data
const sessions = await ctx.db
.query("sessions")
.withIndex("userId", (q) => q.eq("userId", userId))
.collect();
for (const session of sessions) {
await ctx.db.delete(session._id);
}
const accounts = await ctx.db
.query("accounts")
.withIndex("userId", (q) => q.eq("userId", userId))
.collect();
for (const account of accounts) {
await ctx.db.delete(account._id);
}
},
});
2. Using triggers for cascade deletes
If you use the convex-helpers triggers library, you can register a trigger on the users table to automatically cascade deletes to related auth tables when a user is deleted. [Cascade Deletes]
3. For large-scale deletions
If a user has many related documents, a single mutation may hit limits. In that case, use a scheduled mutation or action to spread the deletions out. [Discord thread]