Custom auth error messages?

I'm following the docs here: https://wasp-lang.dev/docs/auth/username-and-pass#customizing-the-auth-flow I now have a custom user registration page & when I try to register with a username that already exists, my signup endpoint (that otherwise works fine), returns a:
Failed to load resource: the server responded with a status of 422 (Unprocessable Entity)
auth.tsx?t=1703620051622:49 error Error: Save failed
at handleApiError (api.ts:84:11)
at signup (signup.ts:8:5)
at async onSubmit (auth.tsx?t=1703620051622:42:13)
at async createFormControl.ts:1127:9
Failed to load resource: the server responded with a status of 422 (Unprocessable Entity)
auth.tsx?t=1703620051622:49 error Error: Save failed
at handleApiError (api.ts:84:11)
at signup (signup.ts:8:5)
at async onSubmit (auth.tsx?t=1703620051622:42:13)
at async createFormControl.ts:1127:9
"Save failed" doesn't seem like a very helpful error message. How do we go about catching these errors or managing what is happening under the wasp magic going on? This will allow me to bubble up more meaningful error messages to the user, such as "Username is already in use. Please try again." Thanks
Username & Password | Wasp
Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client-side.
9 Replies
Filip
Filip6mo ago
Edit: read Miho's suggestion below Hi again @KYAN1TE, I'm not sure whether it's possible to get more details by just catching the error (@miho correct me if I'm wrong), but you can implement a custom action that does all the checking you need (e.g., see if the user exists and return an appropriate error if it does): https://wasp-lang.dev/docs/auth/username-and-pass#2-creating-your-custom-actions
Username & Password | Wasp
Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client-side.
miho
miho6mo ago
When you signup with an existing user, the response body will contain an extra message for you 🙂 That being said, you can't really get the "lower level" access to catch the auth errors yourself unless you are willing to create your custom signup action and use Prisma directly (which I hope you don't need 🤞) Were you able to get done what you were looking for? 🙂
No description
KYAN1TE
KYAN1TE6mo ago
@Filip Thanks for the response! I have actually implemented a custom action as the docs you linked were what I was following. The issue is that the catch statement that sets the error on the UI, isn't very valuable when the error is something very generic such as Request failed with status code 500 to the user. Server side I get a PrismaClientKnownRequestError so there may be something fancier I can do in my catch statement. Edit: Unable to catch that error server side :/ In addition, not sure I'm a fan of an extra database query to check whether a user already exists with a username, particularly when it's already set as a unique constraint in the SQL database, if that was the suggestion? Edit: I take the above back, given that this seems to be what wasp does out of the box: https://github.com/wasp-lang/wasp/blob/994a58f0dc37744bc9f03830d7d0ca43066bb345/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts#L36 @miho As said above, in my preview tab, I simply see the PrismaClientKnownRequestError as part of my custom signup action. So I guess as long as I want to roll with a custom signup action where I'm using Prisma directly, I won't get any wasp magic for free, right? @Filip @miho Given the above, is it safe to say that my headaches are as a result of rolling a custom signup action? The reason I've gone down this approach is because I want my users to be able to also specify a display name upon signing up alongside some other preferences. e.g.
import { User } from "@wasp/entities";
import type { SignupUser } from "@wasp/actions/types";

type SignupPayload = Pick<
User,
"username" | "password" | "displayName"
>;

export const signUp: SignupUser<SignupPayload, User> = async (
args,
context
) => {
const newUser = context.entities.User.create({
data: {
username: args.username,
password: args.password,
displayName: args.displayName
},
});

return newUser;
};
import { User } from "@wasp/entities";
import type { SignupUser } from "@wasp/actions/types";

type SignupPayload = Pick<
User,
"username" | "password" | "displayName"
>;

