Server side validation

Docs are showing examples of implementation of betterAuth using auth-client, which is great, simple, with callbacks to handle errors, etc. BUT it allows only for client side validation which is NOT SECURE as you can bypass it easily and harm server, db, etc. How do I add server side validation of all the fields for signUp/SignIn? I know I can use auth.api.(whatever) but then I need to handle all the errors and other stuff by myself. Am I missing something? Does betterAuth library makes some server side validation internally? Or I have to choose between simplicity of implementation and security ? Thank you
Solution:
you can use try catch if u want - ```ts import { APIError } from "better-auth/api"; try {...
Jump to solution
12 Replies
KiNFiSH
KiNFiSH2w ago
All those client exposed API do have their own corresponding server side API call which will be mounted if those are enabled. So you can skip the client convenance here and directly make a request to the mounted sever side endpoints as well.
Glen Kurio
Glen KurioOP2w ago
Ok, but could you give me an example of how does it look in the code? Or is it somewhere in the docs ? Because I'm not following, sorry I have this in my action:
"use server";

import { validatedAction } from "@/lib/action-helpers";
import { auth } from "@/lib/auth";
import { signInSchema } from "@/lib/types";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export const loginEmail = validatedAction(signInSchema, async (data) => {
const { email, password } = data;

await auth.api.signInEmail({
body: { email, password },
});

redirect("/dashboard");
});
"use server";

import { validatedAction } from "@/lib/action-helpers";
import { auth } from "@/lib/auth";
import { signInSchema } from "@/lib/types";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export const loginEmail = validatedAction(signInSchema, async (data) => {
const { email, password } = data;

await auth.api.signInEmail({
body: { email, password },
});

redirect("/dashboard");
});
How do I handle all those errors it may return 429, 401, 403? auth.api.signInEmail does not return them. this is what it returns (assigned to 'result' variable)
const result: {
redirect: boolean;
token: string;
url: string | undefined;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}
const result: {
redirect: boolean;
token: string;
url: string | undefined;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}
Solution
KiNFiSH
KiNFiSH2w ago
you can use try catch if u want -
import { APIError } from "better-auth/api";

try {
const result = await auth.api.signInEmail({
body: {
password: "password"
}
});

} catch (error) {
if (error instanceof APIError) {
switch (error.status) {
case 429: // Too Many Requests
console.error("Rate limit exceeded. Please try again later.");
break;

case 401: // Unauthorized
console.error("Invalid credentials");
break;

case 403: // Forbidden
console.error("Access denied");
break;

case "BAD_REQUEST":
console.error(error.message);
break;

case "USER_NOT_FOUND":
console.error("User not found");
break;

case "INVALID_EMAIL":
console.error("Invalid email format");
break;

case "PASSWORD_TOO_SHORT":
case "PASSWORD_TOO_LONG":
console.error(error.message);
break;

default:
console.error("An unexpected error occurred");
}
} else {
console.error("An unexpected error occurred", error);
}
}
import { APIError } from "better-auth/api";

try {
const result = await auth.api.signInEmail({
body: {
password: "password"
}
});

} catch (error) {
if (error instanceof APIError) {
switch (error.status) {
case 429: // Too Many Requests
console.error("Rate limit exceeded. Please try again later.");
break;

case 401: // Unauthorized
console.error("Invalid credentials");
break;

case 403: // Forbidden
console.error("Access denied");
break;

case "BAD_REQUEST":
console.error(error.message);
break;

case "USER_NOT_FOUND":
console.error("User not found");
break;

case "INVALID_EMAIL":
console.error("Invalid email format");
break;

case "PASSWORD_TOO_SHORT":
case "PASSWORD_TOO_LONG":
console.error(error.message);
break;

default:
console.error("An unexpected error occurred");
}
} else {
console.error("An unexpected error occurred", error);
}
}
Nico64
Nico642w ago
const result: {
redirect: boolean;
token: string;
url: string | undefined;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}
const result: {
redirect: boolean;
token: string;
url: string | undefined;
user: {
id: string;
email: string;
name: string;
image: string | null | undefined;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}
Hey @KINFISH By the way, do you know if this token can/should be returned to the frontend or is it private data for the backend? Because I use auth.api.signInEmail() on the backend to signin users, I don't use authClient.
KiNFiSH
KiNFiSH2w ago
Nope.. token is not returned on the frontend using authClient just only on backend
Nico64
Nico642w ago
ok thanks. What's it for then?
KiNFiSH
KiNFiSH2w ago
for token , you will get it from an endpoint handler which is auth.api.(handlerName)
Nico64
Nico642w ago
Yes with auth.api.signinEmail but what is its use? To understand if I need it at some point
KiNFiSH
KiNFiSH2w ago
when you add plugins and emailAndPassword enable. they give you to 2 apis to use one for client and one for server and at the same the endpoint for the each plugin and enables will be mounted so when u call a client api that is actually the same doing a call to the endpoint that is mounted but actually for server api since you already had a server endpoint mounted it calls the handler function to run not actually endpoint which does not make sense. since.u are already on the server so for RSC frameworks or any metaframework or backend codes that needs the api make sure to use it
Nico64
Nico642w ago
I know, I used authClient and set the handler to the server side. But there are some behaviors I'm not a fan of. For example, sending an email for each login attempt when a verification process is in progress. I prefer to have a little more control and manage each endpoint myself to perform certain data validations, etc. So I directly query my backend, which in turn executes auth.api. So, I'm wondering if I should return "token" to the front end, or if it's data only for the backend. some routes I do nothing, I just forward the request to auth.api
KiNFiSH
KiNFiSH2w ago
what kind of customization are you looking for ?
Nico64
Nico642w ago
As I said in my previous message, for example, I want to prevent someone who hasn't finished verifying their mail from receiving a bunch of emails every time they try to signin (also on update its email). https://github.com/better-auth/better-auth/issues/788#issuecomment-2735069860 It's also a precaution to preserve the behavior I use. After an upgrade there was a breaking change on the reset password. But about this token, can you please tell me if I should return it to the front or not? What's the use of this token?

Did you find this page helpful?