Skip to content

Commit a11f0fe

Browse files
committed
feat(add-on): Better Auth
1 parent b23d3a1 commit a11f0fe

File tree

12 files changed

+305
-0
lines changed

12 files changed

+305
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Setting up Better Auth
2+
3+
- Set the `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` in your `.env.local`.
4+
- if you are using pnpm or deno, make sure `better-sqlite3` post install script is run
5+
- Run `npx @better-auth/cli migrate`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Better Auth Secret can be anything, preferable a random string
2+
BETTER_AUTH_SECRET=
3+
4+
# Backend URL
5+
BETTER_AUTH_URL=http://localhost:3000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { authClient } from "@/lib/auth-client";
2+
3+
export default function HeaderUser() {
4+
return (
5+
<div className="flex items-center justify-center gap-2">
6+
<Avatar />
7+
<LogoutButton />
8+
</div>
9+
);
10+
}
11+
12+
function Avatar() {
13+
const { data } = authClient.useSession();
14+
const user = data?.user;
15+
if (!user) return null;
16+
return (
17+
<div className="relative inline-flex items-center justify-center w-6 h-6 overflow-hidden bg-gray-100 rounded-full dark:bg-gray-600">
18+
<span className="text-xs font-medium text-gray-600 dark:text-gray-300 uppercase">
19+
{user.name.slice(0, 2)}
20+
</span>
21+
</div>
22+
);
23+
}
24+
25+
function LogoutButton() {
26+
const { data } = authClient.useSession();
27+
const session = data?.session;
28+
if (!session) return null;
29+
return (
30+
<button
31+
onClick={() => authClient.signOut()}
32+
type="button"
33+
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"
34+
>
35+
Logout
36+
</button>
37+
);
38+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createAuthClient } from "better-auth/react"
2+
3+
export const authClient = createAuthClient()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { betterAuth } from "better-auth";
2+
import { db } from "./db";
3+
4+
export const auth = betterAuth({
5+
// This example uses local sqlite database
6+
database: db,
7+
emailAndPassword: {
8+
enabled: true
9+
},
10+
socialProviders: {
11+
// You can add more providers here, check the documentation for more details
12+
// github: {
13+
// clientId: "",
14+
// clientSecret: ""
15+
// }
16+
}
17+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Database from "better-sqlite3";
2+
3+
export const db = new Database("./sqlite.db");
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { auth } from '@/lib/auth'
2+
import { createAPIFileRoute } from '@tanstack/react-start/api'
3+
4+
export const APIRoute = createAPIFileRoute('/api/auth/$')({
5+
GET: ({ request }) => {
6+
return auth.handler(request)
7+
},
8+
POST: ({ request }) => {
9+
return auth.handler(request)
10+
},
11+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { authClient } from "@/lib/auth-client";
2+
import { createFileRoute, Link } from "@tanstack/react-router";
3+
import { useState } from "react";
4+
5+
export const Route = createFileRoute("/demo/better-auth/login")({
6+
component: RouteComponent,
7+
});
8+
9+
function RouteComponent() {
10+
const { data, isPending } = authClient.useSession();
11+
const session = data?.session;
12+
const [error, setError] = useState<string | null>(null);
13+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
14+
const formData = new FormData(e.currentTarget);
15+
e.preventDefault();
16+
const email = formData.get("email") as string;
17+
const password = formData.get("password") as string;
18+
authClient.signIn.email(
19+
{
20+
email,
21+
password,
22+
},
23+
{
24+
onError(context) {
25+
setError(context.error.message);
26+
},
27+
onSuccess() {
28+
e.currentTarget.reset();
29+
},
30+
},
31+
);
32+
};
33+
return (
34+
<>
35+
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white mb-4">
36+
Sign in to your account
37+
</h1>
38+
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
39+
<div>
40+
<label
41+
htmlFor="email"
42+
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
43+
>
44+
Your email
45+
</label>
46+
<input
47+
type="email"
48+
name="email"
49+
id="email"
50+
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"
51+
placeholder="[email protected]"
52+
required
53+
/>
54+
</div>
55+
<div>
56+
<label
57+
htmlFor="password"
58+
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
59+
>
60+
Password
61+
</label>
62+
<input
63+
type="password"
64+
name="password"
65+
id="password"
66+
placeholder="••••••••"
67+
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"
68+
required
69+
/>
70+
</div>
71+
<button
72+
type="submit"
73+
disabled={isPending || !!session}
74+
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"
75+
>
76+
Sign in
77+
</button>
78+
{session && <p className="text-green-500 m-0">You are signed in</p>}
79+
{error && <p className="text-red-500 m-0">{error}</p>}
80+
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
81+
Don't have an account yet?{" "}
82+
<Link
83+
to="/demo/better-auth/signup"
84+
className="font-medium text-primary-600 hover:underline dark:text-primary-500"
85+
>
86+
Sign up
87+
</Link>
88+
</p>
89+
</form>
90+
</>
91+
);
92+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { authClient } from "@/lib/auth-client";
2+
import { createFileRoute, Link } from "@tanstack/react-router";
3+
import { useState } from "react";
4+
5+
export const Route = createFileRoute("/demo/better-auth/signup")({
6+
component: RouteComponent,
7+
});
8+
9+
function RouteComponent() {
10+
const { data, isPending } = authClient.useSession();
11+
const session = data?.session;
12+
const [error, setError] = useState<string | null>(null);
13+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
14+
const formData = new FormData(e.currentTarget);
15+
e.preventDefault();
16+
const email = formData.get("email") as string;
17+
const password = formData.get("password") as string;
18+
const name = (formData.get("email") as string)?.split("@")[0];
19+
authClient.signUp.email(
20+
{
21+
email,
22+
password,
23+
name,
24+
},
25+
{
26+
onError(context) {
27+
setError(context.error.message);
28+
},
29+
onSuccess() {
30+
e.currentTarget.reset();
31+
},
32+
},
33+
);
34+
};
35+
return (
36+
<>
37+
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white mb-4">
38+
Sign up to your account
39+
</h1>
40+
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
41+
<div>
42+
<label
43+
htmlFor="email"
44+
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
45+
>
46+
Your email
47+
</label>
48+
<input
49+
type="email"
50+
name="email"
51+
id="email"
52+
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"
53+
placeholder="[email protected]"
54+
required
55+
/>
56+
</div>
57+
<div>
58+
<label
59+
htmlFor="password"
60+
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
61+
>
62+
Password
63+
</label>
64+
<input
65+
type="password"
66+
name="password"
67+
id="password"
68+
placeholder="••••••••"
69+
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"
70+
required
71+
/>
72+
</div>
73+
<button
74+
type="submit"
75+
disabled={isPending || !!session}
76+
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"
77+
>
78+
Sign up
79+
</button>
80+
{session && <p className="text-green-500 m-0">You are signed in</p>}
81+
{error && <p className="text-red-500 m-0">{error}</p>}
82+
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
83+
Already have an account?{" "}
84+
<Link
85+
to="/demo/better-auth/login"
86+
className="font-medium text-primary-600 hover:underline dark:text-primary-500"
87+
>
88+
Sign in
89+
</Link>
90+
</p>
91+
</form>
92+
</>
93+
);
94+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createFileRoute, Outlet } from "@tanstack/react-router";
2+
3+
export const Route = createFileRoute("/demo/better-auth")({
4+
component: RouteComponent,
5+
});
6+
7+
function RouteComponent() {
8+
return (
9+
<section className="bg-gray-50 dark:bg-gray-900 flex items-center justify-center mx-auto min-h-[calc(100vh-32px)]">
10+
<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">
11+
<Outlet />
12+
</div>
13+
</section>
14+
);
15+
}
16+
17+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "Better Auth",
3+
"description": "Add Better Auth authentication to your application.",
4+
"phase": "add-on",
5+
"templates": ["file-router"],
6+
"link": "https://www.better-auth.com",
7+
"routes": [
8+
{
9+
"url": "/demo/better-auth/signup",
10+
"name": "Better Auth"
11+
}
12+
],
13+
"dependsOn": ["start"]
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"dependencies": {
3+
"better-auth": "^1.2.7",
4+
"better-sqlite3": "^11.9.1"
5+
}
6+
}

0 commit comments

Comments
 (0)