export const signUp: SignupUser<SignupPayload, User> = async (
args,
context
) => {
const newUser = context.entities.User.create({
data: {
username: args.username,
password: args.password,
displayName: args.displayName
},
});

return newUser;
};
I'd much rather just be able to use the out of box wasp (last thing I really want to do is roll my own auth...) tbh if I can achieve what I want to achieve with it.
KYAN1TE
KYAN1TE6mo ago
Me again, I've just discovered https://wasp-lang.dev/docs/auth/overview#customizing-the-signup-process I don't know how I overlooked this, I think I'm okay now as I can just extend the existing wasp auth 👍 Will leave this open for the next day or two in case I run in to any more issues otherwise I'll most likely mark as solved
Using Auth | Wasp
Auth is an essential piece of any serious application. Coincidentally, Wasp provides authentication and authorization support out of the box.
Filip
Filip6mo ago
@KYAN1TE glad to hear it! Good luck with the rest of the implementation. We'll be here if you need us 🙂
KYAN1TE
KYAN1TE6mo ago
Hmmm, I've hit another wall 🤕 The appearance prop options seem to be limited (relative to my custom inputs that use tailwind for example) & I also cannot get rid of the text that says Create a new account for example using the wasp SignupForm. Is there a way around this? Just had a look at the source code. Doesn't look like it'd be too much overhead to add some extra props to facilitate not displaying the title &/or the default inputs at first glance (I could be wrong). In fact, it looks like it'd take me longer to setup all the haskell stuff to run the project, than it would do for me to just do the react changes & submit a PR 😭 I've rolled back the years to pretend I know how to write css & ended up applying this to my auth component lol: h2 { display: none; } div:has(> input[name="username"]:not(#username)) { display: none; } div:has(> input[name="password"]:not(#password)) { display: none; } Downside is that the has selector doesn't work on some modern versions of firefox, so I will continue having a play & this can be a workaround for now I guess until the out of box components have more customisation options 🤷‍♂️ and something like this covers firefox:
const defaultWaspFormFieldsToHide = ["username", "password"];

useEffect(() => {
const hideFirstFormField = (fieldName: string) => {
const input = document.querySelector(
`input[name="${fieldName}"]:not(#${fieldName})`
) as HTMLElement;
if (input) {
input.style.display = "none";

const prevLabel = input.previousElementSibling as HTMLElement;
if (prevLabel && prevLabel.tagName === "LABEL") {
prevLabel.style.display = "none";
}
}
};

const defaultWaspFormTitle = document.querySelector('h2') as HTMLElement;
defaultWaspFormTitle.style.display = 'none';
defaultWaspFormFieldsToHide.map(hideFirstFormField);
});
const defaultWaspFormFieldsToHide = ["username", "password"];

useEffect(() => {
const hideFirstFormField = (fieldName: string) => {
const input = document.querySelector(
`input[name="${fieldName}"]:not(#${fieldName})`
) as HTMLElement;
if (input) {
input.style.display = "none";

const prevLabel = input.previousElementSibling as HTMLElement;
if (prevLabel && prevLabel.tagName === "LABEL") {
prevLabel.style.display = "none";
}
}
};

const defaultWaspFormTitle = document.querySelector('h2') as HTMLElement;
defaultWaspFormTitle.style.display = 'none';
defaultWaspFormFieldsToHide.map(hideFirstFormField);
});
Let me know if there is a better way that doesn't rely on changes made to Wasp itself, thanks
martinsos
martinsos6mo ago
Nice job! Yeah we can add more customisation options, and there might be one more trivk you can do - i am not at my pc right now but i will get back to you in 2 days regarding this!
KYAN1TE
KYAN1TE6mo ago
No worries, thanks @martinsos !
martinsos
martinsos5mo ago
Sorry for being a bit late with the response -> first of all, impressive work with the CSS (and some JS, ok :D) to get this working! Hah, I never dared to say I know CSS, I kind of always just get by :D. So there are a couple of options here: 1. We could, in the future versions of Wasp, make the signup form more flexible, so you can also specify stuff like this. I created an issue for this, but we probably won't tackle it immediately: https://github.com/wasp-lang/wasp/issues/1617 . 2. You could not use our SignupForm, but instead go "level deeper" and implement your own form for signup while using our lower-level primitives. For example, if you are using username/pass auth method, you could use signup and login actions that Wasp exposes for it. Here are these primitives for username/pass: https://wasp-lang.dev/docs/auth/username-and-pass#1-using-the-signup-and-login-actions , here are for OAuth providers: https://wasp-lang.dev/docs/auth/social-auth/overview#ui-helpers . This said, I have to admit I had hard time finding docs for analogous primitives for email/pass -> we hovewer recently rewrote most of our docs, so maybe it got lost / moved in the process. @miho do you have any idea where we could find this info in the docs? But in the meantime you can look at generated code in .wasp/out/ and build your sign up form based on that also. 3. The quite hackish way is to modify the templates that Wasp uses. So Wasp uses these template files to generate most of its code, and you can modify them directly. This wont persist between Wasp versions (so if you install new Wasp version, it will bring another set of templates with it), and it will also apply to all the apps you are building with Wasp, so it is quite a specific hack. On my machine I can find these under /home/martin/.local/share/wasp/data/Generator/templates/. NOTE: This is a hack though, it is specifc for your local machine, ... . So probably best not do to this. So option (2) here is really the right way at the moment! I am curious, what is the end goal you are aiming for? You have hidden the title, and you have also hidden some inputs? What remains then, of your signup form? What do you want it to look like?