SolidJS SPA with Better Auth

I am trying to build a SolidJS SPA that uses the new Better Auth library to manage all things related to authentication/authorization for my app. Post all the needed configuration for it, on the SPA side, I have to use the Better Auth client to invoke all the auth related methods on my UI. I have one example here that I'm wrestling with in trying to get a redirect to happen after the user signs in but the redirect is not happening on the Solid SPA side: My Better Auth Client configuration:
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
My SignIn component:
import { Button } from "@kobalte/core/button";
import { action, query, redirect } from "@solidjs/router";
import { createEffect } from "solid-js";

import Logo from "../assets/logo_login.svg";
import { TextInput } from "../components/TextInput";
import { authClient } from "../lib/auth-client";
import { convertToRecord } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

export const getUserSession = query(() => authClient.getSession(), "userSession");

const signIn = action(async (data: FormData) => {
authClient.signIn.username(convertToRecord<SignInForm>(data), {
onSuccess: () => {
console.log("ON SUCCESS >>>>") // this logs correctly
throw redirect("/home", { revalidate: getUserSession.key }); // <--- this does not redirect
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
});

export default function SignIn() {
return (
<div class="h-full flex flex-col items-center justify-center gap-2">
<img src={Logo} width="200" />
<form action={signIn} method="post" class="flex flex-col gap-4">
<TextInput name="username" label="Username" />
<TextInput name="password" label="Password" type="password" />
<Button class="button" type="submit">
Login
</Button>
</form>
</div>
);
}
import { Button } from "@kobalte/core/button";
import { action, query, redirect } from "@solidjs/router";
import { createEffect } from "solid-js";

import Logo from "../assets/logo_login.svg";
import { TextInput } from "../components/TextInput";
import { authClient } from "../lib/auth-client";
import { convertToRecord } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

export const getUserSession = query(() => authClient.getSession(), "userSession");

const signIn = action(async (data: FormData) => {
authClient.signIn.username(convertToRecord<SignInForm>(data), {
onSuccess: () => {
console.log("ON SUCCESS >>>>") // this logs correctly
throw redirect("/home", { revalidate: getUserSession.key }); // <--- this does not redirect
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
});

export default function SignIn() {
return (
<div class="h-full flex flex-col items-center justify-center gap-2">
<img src={Logo} width="200" />
<form action={signIn} method="post" class="flex flex-col gap-4">
<TextInput name="username" label="Username" />
<TextInput name="password" label="Password" type="password" />
<Button class="button" type="submit">
Login
</Button>
</form>
</div>
);
}
I'm suspecting that perhaps, the issue has to do with the fact that I am doing the throw redirect inside of the onSuccess handler of the Better Auth client as opposed to the scope of the action itself? Wondering if others have attempted this yet.
4 Replies
Brendonovich
Brendonovich20h ago
My guess is what you suggested, the throw needs to be handled in the action's scope
MaveriX89
MaveriX89OP20h ago
Gotcha -- let me see if Better Auth supports the non-callback expression for the signIn API call.
Brendonovich
Brendonovich20h ago
If it doesn't you can wrap it in your own promise
MaveriX89
MaveriX89OP20h ago
Yeah, this worked out in the end:
const signIn = action(async (data: FormData) => {
const response = await authClient.signIn.username(convertToRecord<SignInForm>(data));

if (response.error) throw new Error(response.error.message);

throw redirect("/home");
});
const signIn = action(async (data: FormData) => {
const response = await authClient.signIn.username(convertToRecord<SignInForm>(data));

if (response.error) throw new Error(response.error.message);

throw redirect("/home");
});

Did you find this page helpful?