gioru
gioru9mo ago

Need help with handling Convex Auth signIn errors in prod

I'm using Convex Auth and during dev I'm handling signin errors like this:
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!validateForm()) return

setSubmitting(true)
try {
const formDataToSend = new FormData()
formDataToSend.append("email", formData.email)
formDataToSend.append("password", formData.password)
formDataToSend.append("flow", flow)

const { signingIn } = await signIn(provider ?? "password", formDataToSend)
if (!signingIn) {
handleSent?.(formData.email)
}
} catch (error: any) {
console.error({ error })
let title = "An error occurred"
let description = ""

if (error.message.includes('InvalidAccountId')) {
title = flow === "signIn" ? "Account not found" : "Email already registered"
description = flow === "signIn" ? "Maybe you meant to sign up?" : "Maybe you meant to log in?"
} else if (error.message.includes('InvalidSecret')) {
title = "Incorrect password"
description = "Check your password or reset it"
} else if (error.message.includes('TooManyFailedAttempts')) {
title = "Too many attempts"
description = "Try again later or reset your password"
} else if (error.message.includes(`Account ${formData.email} already exists`)) {
title = "Account already registered"
description = "Maybe you meant to log in?"
}

toast({ title, description, variant: "destructive" })
} finally {
setSubmitting(false)
}
}
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!validateForm()) return

setSubmitting(true)
try {
const formDataToSend = new FormData()
formDataToSend.append("email", formData.email)
formDataToSend.append("password", formData.password)
formDataToSend.append("flow", flow)

const { signingIn } = await signIn(provider ?? "password", formDataToSend)
if (!signingIn) {
handleSent?.(formData.email)
}
} catch (error: any) {
console.error({ error })
let title = "An error occurred"
let description = ""

if (error.message.includes('InvalidAccountId')) {
title = flow === "signIn" ? "Account not found" : "Email already registered"
description = flow === "signIn" ? "Maybe you meant to sign up?" : "Maybe you meant to log in?"
} else if (error.message.includes('InvalidSecret')) {
title = "Incorrect password"
description = "Check your password or reset it"
} else if (error.message.includes('TooManyFailedAttempts')) {
title = "Too many attempts"
description = "Try again later or reset your password"
} else if (error.message.includes(`Account ${formData.email} already exists`)) {
title = "Account already registered"
description = "Maybe you meant to log in?"
}

toast({ title, description, variant: "destructive" })
} finally {
setSubmitting(false)
}
}
I noticed this doesn't work in production because the errors lose any kind of descriptions when they arrive to the client. Now, what's the correct way to handle auth errors (like user logins but it is not registered, the password is wrong, etc)? Thank you
43 Replies
ballingt
ballingt9mo ago
As you've seen, error messages are stripped of this kind of debugging info in production. This is for security, to make sure just any error that throws in the backend doesn't reveal information that shouldn't be revealed.
ballingt
ballingt9mo ago
GitHub
convex-auth/src/server/implementation.ts at main · get-convex/conve...
Library for built-in auth. Contribute to get-convex/convex-auth development by creating an account on GitHub.
ballingt
ballingt9mo ago
One approach is to change these all into ConvexErrors, but we need to figure out in which situations that's safe to do.
ballingt
ballingt9mo ago
Application Errors | Convex Developer Hub
If you have expected ways your functions might fail, you can either return
ballingt
ballingt9mo ago
Another would be to catch these errors on the server and then return a member of a type union describing the issue Since this implementation.js file is part of the library this is a library change; this is work that needs to be done still. @gioru is there a format you'd want these in? Is catching errors how you'd like to do it, or are you just doing that because it was the only way?
cameronm
cameronm9mo ago
@ballingt @gioru I ran into a similar issue. Even on the server, the errors returned are difficult to handle in try catch blocks as they are just the standard Error class. Sometimes the message is like a code, for instance 'InvalidSecret', but its inconsistent Would be nice even if these were custom named Error classes to check with instanceof to better handle some of the expected error types like @gioru is checking with string checks for example if (error instanceof ConvexAuthTooManyFailedAttemptsError) A little verbose, but you get what I mean. Or create a custom ConvexAuthError or ConvexServerError class extending Error with some additional details like a code that can be documented somewhere in the docs so we have a reference of expected errors to handle
ballingt
ballingt9mo ago
instanceof doesn't work well with serializing errors to the browser so we tend to encourage typing the data associated with the ConvexError instead for anything that has to go over the wire But for errors that are dealt with within this code, yeah custom ConvexAuthBlahBLahBlah errors would work well This is (probably obviously) part of the codebase that isn't finished yet, we focused on getting the security stuff correct first but now this is high on the list for next things to do. Whatever it is ideally it'll be something that you can exhaustively check with TypeScript, so either a union of Error types or a single error with a field that has these messages on it Feel free to file an issue here if you want to be updated on progress https://github.com/get-convex/convex-auth/issues
cameronm
cameronm9mo ago
@ballingt yes, agree with the instanceof. Should have added I am mainly doing this with an HTTP Api using HTTP Actions and not a Convex Client. So I am handling all errors on the server and just returning the appropriate responses. The client solution is definitely a trickier one with the reactivity and not exposing sensitive information.
gioru
gioruOP9mo ago
Tried the api route way but had no success since useAuthActions can be called only inside react components. Tried the signIn convex server function too but wasn't enough to make the signin flow work. 🥲
http.route({
path: '/sign-in',
method: 'POST',
handler: httpAction(async (ctx, request) => {
const { provider, params } = await request.json();
const { signIn } = useAuthActions()

try {
const signinResult = await signIn(provider, params)

return new Response(JSON.stringify(signinResult), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
}
})
} catch (error: any) {
return new Response(error, { status: 400 })
}
})
})

