Skip to content

feat(add-on): Better Auth #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Setting up Better Auth

- Set the `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` in your `.env.local`.
- if you are using pnpm or deno, make sure `better-sqlite3` post install script is run
- Run `npx @better-auth/cli migrate`
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Better Auth Secret can be anything, preferable a random string
BETTER_AUTH_SECRET=

# Backend URL
BETTER_AUTH_URL=http://localhost:3000
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { authClient } from "@/lib/auth-client";

export default function HeaderUser() {
return (
<div className="flex items-center justify-center gap-2">
<Avatar />
<LogoutButton />
</div>
);
}

function Avatar() {
const { data } = authClient.useSession();
const user = data?.user;
if (!user) return null;
return (
<div className="relative inline-flex items-center justify-center w-6 h-6 overflow-hidden bg-gray-100 rounded-full dark:bg-gray-600">
<span className="text-xs font-medium text-gray-600 dark:text-gray-300 uppercase">
{user.name.slice(0, 2)}
</span>
</div>
);
}

function LogoutButton() {
const { data } = authClient.useSession();
const session = data?.session;
if (!session) return null;
return (
<button
onClick={() => authClient.signOut()}
type="button"
className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-2 py-1 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
>
Logout
</button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createAuthClient } from "better-auth/react"

export const authClient = createAuthClient()
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { betterAuth } from "better-auth";
import { db } from "./db";

export const auth = betterAuth({
// This example uses local sqlite database
database: db,
emailAndPassword: {
enabled: true
},
socialProviders: {
// You can add more providers here, check the documentation for more details
// github: {
// clientId: "",
// clientSecret: ""
// }
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Database from "better-sqlite3";

export const db = new Database("./sqlite.db");
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { auth } from '@/lib/auth'
import { createAPIFileRoute } from '@tanstack/react-start/api'

export const APIRoute = createAPIFileRoute('/api/auth/$')({
GET: ({ request }) => {
return auth.handler(request)
},
POST: ({ request }) => {
return auth.handler(request)
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { authClient } from "@/lib/auth-client";
import { createFileRoute, Link } from "@tanstack/react-router";
import { useState } from "react";

export const Route = createFileRoute("/demo/better-auth/login")({
component: RouteComponent,
});

function RouteComponent() {
const { data, isPending } = authClient.useSession();
const session = data?.session;
const [error, setError] = useState<string | null>(null);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const formData = new FormData(e.currentTarget);
e.preventDefault();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
authClient.signIn.email(
{
email,
password,
},
{
onError(context) {
setError(context.error.message);
},
onSuccess() {
e.currentTarget.reset();
},
},
);
};
return (
<>
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white mb-4">
Sign in to your account
</h1>
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
<div>
<label
htmlFor="email"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Your email
</label>
<input
type="email"
name="email"
id="email"
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="[email protected]"
required
/>
</div>
<div>
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Password
</label>
<input
type="password"
name="password"
id="password"
placeholder="••••••••"
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
required
/>
</div>
<button
type="submit"
disabled={isPending || !!session}
className="w-full text-white bg-blue-700 hover:bg-blue-800 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
>
Sign in
</button>
{session && <p className="text-green-500 m-0">You are signed in</p>}
{error && <p className="text-red-500 m-0">{error}</p>}
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
Don't have an account yet?{" "}
<Link
to="/demo/better-auth/signup"
className="font-medium text-primary-600 hover:underline dark:text-primary-500"
>
Sign up
</Link>
</p>
</form>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { authClient } from "@/lib/auth-client";
import { createFileRoute, Link } from "@tanstack/react-router";
import { useState } from "react";

export const Route = createFileRoute("/demo/better-auth/signup")({
component: RouteComponent,
});

function RouteComponent() {
const { data, isPending } = authClient.useSession();
const session = data?.session;
const [error, setError] = useState<string | null>(null);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const formData = new FormData(e.currentTarget);
e.preventDefault();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const name = (formData.get("email") as string)?.split("@")[0];
authClient.signUp.email(
{
email,
password,
name,
},
{
onError(context) {
setError(context.error.message);
},
onSuccess() {
e.currentTarget.reset();
},
},
);
};
return (
<>
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white mb-4">
Sign up to your account
</h1>
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
<div>
<label
htmlFor="email"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Your email
</label>
<input
type="email"
name="email"
id="email"
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="[email protected]"
required
/>
</div>
<div>
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Password
</label>
<input
type="password"
name="password"
id="password"
placeholder="••••••••"
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
required
/>
</div>
<button
type="submit"
disabled={isPending || !!session}
className="w-full text-white bg-blue-700 hover:bg-blue-800 disabled:opacity-50 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
>
Sign up
</button>
{session && <p className="text-green-500 m-0">You are signed in</p>}
{error && <p className="text-red-500 m-0">{error}</p>}
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
Already have an account?{" "}
<Link
to="/demo/better-auth/login"
className="font-medium text-primary-600 hover:underline dark:text-primary-500"
>
Sign in
</Link>
</p>
</form>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";

export const Route = createFileRoute("/demo/better-auth")({
component: RouteComponent,
});

function RouteComponent() {
return (
<section className="bg-gray-50 dark:bg-gray-900 flex items-center justify-center mx-auto min-h-[calc(100vh-32px)]">
<div className="p-6 w-full bg-white rounded-lg shadow dark:border dark:bg-gray-800 dark:border-gray-700 max-w-md mx-4">
<Outlet />
</div>
</section>
);
}


14 changes: 14 additions & 0 deletions packages/cta-engine/templates/react/add-on/better-auth/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "Better Auth",
"description": "Add Better Auth authentication to your application.",
"phase": "add-on",
"templates": ["file-router"],
"link": "https://www.better-auth.com",
"routes": [
{
"url": "/demo/better-auth/signup",
"name": "Better Auth"
}
],
"dependsOn": ["start"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"dependencies": {
"better-auth": "^1.2.7",
"better-sqlite3": "^11.9.1"
}
}