http.route({
path: '/sign-in',
method: 'OPTIONS',
handler: httpAction(async (ctx, request) => {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
}
})
})
})
http.route({
path: '/sign-in',
method: 'POST',
handler: httpAction(async (ctx, request) => {
const { provider, params } = await request.json();
const { signIn } = useAuthActions()

try {
const signinResult = await signIn(provider, params)

return new Response(JSON.stringify(signinResult), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
}
})
} catch (error: any) {
return new Response(error, { status: 400 })
}
})
})

http.route({
path: '/sign-in',
method: 'OPTIONS',
handler: httpAction(async (ctx, request) => {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
}
})
})
})
smaccoun
smaccoun7mo ago
Has anyone found an example of this where someone cleanly handles auth errors? For example incorrect password. Handling application (expected) errors is quite important in signup
dowski
dowski7mo ago
here's an example of handling invalid password using ConvexError: https://github.com/get-convex/convex-auth/commit/c95f02700207a7679189ad3fbb1765512e085615
GitHub
Handle password validation failure in the custom sign-up example (#...
Handle password validation failure in the custom sign-up example Uses a shared constant value between the backend auth config and the frontend and sends it in a ConvexError when password validat...
dowski
dowski7mo ago
it's not a holistic solution to the error handling problem though
ballingt
ballingt7mo ago
Oh nice! @smaccoun is this the kind of thing you're looking for, incorrect password during signup?
smaccoun
smaccoun7mo ago
Yup this is on the right track! But as mentioned would be good to have a full enum list to match over, but this is a great start and at least ahndles the invalid password case which is what i was testing
ballingt
ballingt7mo ago
@smaccoun I wrote a bit about this in https://github.com/get-convex/convex-auth/issues/124, the enum list of failures makes sense but is a higher level interface than anything Convex Auth provides today. It's a good idea but would be a bigger change, the more likely change in the short term is adding a callback to allow you to build your own enum.
Eva
Eva6mo ago
I'm trying to figure out how to display a simple "email does not exist" message for users who sign in with an invalid ID — I've read through this thread and a few issues on GitHub but it's still unclear. Am I correct in understanding that there's no way to display this message currently without creating a custom provider? I see there's a callback for invalid password handling, but there isn't one for an invalid email, and the errors get stripped from the app in production. Wondering what the most straightforward way is to implement this common error state @Michal Srb do you know if there's a workaround for this right now? Or does it require a library change
ballingt
ballingt6mo ago
That's right, you need a custom provider. An alternative could be for the existing provider to accept options for whether it's ok to reveal that an email does not exist, etc for all the errors that could be thrown.
ballingt
ballingt6mo ago
That discussion on https://github.com/get-convex/convex-auth/issues/124 doesn't list specific ones that would be useful to implement, would love to hear which others you want.
GitHub
Cleanly handling expected errors with Password provider · Issue #12...
There are a couple of posts on the discord about this but doesn't seem to be any answer. In short, it's really unclear how to properly handle expected (application) errors. These types of e...
ballingt
ballingt6mo ago
well it lists too many, everything from Firebase Generally configuration through code seems nice for complex cases, but if it's just a few errors you'd like thrown those could be added to the provider that comes with the library.
Eva
Eva6mo ago
These two basic ones would cover most cases, I imagine: - Account doesn't exist / email not found - Login rate limit exceeded For the password reset flow (these may exist already?, haven't checked): - Code expired - Invalid code
smaccoun
smaccoun5mo ago
Hey sorry for the slow response everyone. I've spent some more time with it and understand much better how the providers work now. See my last comment on that issue...i don't think any architectural change is necessary, but some docs or good default exports might help out Actually @Eva at the start of that comment i mention some common ones that mabe could be a part of all, regardless of provider implementation. I think the list you have there of those 4 might be really good examples that could exist for that Seems like it would be nice for the universal errors (like rate limit exceeded) to stil be baked in, but tbh I dunno if the best solution is to have that in some common core code or is better just documented or exported with default providers (i provided one such example in the comment). Definitely interesting to think about....I think both solutions could probably be fine
ballingt
ballingt5mo ago
ok so far I see Eva mentions 1. Account doesn't exist / email not found 2. Login rate limit exceeded and you mention * ConvexAuthTooManyFailedAttemptsError (same as 2 above) * Invalid Secret (wrong password I assume?) so whoever writes a new email provider should consider these three
smaccoun
smaccoun5mo ago
I think those are pretty good yea. I still think some kind of just exposed composable config or default provider could be fine too, rather than a single forced response. Something like export function defaultPasswordConfig(args: {minPasswordLength: string, ....}): PasswordConfig {...} that then provides those errors might be a good solution but i dunno as much about how the whole system works so just an idea
const ParamsSchema = z.object({
email: z.string().email(),
});

export const defaultPasswordConfig = (custom: {minPasswordLength?: number}): PasswordConfig => ({
validatePasswordRequirements: (password) => {
const minPasswordLength = custom.minPasswordLength ?? 8
if (password.length >= minPasswordLength) {
throw new ConvexError("Password must be at least 8 characters");
}
},
crypto: {
verifySecret: async (secret, hash) => {
if (secret === hash) {
return true;
}
throw new ConvexError("Invalid credentials");
},
hashSecret: async (secret) => {
console.log("TODO???");
return secret;
},
},
profile(params) {
// check for existing account errors here also?

const { error, data } = ParamsSchema.safeParse(params);
if (error) {
throw new ConvexError("Invalid email");
}
return { email: data.email };
},
})
const ParamsSchema = z.object({
email: z.string().email(),
});

export const defaultPasswordConfig = (custom: {minPasswordLength?: number}): PasswordConfig => ({
validatePasswordRequirements: (password) => {
const minPasswordLength = custom.minPasswordLength ?? 8
if (password.length >= minPasswordLength) {
throw new ConvexError("Password must be at least 8 characters");
}
},
crypto: {
verifySecret: async (secret, hash) => {
if (secret === hash) {
return true;
}
throw new ConvexError("Invalid credentials");
},
hashSecret: async (secret) => {
console.log("TODO???");
return secret;
},
},
profile(params) {
// check for existing account errors here also?

const { error, data } = ParamsSchema.safeParse(params);
if (error) {
throw new ConvexError("Invalid email");
}
return { email: data.email };
},
})
Didn't actually check if this code runs ^^ but just is the gist of it
Sronds
Sronds4mo ago
are there any updates on this? @Tom currently running into a similar issue and would love to know if there is a clean way to show the relevant error on the client
ballingt
ballingt4mo ago
Writing your own provider is the only way to know which issues occured here so far. If someone writes a provider that does something reasonable here happy to include it as an option in the library.
Aseem
Aseem2mo ago
Stumbled upon this issue again and again.
No description
erquhart
erquhart2mo ago
This is a different issue, can you open a new post
Hopeless XI
Hopeless XI2w ago
Hello i am new to Convex Dev and first thing i tried Convex Auth. I tried Password and ConvexCredentials but it doesn't give out user friendly errors. Has this issue fixed? It was bad experience for me as a new Convex Developer to face this issue as auth is very common thing? Please tell me how to fix the issues - I tried custom signIn using actionGeneric as well as created my custom ConvexCredentials but it doesnt show the Convex Error message I want it show. Am i missing something from the docs? Man I had high hopes from convex but it is failing with simple auth issues? Good think I worked on auth before doing other things as it would have led to disappointment later on. P.S I am not againt Covex and I have high hopes but please tell me how to show custom error messages for login and sign up with various edge cases.
erquhart
erquhart2w ago
Passwords - Convex Auth
Authentication library for your Convex backend
Hopeless XI
Hopeless XI2w ago
I tried that but i still dont get custom errors rather strange long errors from convex. Do you have any working examples with custom error messages for sign in and sign up
erquhart
erquhart2w ago
Can you share an example error, and also what version of convex-auth you're running I don't have an example but what you're describing sounds like a bug that was fixed very recently. The examples in the docs there should work as described. Can you also confirm whether you're using next.js
Hopeless XI
Hopeless XI2w ago
No not next js. React + convex latest version Not using next.js. Just vite + react + convex "@auth/core": "0.37.0", "@convex-dev/auth": "^0.0.81", "convex": "^1.23.0", "react": "^19.0.0", -- Error i get in terminal: [CONVEX A(auth:authSignIn)] Uncaught Error: InvalidAccountId .... I know error is for user not found. I gett similar errors for different edge cases. --
Hopeless XI
Hopeless XI2w ago
I want to show the different user friendly error messages based on sign in or sign up? How can i do it??
erquhart
erquhart2w ago
You're parsing and validating the password inside of the authorize() method, have you tried validatePasswordRequirements()?
Hopeless XI
Hopeless XI2w ago
I did try but that didnt work. The errors r not friendly. I want to show invalid credentials error for any wrong password user types but validate and show password invalid message on sign up as well as show user already exists error in case. Not sure how to build proper error handling system on convex I even asked ai on docs but it saus there r some issues and it lead me to this discord channel page
erquhart
erquhart2w ago
I need to take a look at why throwing a ConvexError from authorize() isn't working, I'd expect that to make it up to the client
Hopeless XI
Hopeless XI2w ago
Yeah please take a look whenever free and let me know i am stuck 😕 🙏
erquhart
erquhart2w ago
Your authorize function should be getting called here: https://github.com/get-convex/convex-auth/blob/f9db58c8ee45ec528e168f792a1621459fd3224e/src/server/implementation/signIn.ts#L189 The signIn() at the top of the call chain, the one you're importing in your auth.ts, is kicking it off here: https://github.com/get-convex/convex-auth/blob/f9db58c8ee45ec528e168f792a1621459fd3224e/src/server/implementation/index.ts#L416 There are no try/catch blocks in between, so I'm surprised the error isn't propagating. If you want to stay unblocked in the meantime, you have the option of running a fork to troubleshoot. Happy to help you set up if you're interested in self serving
Hopeless XI
Hopeless XI2w ago
Can you create a new convex auth project and test my authorise code? you can debug the issue faster. Default project already comes with sign in just have to add authorise and console log in react? Convex Error is not being sent to frontend
erquhart
erquhart2w ago
I'm saying I can't get to it now, but I do plan to look into it. Just giving you a self serve option if you want to try and get unblocked sooner. Sorry, I know this is frustrating
Hopeless XI
Hopeless XI2w ago
Can you read through the code and see if i am making some mistake? I am not sure why my "signin if block" is not being triggered instead it keeps going to the last line of catch block "throw error" ?
erquhart
erquhart2w ago
Can you paste an example error that you expect to not reach that line? Also please log “error.constructor.name” and paste that as well.

Did you find this page helpful?