mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 20:37:44 +00:00
feat: docs example trial
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "express",
|
"name": "@better-auth/dev-express",
|
||||||
|
"private": true,
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -26,3 +26,4 @@ yarn-error.log*
|
|||||||
.env*.local
|
.env*.local
|
||||||
.vercel
|
.vercel
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
certificates
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { AnimatePresence } from "@/components/ui/fade-in"
|
|
||||||
|
|
||||||
const ChangelogOne = () => {
|
|
||||||
return (
|
|
||||||
<AnimatePresence>
|
|
||||||
<div className="flex flex-col gap-4 items-start justify-center max-w-full md:max-w-2xl">
|
|
||||||
<img src="https://camo.githubusercontent.com/3282afc585d07e52e883ac2345467841e5c9cbe3befdec9dd6f84c603748e0d4/68747470733a2f2f726573656e642e636f6d2f5f6e6578742f696d6167653f75726c3d253246737461746963253246706f737473253246776562686f6f6b732e6a706726773d36343026713d3735" className="w-full h-[400px] object-cover rounded-lg" />
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h2 className="text-2xl font-bold tracking-tighter">
|
|
||||||
Commit message suggestions
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300 text-[0.855rem]">
|
|
||||||
In the latest release, I've added support for commit message and description suggestions via an integration with OpenAI. Commit looks at all of your changes, and feeds that into the machine with a bit of prompt-tuning to get back a commit message that does a surprisingly good job at describing the intent of your changes.
|
|
||||||
It's also been a pretty helpful way to remind myself what the hell I was working on at the end of the day yesterday when I get back to my computer and realize I didn't commit any of my work.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h4 className="text-xl tracking-tighter"> Improvement</h4>
|
|
||||||
</div>
|
|
||||||
<ul className="list-disc ml-10 text-[0.855rem] text-gray-600 dark:text-gray-300">
|
|
||||||
<li>
|
|
||||||
Added commit message and description suggestions powered by OpenAI
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Started commit message and description suggestions powered by OpenAI
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
Restored message and description suggestions powered by OpenAI
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Added commit message and description suggestions powered by OpenAI
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</AnimatePresence >
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default ChangelogOne
|
|
||||||
33
docs/app/changelogs/_logs/2024-09-28.tsx
Normal file
33
docs/app/changelogs/_logs/2024-09-28.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { AnimatePresence } from "@/components/ui/fade-in";
|
||||||
|
|
||||||
|
const listOfFeatures = ["The first better-auth public beta release"];
|
||||||
|
|
||||||
|
const bugFixes = [""];
|
||||||
|
|
||||||
|
const ChangelogOne = () => {
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
<div className="flex flex-col gap-4 items-start justify-center max-w-full md:max-w-2xl">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-2xl font-bold tracking-tighter">
|
||||||
|
Public Beta Release
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
The first public beta release of better-auth is now available. This
|
||||||
|
release includes a lot of new features and improvements.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 dark:text-gray-300 text-[0.855rem]"></p>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h4 className="text-xl tracking-tighter">Features</h4>
|
||||||
|
</div>
|
||||||
|
<ul className="list-disc ml-10 text-[0.855rem] text-gray-600 dark:text-gray-300">
|
||||||
|
{listOfFeatures.map((change, i) => (
|
||||||
|
<li key={i}>{change}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ChangelogOne;
|
||||||
@@ -1,23 +1,8 @@
|
|||||||
import ChangelogOne from "./2024-08-09";
|
import ChangelogOne from "./2024-09-28";
|
||||||
export const Logs = [
|
export const Logs = [
|
||||||
{
|
{
|
||||||
name: "Changelog 1",
|
name: "Changelog 1",
|
||||||
date: "2024-09-09",
|
date: "2024-09-28",
|
||||||
component: ChangelogOne,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Changelog 1",
|
|
||||||
date: "2024-09-09",
|
|
||||||
component: ChangelogOne,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Changelog 1",
|
|
||||||
date: "2024-09-09",
|
|
||||||
component: ChangelogOne,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Changelog 1",
|
|
||||||
date: "2024-09-09",
|
|
||||||
component: ChangelogOne,
|
component: ChangelogOne,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,83 +1,112 @@
|
|||||||
import { Layout } from './_components/_layout'
|
import { Layout } from "./_components/_layout";
|
||||||
import { useId } from 'react'
|
import { useId } from "react";
|
||||||
|
|
||||||
import { Intro, IntroFooter } from './_components/changelog-layout'
|
import { Intro, IntroFooter } from "./_components/changelog-layout";
|
||||||
import { StarField } from './_components/stat-field'
|
import { StarField } from "./_components/stat-field";
|
||||||
|
|
||||||
function Timeline() {
|
function Timeline() {
|
||||||
let id = useId()
|
let id = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pointer-events-none absolute inset-0 z-50 overflow-hidden lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem] lg:overflow-visible'>
|
<div className="pointer-events-none absolute inset-0 z-50 overflow-hidden lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem] lg:overflow-visible">
|
||||||
<svg className='absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 lg:left-full lg:ml-1 xl:left-auto xl:right-1 xl:ml-0' aria-hidden='true'>
|
<svg
|
||||||
|
className="absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 lg:left-full lg:ml-1 xl:left-auto xl:right-1 xl:ml-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id={id} width='6' height='8' patternUnits='userSpaceOnUse'>
|
<pattern id={id} width="6" height="8" patternUnits="userSpaceOnUse">
|
||||||
<path d='M0 0H6M0 8H6' className='stroke-sky-900/10 xl:stroke-white/10 dark:stroke-white/10' fill='none' />
|
<path
|
||||||
|
d="M0 0H6M0 8H6"
|
||||||
|
className="stroke-sky-900/10 xl:stroke-white/10 dark:stroke-white/10"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width='100%' height='100%' fill={`url(#${id})`} />
|
<rect width="100%" height="100%" fill={`url(#${id})`} />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Glow() {
|
function Glow() {
|
||||||
let id = useId()
|
let id = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='absolute inset-0 -z-10 overflow-hidden bg-gradient-to-tr from-transparent via-stone-950/5 to-transparent/10 lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem]'>
|
<div className="absolute inset-0 -z-10 overflow-hidden bg-gradient-to-tr from-transparent via-stone-950/5 to-transparent/10 lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem]">
|
||||||
<svg className='absolute -bottom-48 left-[-40%] h-[80rem] w-[180%] lg:-right-40 lg:bottom-auto lg:left-auto lg:top-[-40%] lg:h-[180%] lg:w-[80rem]' aria-hidden='true'>
|
<svg
|
||||||
|
className="absolute -bottom-48 left-[-40%] h-[80rem] w-[180%] lg:-right-40 lg:bottom-auto lg:left-auto lg:top-[-40%] lg:h-[180%] lg:w-[80rem]"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<radialGradient id={`${id}-desktop`} cx='100%'>
|
<radialGradient id={`${id}-desktop`} cx="100%">
|
||||||
<stop offset='0%' stopColor='rgba(41, 37, 36, 0.4)' />
|
<stop offset="0%" stopColor="rgba(41, 37, 36, 0.4)" />
|
||||||
<stop offset='53.95%' stopColor='rgba(28, 25, 23, 0.09)' />
|
<stop offset="53.95%" stopColor="rgba(28, 25, 23, 0.09)" />
|
||||||
<stop offset='100%' stopColor='rgba(0, 0, 0, 0)' />
|
<stop offset="100%" stopColor="rgba(0, 0, 0, 0)" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
<radialGradient id={`${id}-mobile`} cy='100%'>
|
<radialGradient id={`${id}-mobile`} cy="100%">
|
||||||
<stop offset='0%' stopColor='rgba(41, 37, 36, 0.3)' />
|
<stop offset="0%" stopColor="rgba(41, 37, 36, 0.3)" />
|
||||||
<stop offset='53.95%' stopColor='rgba(28, 25, 23, 0.09)' />
|
<stop offset="53.95%" stopColor="rgba(28, 25, 23, 0.09)" />
|
||||||
<stop offset='100%' stopColor='rgba(0, 0, 0, 0)' />
|
<stop offset="100%" stopColor="rgba(0, 0, 0, 0)" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width='100%' height='100%' fill={`url(#${id}-desktop)`} className='hidden lg:block' />
|
<rect
|
||||||
<rect width='100%' height='100%' fill={`url(#${id}-mobile)`} className='lg:hidden' />
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
fill={`url(#${id}-desktop)`}
|
||||||
|
className="hidden lg:block"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
fill={`url(#${id}-mobile)`}
|
||||||
|
className="lg:hidden"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<div className='absolute inset-x-0 bottom-0 right-0 h-px bg-white/5 mix-blend-overlay lg:left-auto lg:top-0 lg:h-auto lg:w-px' />
|
<div className="absolute inset-x-0 bottom-0 right-0 h-px bg-white/5 mix-blend-overlay lg:left-auto lg:top-0 lg:h-auto lg:w-px" />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FixedSidebar({ main, footer }: { main: React.ReactNode; footer: React.ReactNode }) {
|
function FixedSidebar({
|
||||||
|
main,
|
||||||
|
footer,
|
||||||
|
}: {
|
||||||
|
main: React.ReactNode;
|
||||||
|
footer: React.ReactNode;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className='relative flex-none overflow-hidden px-10 lg:pointer-events-none md:fixed lg:inset-0 lg:z-40 lg:flex lg:px-0'>
|
<div className="relative flex-none overflow-hidden px-10 lg:pointer-events-none md:fixed lg:inset-0 lg:z-40 lg:flex lg:px-0">
|
||||||
<Glow />
|
<Glow />
|
||||||
<div className='relative flex w-full lg:pointer-events-auto lg:mr-[calc(max(2rem,50%-35rem)+40rem)] lg:min-w-[32rem] lg:overflow-y-auto lg:overflow-x-hidden lg:pl-[max(4rem,calc(50%-44rem))]'>
|
<div className="relative flex w-full lg:pointer-events-auto lg:mr-[calc(max(2rem,50%-35rem)+40rem)] lg:min-w-[32rem] lg:overflow-y-auto lg:overflow-x-hidden lg:pl-[max(4rem,calc(50%-44rem))]">
|
||||||
<div className='mx-auto pr-20 max-w-lg lg:mx-auto lg:flex lg:max-w-4xl lg:flex-col lg:before:flex-1 lg:before:pt-6'>
|
<div className="mx-auto pr-20 max-w-lg lg:mx-auto lg:flex lg:max-w-4xl lg:flex-col lg:before:flex-1 lg:before:pt-6">
|
||||||
<div className='pb-16 pt-20 sm:pb-20 sm:pt-32 lg:py-20'>
|
<div className="pb-16 pt-20 sm:pb-20 sm:pt-32 lg:py-20">
|
||||||
<div className='relative '>
|
<div className="relative ">
|
||||||
<StarField className='-right-44 top-14' />
|
<StarField className="-right-44 top-14" />
|
||||||
{main}
|
{main}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-1 items-end justify-center pb-4 lg:justify-start lg:pb-6'>{footer}</div>
|
<div className="flex flex-1 items-end justify-center pb-4 lg:justify-start lg:pb-6">
|
||||||
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChangeLogLayout = ({ children }: { children: React.ReactNode }) => {
|
const ChangeLogLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<div className='h-full font-sans overflow-y-auto'>
|
<div className="h-full font-sans overflow-y-auto">
|
||||||
|
|
||||||
<FixedSidebar main={<Intro />} footer={<IntroFooter />} />
|
<FixedSidebar main={<Intro />} footer={<IntroFooter />} />
|
||||||
<div />
|
<div />
|
||||||
<div className='relative overflow-y-auto flex gap-2 justify-start items-start'>
|
<div className="relative overflow-y-auto flex gap-2 justify-start items-start">
|
||||||
<Timeline />
|
<Timeline />
|
||||||
<div className='space-y-20 py-20 w-[80%] mx-auto md:mx-0 md:ml-auto md:w-1/2 h-screen sm:space-y-20 sm:py-20'>{children}</div>
|
<div className="space-y-20 py-20 w-[80%] mx-auto md:mx-0 md:ml-auto md:w-1/2 h-screen sm:space-y-20 sm:py-20">
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ChangeLogLayout
|
export default ChangeLogLayout;
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import { AnimatePresence } from "@/components/ui/fade-in"
|
import { AnimatePresence } from "@/components/ui/fade-in";
|
||||||
import { Logs } from "./_logs"
|
import { Logs } from "./_logs";
|
||||||
import { FormattedDate } from "./_components/fmt-dates"
|
import { FormattedDate } from "./_components/fmt-dates";
|
||||||
const ChangelogPage = () => {
|
const ChangelogPage = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='mt-10 overflow-visible h-full flex flex-col gap-10'>
|
<div className="mt-10 overflow-visible h-full flex flex-col gap-10">
|
||||||
{Logs.map((log) => {
|
{Logs.map((log) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative my-5 h-auto">
|
<div className="relative my-5 h-auto">
|
||||||
<div className="sticky top-2 flex-1 h-full">
|
<div className="sticky top-2 flex-1 h-full">
|
||||||
<FormattedDate className="absolute md:-left-32 left-0 text-sm -top-8 md:top-0 font-light" date={log.date} />
|
<FormattedDate
|
||||||
|
className="absolute md:-left-32 left-0 text-sm -top-8 md:top-0 font-light"
|
||||||
|
date={log.date}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<log.component />
|
<log.component />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
export default ChangelogPage
|
export default ChangelogPage;
|
||||||
|
|||||||
@@ -10,20 +10,16 @@ import {
|
|||||||
Webhook,
|
Webhook,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import { LockClosedIcon } from "@radix-ui/react-icons";
|
||||||
LockClosedIcon,
|
|
||||||
} from "@radix-ui/react-icons";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { TechStackDisplay } from "./display-techstack";
|
import { TechStackDisplay } from "./display-techstack";
|
||||||
import { Ripple } from "./ripple";
|
import { Ripple } from "./ripple";
|
||||||
|
|
||||||
|
|
||||||
export default function Features() {
|
export default function Features() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="md:max-w-[1300px] mt-10 mx-auto font-geist relative md:border-l-0 md:border-[1.2px] rounded-none -pr-2">
|
<div className="md:w-10/12 mt-10 mx-auto font-geist relative md:border-l-0 md:border-[1.2px] rounded-none -pr-2">
|
||||||
<Plus className="absolute top-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
|
<Plus className="absolute top-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 md:mx-0 grid-rows-4 md:grid-rows-4 w-full">
|
<div className="grid grid-cols-1 md:grid-cols-3 md:mx-0 grid-rows-4 md:grid-rows-4 w-full">
|
||||||
<div className="relative items-start justify-start border-l-[1.2px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10 overflow-clip">
|
<div className="relative items-start justify-start border-l-[1.2px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10 overflow-clip">
|
||||||
@@ -31,7 +27,9 @@ export default function Features() {
|
|||||||
|
|
||||||
<div className="flex gap-2 items-center my-1">
|
<div className="flex gap-2 items-center my-1">
|
||||||
<PlugZap2Icon className="w-4 h-4" />
|
<PlugZap2Icon className="w-4 h-4" />
|
||||||
<p className="text-gray-600 dark:text-gray-400">Framework Agnostic </p>
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
Framework Agnostic{" "}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div className="max-w-full">
|
<div className="max-w-full">
|
||||||
@@ -42,12 +40,10 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
Supports your favorite frontend, backend and meta frameworks, including React, Vue, Svelte, Solid, Next.js, Nuxt.js, Hono, and more{" "}
|
Supports your favorite frontend, backend and meta frameworks,
|
||||||
<a
|
including React, Vue, Svelte, Solid, Next.js, Nuxt.js, Hono, and
|
||||||
className="text-gray-50"
|
more{" "}
|
||||||
href=".docs"
|
<a className="text-gray-50" href=".docs" target="_blank">
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -69,12 +65,9 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
Builtin support for email and password authentication, with secure password hashing and account management features{" "}
|
Builtin support for email and password authentication, with secure
|
||||||
<a
|
password hashing and account management features{" "}
|
||||||
className="text-gray-50"
|
<a className="text-gray-50" href="/docs" target="_blank">
|
||||||
href="/docs"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -96,12 +89,9 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
Allow users to sign in with their accounts, including Github, Google, Discord, Twitter, and more.{" "}
|
Allow users to sign in with their accounts, including Github,
|
||||||
<a
|
Google, Discord, Twitter, and more.{" "}
|
||||||
className="text-gray-50"
|
<a className="text-gray-50" href="#" target="_blank">
|
||||||
href="#"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -121,23 +111,20 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
With our built-in two factor authentication plugin, you can add an extra layer of security to your account.
|
With our built-in two factor authentication plugin, you can add an
|
||||||
{" "}
|
extra layer of security to your account.{" "}
|
||||||
<Link
|
<Link className="text-gray-50" href="/docs" target="_blank">
|
||||||
className="text-gray-50"
|
|
||||||
href="/docs"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more
|
Learn more
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="items-start justify-staart border-l-[1.2px] border-t-[1.2px] flex flex-col p-10 ">
|
<div className="items-start justify-staart border-l-[1.2px] border-t-[1.2px] flex flex-col p-10 ">
|
||||||
<div className="flex gap-2 items-center my-1">
|
<div className="flex gap-2 items-center my-1">
|
||||||
<RabbitIcon className="w-4 h-4" />
|
<RabbitIcon className="w-4 h-4" />
|
||||||
<p className="text-gray-600 dark:text-gray-400">Organization & Access Control </p>
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
Organization & Access Control{" "}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div className="max-w-full">
|
<div className="max-w-full">
|
||||||
@@ -148,12 +135,9 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
Manage users and their access to resources within your application.{" "}
|
Manage users and their access to resources within your
|
||||||
<a
|
application.{" "}
|
||||||
className="text-gray-50"
|
<a className="text-gray-50" href="/docs" target="_blank">
|
||||||
href="/docs"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -163,7 +147,9 @@ export default function Features() {
|
|||||||
<Plus className="absolute bottom-[-15px] right-[-15px] text-black/20 dark:text-white/40 w-8 h-8" />
|
<Plus className="absolute bottom-[-15px] right-[-15px] text-black/20 dark:text-white/40 w-8 h-8" />
|
||||||
<div className="flex gap-2 items-center my-1">
|
<div className="flex gap-2 items-center my-1">
|
||||||
<PlugIcon className="w-4 h-4" />
|
<PlugIcon className="w-4 h-4" />
|
||||||
<p className="text-gray-600 dark:text-gray-400">Plugin Ecosystem </p>
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
Plugin Ecosystem{" "}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-full">
|
<div className="max-w-full">
|
||||||
<div className="flex gap-3 ">
|
<div className="flex gap-3 ">
|
||||||
@@ -174,12 +160,9 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||||
Enhance your application with our official plugins and those created by the community.{" "}
|
Enhance your application with our official plugins and those
|
||||||
<a
|
created by the community.{" "}
|
||||||
className="text-gray-50"
|
<a className="text-gray-50" href="/docs" target="_blank">
|
||||||
href="/docs"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -197,9 +180,7 @@ export default function Features() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-4xl md:text-4xl mt-4 tracking-tighter font-normal max-w-md mx-auto text-center">
|
<p className="text-4xl md:text-4xl mt-4 tracking-tighter font-normal max-w-md mx-auto text-center">
|
||||||
<strong>
|
<strong>Roll your own auth with confidence in minutes!</strong>
|
||||||
Roll your own auth with confidence in minutes!
|
|
||||||
</strong>
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex mt-[10px] z-20 justify-center items-start">
|
<div className="flex mt-[10px] z-20 justify-center items-start">
|
||||||
<TechStackDisplay
|
<TechStackDisplay
|
||||||
@@ -210,7 +191,6 @@ export default function Features() {
|
|||||||
"solidStart",
|
"solidStart",
|
||||||
"react",
|
"react",
|
||||||
"hono",
|
"hono",
|
||||||
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,8 +202,6 @@ export default function Features() {
|
|||||||
<Ripple />
|
<Ripple />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function ArticleLayout() {
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
function getDefaultValue() {
|
function getDefaultValue() {
|
||||||
const defaultValue = contents.findIndex((item) =>
|
const defaultValue = contents.findIndex((item) =>
|
||||||
item.list.some((listItem) => listItem.href === pathname),
|
item.list.some((listItem) => listItem.href === pathname)
|
||||||
);
|
);
|
||||||
return defaultValue === -1 ? 0 : defaultValue;
|
return defaultValue === -1 ? 0 : defaultValue;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ export default function ArticleLayout() {
|
|||||||
<AccordionItem value={`item-${i}`} key={item.title}>
|
<AccordionItem value={`item-${i}`} key={item.title}>
|
||||||
<AccordionTrigger className="border-b border-lines px-5 py-2.5 text-left">
|
<AccordionTrigger className="border-b border-lines px-5 py-2.5 text-left">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{item.Icon && <item.Icon />}
|
{item.Icon && <item.Icon className="w-5 h-5" />}
|
||||||
{item.title}
|
{item.title}
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
AppWindow,
|
||||||
|
Clock,
|
||||||
Key,
|
Key,
|
||||||
LucideAArrowDown,
|
LucideAArrowDown,
|
||||||
LucideIcon,
|
LucideIcon,
|
||||||
@@ -348,6 +350,17 @@ export const contents: Content[] = [
|
|||||||
href: "/docs/plugins/1st-party-plugins",
|
href: "/docs/plugins/1st-party-plugins",
|
||||||
icon: LucideAArrowDown,
|
icon: LucideAArrowDown,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Two Factor",
|
||||||
|
icon: ScanFace,
|
||||||
|
href: "/docs/plugins/2fa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Username",
|
||||||
|
icon: UserSquare2,
|
||||||
|
href: "/docs/plugins/username",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Passkey",
|
title: "Passkey",
|
||||||
href: "/docs/plugins/passkey",
|
href: "/docs/plugins/passkey",
|
||||||
@@ -365,16 +378,6 @@ export const contents: Content[] = [
|
|||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Two Factor",
|
|
||||||
icon: ScanFace,
|
|
||||||
href: "/docs/plugins/2fa",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Username",
|
|
||||||
icon: UserSquare2,
|
|
||||||
href: "/docs/plugins/username",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Authorization",
|
title: "Authorization",
|
||||||
group: true,
|
group: true,
|
||||||
@@ -392,6 +395,11 @@ export const contents: Content[] = [
|
|||||||
href: "/docs/plugins/1st-party-plugins",
|
href: "/docs/plugins/1st-party-plugins",
|
||||||
icon: LucideAArrowDown,
|
icon: LucideAArrowDown,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Rate Limit",
|
||||||
|
icon: Clock,
|
||||||
|
href: "/docs/plugins/rate-limit",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Bearer",
|
title: "Bearer",
|
||||||
icon: Key,
|
icon: Key,
|
||||||
@@ -399,4 +407,15 @@ export const contents: Content[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Examples",
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
title: "Next JS",
|
||||||
|
href: "/docs/examples/next-js",
|
||||||
|
icon: Icons.nextJS,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Icon: AppWindow,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
20
docs/content/docs/examples/next-js.mdx
Normal file
20
docs/content/docs/examples/next-js.mdx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
title: Next js example
|
||||||
|
descirption: Better auth next js example
|
||||||
|
---
|
||||||
|
|
||||||
|
<iframe src="https://codesandbox.io/p/github/better-auth/better-auth/draft/dry-wind?workspaceId=f8670ba9-e253-4b60-91a6-369d03522afe&embed=1"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
width: "100%",
|
||||||
|
height: "500px",
|
||||||
|
border: 0,
|
||||||
|
borderRadius: "4px",
|
||||||
|
overflow: "hidden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="Bekacru/better-auth-demo/draft/awesome-chihiro"
|
||||||
|
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||||
|
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||||
|
module="/demo/nextjs"
|
||||||
|
></iframe>
|
||||||
73
docs/content/docs/integrations/hono.mdx
Normal file
73
docs/content/docs/integrations/hono.mdx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
title: Hono Integration
|
||||||
|
description: Hono Integration Guide
|
||||||
|
---
|
||||||
|
|
||||||
|
This integration guide is assuming you are using Hono with node server.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install Better Auth
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
npm install better-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set Environment Variables
|
||||||
|
|
||||||
|
Create a `.env` file in the root of your project and add the following environment variables:
|
||||||
|
|
||||||
|
**Set Base URL**
|
||||||
|
```txt title=".env"
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set Secret**
|
||||||
|
|
||||||
|
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
|
||||||
|
```txt title=".env"
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
```
|
||||||
|
<GenerateSecret/>
|
||||||
|
|
||||||
|
## Configure Server
|
||||||
|
|
||||||
|
### Create Better Auth instance
|
||||||
|
|
||||||
|
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
|
||||||
|
|
||||||
|
```ts twoslash title="auth.ts"
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite", //change this to your database provider
|
||||||
|
url: "./db.sqlite", // path to your database or connection string
|
||||||
|
}
|
||||||
|
// Refer to the api documentation for more configuration options
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="warn">
|
||||||
|
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Mount the handler
|
||||||
|
|
||||||
|
We need to mount the handler to hono endpoint.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { auth } from "./auth";
|
||||||
|
import { serve } from "@hono/node-server";
|
||||||
|
import { cors } from "hono/cors";
|
||||||
|
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
app.on(["POST", "GET"], "/api/auth/**", (c) => {
|
||||||
|
return auth.handler(c.req.raw);
|
||||||
|
});
|
||||||
|
|
||||||
|
serve(app);
|
||||||
|
```
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Nuxt.js
|
title: Nuxt.js Integration
|
||||||
description: Learn how to integrate Better Auth with Nuxt.js
|
description: Learn how to integrate Better Auth with Nuxt.js
|
||||||
---
|
---
|
||||||
|
|
||||||
64
docs/content/docs/integrations/solid-start.mdx
Normal file
64
docs/content/docs/integrations/solid-start.mdx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
title: Solid Start Integration
|
||||||
|
description: Solid Start integratons guide
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install Better Auth
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
npm install better-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set Environment Variables
|
||||||
|
|
||||||
|
Create a `.env` file in the root of your project and add the following environment variables:
|
||||||
|
|
||||||
|
**Set Base URL**
|
||||||
|
```txt title=".env"
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set Secret**
|
||||||
|
|
||||||
|
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
|
||||||
|
```txt title=".env"
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
```
|
||||||
|
|
||||||
|
<GenerateSecret/>
|
||||||
|
|
||||||
|
## Configure Server
|
||||||
|
|
||||||
|
### Create Better Auth instance
|
||||||
|
|
||||||
|
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
|
||||||
|
|
||||||
|
```ts twoslash title="auth.ts"
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite", //change this to your database provider
|
||||||
|
url: "./db.sqlite", // path to your database or connection string
|
||||||
|
}
|
||||||
|
// Refer to the api documentation for more configuration options
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="warn">
|
||||||
|
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Mount the handler
|
||||||
|
|
||||||
|
We need to mount the handler to Solid Start server. Put the following code in your `*auth.ts` file inside `/routes/api/auth` folder.
|
||||||
|
|
||||||
|
```ts title="*auth.ts"
|
||||||
|
import { auth } from "~/lib/auth";
|
||||||
|
import { toSolidStartHandler } from "better-auth/solid-start";
|
||||||
|
|
||||||
|
export const { GET, POST } = toSolidStartHandler(auth);
|
||||||
|
```
|
||||||
148
docs/content/docs/integrations/svelte-kit.mdx
Normal file
148
docs/content/docs/integrations/svelte-kit.mdx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
---
|
||||||
|
title: Svelte Kit Integration
|
||||||
|
description: Learn how to integrate Better Auth with Svelte Kit
|
||||||
|
---
|
||||||
|
|
||||||
|
Better Auth has first class support for Svelte Kit. It provides utilities to make it easier to use Better Auth with Svelte Kit.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
First, install Better Auth
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
npm install better-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set Environment Variables
|
||||||
|
|
||||||
|
Create a `.env` file in the root of your project and add the following environment variables:
|
||||||
|
|
||||||
|
**Set Base URL**
|
||||||
|
```txt title=".env"
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set Secret**
|
||||||
|
|
||||||
|
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
|
||||||
|
```txt title=".env"
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
```
|
||||||
|
|
||||||
|
<GenerateSecret/>
|
||||||
|
|
||||||
|
## Configure Server
|
||||||
|
|
||||||
|
### Create Better Auth instance
|
||||||
|
|
||||||
|
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
|
||||||
|
|
||||||
|
```ts twoslash title="auth.ts"
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite", //change this to your database provider
|
||||||
|
url: "./db.sqlite", // path to your database or connection string
|
||||||
|
}
|
||||||
|
// Refer to the api documentation for more configuration options
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="warn">
|
||||||
|
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Mount the handler
|
||||||
|
|
||||||
|
We need to mount the handler to svelte kit server hook.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { auth } from "$lib/auth";
|
||||||
|
import { svelteKitHandler } from "better-auth/svelte-kit";
|
||||||
|
|
||||||
|
export async function handle({ event, resolve }) {
|
||||||
|
return svelteKitHandler({ event, resolve, auth });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migrate the database
|
||||||
|
Run the following command to create the necessary tables in your database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx better-auth migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Create a client
|
||||||
|
|
||||||
|
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
|
||||||
|
|
||||||
|
```ts twoslash title="client.ts"
|
||||||
|
import { createAuthClient } from "better-auth/svelte" // make sure to import from better-auth/svlete
|
||||||
|
|
||||||
|
export const client = createAuthClient({
|
||||||
|
//you can pass client configuration here
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
|
||||||
|
Some of the actinos are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and refelct changes when there is a change like a user signing in or out affecting the session state.
|
||||||
|
|
||||||
|
### Example usage
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
import { client } from "$lib/client";
|
||||||
|
const session = client.$session;
|
||||||
|
</script>
|
||||||
|
<div>
|
||||||
|
{#if $session}
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{$session?.user.name}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
on:click={async () => {
|
||||||
|
await client.signOut();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Signout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
on:click={async () => {
|
||||||
|
await client.signIn.social({
|
||||||
|
provider: "github",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Continue with github
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Getting Session on a loader
|
||||||
|
|
||||||
|
```ts title="+page.ts"
|
||||||
|
import { auth } from "$lib/auth";
|
||||||
|
|
||||||
|
export async function load(request: Request) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: request.headers,
|
||||||
|
});
|
||||||
|
if (!session) {
|
||||||
|
return {
|
||||||
|
status: 401,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
error: "Unauthorized",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
```
|
||||||
387
docs/content/docs/plugins/2fa.mdx
Normal file
387
docs/content/docs/plugins/2fa.mdx
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
---
|
||||||
|
title: Two-Factor Authentication (2FA)
|
||||||
|
description: Enhance your app's security with two-factor authentication
|
||||||
|
---
|
||||||
|
|
||||||
|
`OTP` `TOTP` `Backup Codes` `Trusted Devices`
|
||||||
|
|
||||||
|
## What is Two-Factor Authentication?
|
||||||
|
|
||||||
|
Two-Factor Authentication (2FA) adds an extra security step when users log in. Instead of just using a password, they'll need to provide a second form of verification. This makes it much harder for unauthorized people to access accounts, even if they've somehow gotten the password.
|
||||||
|
|
||||||
|
This plugin offers two main methods of 2FA:
|
||||||
|
|
||||||
|
1. **OTP (One-Time Password)**: A temporary code sent to the user's email or phone.
|
||||||
|
2. **TOTP (Time-based One-Time Password)**: A code generated by an app on the user's device.
|
||||||
|
|
||||||
|
**Additional features include:**
|
||||||
|
- Generating backup codes for account recovery
|
||||||
|
- Enabling/disabling 2FA
|
||||||
|
- Managing trusted devices
|
||||||
|
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
To get started with Two-Factor Authentication, follow these steps:
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Add the plugin to your auth config:
|
||||||
|
|
||||||
|
```ts title="auth.ts"
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { twoFactor } from "better-auth/plugins"
|
||||||
|
|
||||||
|
export const auth = await betterAuth({
|
||||||
|
// ... other config options
|
||||||
|
plugins: [
|
||||||
|
twoFactor({
|
||||||
|
issuer: "my-app"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
|
||||||
|
### Migrate your database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx better-auth migrate
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Add the client plugin:
|
||||||
|
|
||||||
|
```ts title="client.ts"
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { twoFactorClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [
|
||||||
|
twoFactorClient({
|
||||||
|
twoFactorPage: "/two-factor" //redirect for two factor verification if enabled // [!code highlight]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Enabling 2FA
|
||||||
|
|
||||||
|
To enable two-factor authentication for a user:
|
||||||
|
|
||||||
|
```ts title="two-factor.ts"
|
||||||
|
const enableTwoFactor = async() => {
|
||||||
|
const { data, error } = await client.twoFactor.enable()
|
||||||
|
if (data) {
|
||||||
|
// 2FA has been enabled successfully
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Sign In with 2FA
|
||||||
|
|
||||||
|
When a user with 2FA enabled tries to sign in, you'll need to verify their 2FA code. If they have 2FA enabled, they'll be redirected to the `twoFactorPage` where they can enter their 2FA code.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const signin = async () => {
|
||||||
|
const { data, error } = await client.signIn.email({
|
||||||
|
email: "user@example.com",
|
||||||
|
password: "password123",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the user will be redirected to the `twoFactorPage` if they have 2FA enabled. If you want to handle the 2FA verification in place, you can use the `options` parameter.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const signin = async () => {
|
||||||
|
const { data, error } = await client.signIn.email({
|
||||||
|
email: "user@example.com",
|
||||||
|
password: "password123",
|
||||||
|
options: {
|
||||||
|
async onSuccess(context) {
|
||||||
|
if (context.data.twoFactorRedirect) {
|
||||||
|
// Handle the 2FA verification in place
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### TOTP
|
||||||
|
|
||||||
|
TOTP is a time-based one-time password algorithm that generates a code based on the current time. It's a more secure method than OTP because it takes into account the time it takes to generate the code.
|
||||||
|
|
||||||
|
#### Getting TOTP URI
|
||||||
|
|
||||||
|
After enabling 2FA, you can get the TOTP URI to display to the user.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, error } = await client.twoFactor.getTotpUri()
|
||||||
|
if (data) {
|
||||||
|
// Use data.totpURI to generate a QR code or display to the user
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Generating a QR Code
|
||||||
|
|
||||||
|
You can use a library like `qrcode` to generate a QR code from the TOTP URI.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { toCanvas } from "qrcode"
|
||||||
|
|
||||||
|
toCanvas(document.getElementById('canvas'), data.totpURI)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Verifying TOTP
|
||||||
|
|
||||||
|
After the user has entered their 2FA code, you can verify it
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const verifyTotp = async (code: string) => {
|
||||||
|
const { data, error } = await client.twoFactor.verifyTotp({ code })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### OTP
|
||||||
|
|
||||||
|
OTP is a one-time password algorithm that generates a code based on the current time. It's a more secure method than TOTP because it takes into account the time it takes to generate the code.
|
||||||
|
|
||||||
|
|
||||||
|
Before using OTP, you need to setup `sendOTP` function.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { twoFactor } from "better-auth/plugins"
|
||||||
|
|
||||||
|
export const auth = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
twoFactor({
|
||||||
|
otpOptions: {
|
||||||
|
async sendOTP(user, otp) {
|
||||||
|
console.log({ user, otp });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sending OTP
|
||||||
|
|
||||||
|
sending otp is done by calling `sendOtp` function. This functino will call your `sendOTP` function that you provide in the `otpOptions` with the otp code and the user.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, error } = await client.twoFactor.sendOtp()
|
||||||
|
if (data) {
|
||||||
|
// Show the OTP to the user
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Verifying OTP
|
||||||
|
|
||||||
|
After the user has entered their OTP code, you can verify it
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const verifyOtp = async (code: string) => {
|
||||||
|
const { data, error } = await client.twoFactor.verifyOtp({ code })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Codes
|
||||||
|
|
||||||
|
|
||||||
|
Backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.
|
||||||
|
|
||||||
|
#### Generating Backup Codes
|
||||||
|
Generate backup codes for account recovery:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, error } = await client.twoFactor.generateBackupCodes()
|
||||||
|
if (data) {
|
||||||
|
// Show the backup codes to the user
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Backup Codes
|
||||||
|
|
||||||
|
Backup codes can be used to recover access to the account if the user loses access to their phone or email.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, error } = await client.twoFactor.verifyBackupCode({code: ""})
|
||||||
|
if (data) {
|
||||||
|
// 2FA verified and account recovered
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trusted Devices
|
||||||
|
|
||||||
|
You can mark a device as trusted by passing `trustDevice` to `verifyTotp` or `verifyOtp`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const verify2FA = async (code: string) => {
|
||||||
|
const { data, error } = await client.twoFactor.verifyTotp({
|
||||||
|
code,
|
||||||
|
callbackURL: "/dashboard",
|
||||||
|
trustDevice: true // Mark this device as trusted
|
||||||
|
})
|
||||||
|
if (data) {
|
||||||
|
// 2FA verified and device trusted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `trustDevice` is set to `true`, the current device will be remembered for 60 days. During this period, the user won't be prompted for 2FA on subsequent sign-ins from this device. The trust period is refreshed each time the user signs in successfully.
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
Trusted devices enhance user convenience but should be used carefully, as they slightly reduce security. Encourage users to only trust personal, secure devices.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
To configure the two factor plugin, you need to add the following code to your better auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { twoFactor } from "better-auth/plugins"
|
||||||
|
|
||||||
|
export const auth = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
twoFactor({ // [!code highlight]
|
||||||
|
issuer: "my-app" // [!code highlight]
|
||||||
|
}) // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Issuer**: The issuer is the name of your application. It's used to generate totp codes. It'll be displayed in the authenticator apps.
|
||||||
|
|
||||||
|
**TOTP options**
|
||||||
|
|
||||||
|
these are options for TOTP.
|
||||||
|
|
||||||
|
<TypeTable
|
||||||
|
type={{
|
||||||
|
digits:{
|
||||||
|
description: 'The number of digits the otp to be',
|
||||||
|
type: 'number',
|
||||||
|
default: 6,
|
||||||
|
},
|
||||||
|
period: {
|
||||||
|
description: 'The period for otp in seconds.',
|
||||||
|
type: 'number',
|
||||||
|
default: 30,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
**OTP options**
|
||||||
|
|
||||||
|
these are options for OTP.
|
||||||
|
|
||||||
|
<TypeTable
|
||||||
|
type={{
|
||||||
|
sendOTP: {
|
||||||
|
description: "a function that sends the otp to the user's email or phone number. It takes two parameters: user and otp",
|
||||||
|
type: "function",
|
||||||
|
},
|
||||||
|
period: {
|
||||||
|
description: 'The period for otp in seconds.',
|
||||||
|
type: 'number',
|
||||||
|
default: 30,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
**Backup Code Options**
|
||||||
|
|
||||||
|
backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.
|
||||||
|
|
||||||
|
<TypeTable
|
||||||
|
type={{
|
||||||
|
amount: {
|
||||||
|
description: "The amount of backup codes to generate",
|
||||||
|
type: "number",
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
description: "The length of the backup codes",
|
||||||
|
type: "number",
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
customBackupCodesGenerate: {
|
||||||
|
description: "A function that generates custom backup codes. It takes no parameters and returns an array of strings.",
|
||||||
|
type: "function",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
To use the two factor plugin in the client, you need to add it on your plugins list.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { twoFactorClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [
|
||||||
|
twoFactorClient({ // [!code highlight]
|
||||||
|
twoFactorPage: "/two-factor" // [!code highlight]
|
||||||
|
}) // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**
|
||||||
|
|
||||||
|
`twoFactorPage`: The page to redirect the user to after they have enabled 2-Factor. This is the page where the user will be redirected to verify their 2-Factor code.
|
||||||
|
|
||||||
|
`redirect`: If set to `false`, the user will not be redirected to the `twoFactorPage` after they have enabled 2-Factor.
|
||||||
|
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
Two factores requires additional fields on the user table. If you use better auth's migration system, it will automatically create this table for you.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const schema = {
|
||||||
|
user: {
|
||||||
|
fields: {
|
||||||
|
twoFactorEnabled: {
|
||||||
|
type: "boolean",
|
||||||
|
required: false,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
twoFactorSecret: {
|
||||||
|
type: "string",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
twoFactorBackupCodes: {
|
||||||
|
type: "string",
|
||||||
|
required: false,
|
||||||
|
returned: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
4
docs/content/docs/plugins/organization.mdx
Normal file
4
docs/content/docs/plugins/organization.mdx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: Organization
|
||||||
|
descirption: The organization plugin allows you to manage your organization's members and teams.
|
||||||
|
---
|
||||||
158
docs/content/docs/plugins/passkey.mdx
Normal file
158
docs/content/docs/plugins/passkey.mdx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
title: Passkey
|
||||||
|
description: Passkey
|
||||||
|
---
|
||||||
|
|
||||||
|
Passkeys are a secure, passwordless authentication method using cryptographic key pairs, supported by WebAuthn and FIDO2 standards in web browsers. They replace passwords with unique key pairs: a private key stored on the user’s device and a public key shared with the website. Users can log in using biometrics, PINs, or security keys, providing strong, phishing-resistant authentication without traditional passwords.
|
||||||
|
|
||||||
|
The passkey plugin implementation is powered by [simple-web-authn](https://simplewebauthn.dev/) behind the scenes.
|
||||||
|
|
||||||
|
## Quick setup
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Add the plugin to your auth config
|
||||||
|
To add the passkey plugin to your auth config, you need to import the plugin and pass it to the `plugins` option of the auth instance.
|
||||||
|
|
||||||
|
**Options**
|
||||||
|
|
||||||
|
`rpID`: A unique identifier for your website. 'localhost' is okay for local dev
|
||||||
|
|
||||||
|
`rpName`: Human-readable title for your website
|
||||||
|
|
||||||
|
`origin`: The URL at which registrations and authentications should occur. 'http://localhost' and 'http://localhost:PORT' are also valid.Do NOT include any trailing /
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { passkey } from "better-auth/plugins"
|
||||||
|
|
||||||
|
export const auth = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
passkey({ // [!code highlight]
|
||||||
|
rpID: "localhost", // [!code highlight]
|
||||||
|
rpName: "BetterAuth", // [!code highlight]
|
||||||
|
origin: "http://localhost:3000", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Add the client plugin
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { passkeyClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
passkeyClient() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Add a passkey
|
||||||
|
|
||||||
|
To add a passkey make sure a user is authenticated and then call the `passkey.addPasskey` function provided by the client.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { passkeyClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
passkeyClient() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
// ---cut---
|
||||||
|
const data = await client.passkey.addPasskey()
|
||||||
|
```
|
||||||
|
This will prompt the user to register a passkey. And it'll add the passkey to the user's account.
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Signin with a passkey
|
||||||
|
|
||||||
|
To signin with a passkey you can use the passkeySignIn method. This will prompt the user to sign in with their passkey.
|
||||||
|
|
||||||
|
Signin method accepts:
|
||||||
|
|
||||||
|
`autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui)
|
||||||
|
|
||||||
|
`callbackURL`: The URL to redirect to after the user has signed in. (optional)
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { passkeyClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
passkeyClient() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
// ---cut---
|
||||||
|
const data = await client.signIn.passkey()
|
||||||
|
```
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
|
||||||
|
## Passkey Configuration
|
||||||
|
|
||||||
|
**rpID**: A unique identifier for your website. 'localhost' is okay for local dev.
|
||||||
|
|
||||||
|
**rpName**: Human-readable title for your website.
|
||||||
|
|
||||||
|
**origin**: The URL at which registrations and authentications should occur. 'http://localhost' and 'http://localhost:PORT' are also valid. Do NOT include any trailing /.
|
||||||
|
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
Passkey requires a database table called `passkey` with the following fields. If you use better auth's migration system, it will automatically create this table for you.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const schema = {
|
||||||
|
passkey: {
|
||||||
|
fields: {
|
||||||
|
publicKey: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: "string",
|
||||||
|
references: {
|
||||||
|
model: "user",
|
||||||
|
field: "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
webauthnUserID: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
counter: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
deviceType: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
backedUp: {
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
transports: {
|
||||||
|
type: "string",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: "date",
|
||||||
|
defaultValue: new Date(),
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
4
docs/content/docs/plugins/rate-limit.mdx
Normal file
4
docs/content/docs/plugins/rate-limit.mdx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: Rate Limit
|
||||||
|
description: The rate limit plugin allows you to limit the number of requests a user can make to your auth server.
|
||||||
|
---
|
||||||
119
docs/content/docs/plugins/username.mdx
Normal file
119
docs/content/docs/plugins/username.mdx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
title: Username
|
||||||
|
description: Username plugin
|
||||||
|
---
|
||||||
|
|
||||||
|
The username plugin wraps the email and password authenticator and adds username support. This allows users to sign in and sign up with their username instead of their email.
|
||||||
|
|
||||||
|
## Qiuck setup
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Add Plugin to the server
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { username } from "better-auth/plugins"
|
||||||
|
|
||||||
|
const auth = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
username() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Add the client plugin
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { usernameClient } from "better-auth/client/plugins"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
usernameClient() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signup with username
|
||||||
|
|
||||||
|
To signup a user with username, you can use the `signUp.username` function provided by the client. The `signUp` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `username`: The username of the user.
|
||||||
|
- `email`: The email address of the user.
|
||||||
|
- `password`: The password of the user. It should be at least 8 characters long and max 32 by default.
|
||||||
|
- `name`: The name of the user.
|
||||||
|
- `image`: The image of the user. (optional)
|
||||||
|
- `callbackURL`: The url to redirect to after the user has signed up. (optional)
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { usernameClient } from "better-auth/client/plugins"
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
usernameClient() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
// ---cut---
|
||||||
|
|
||||||
|
const data = await client.signUp.username({
|
||||||
|
username: "test",
|
||||||
|
email: "test@email.com",
|
||||||
|
password: "password1234",
|
||||||
|
name: "test",
|
||||||
|
image: "https://example.com/image.png",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with username
|
||||||
|
|
||||||
|
To signin a user with username, you can use the `signIn.username` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `username`: The username of the user.
|
||||||
|
- `password`: The password of the user.
|
||||||
|
- `callbackURL`: The url to redirect to after the user has signed in. (optional)
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
import { usernameClient } from "better-auth/client/plugins"
|
||||||
|
const client = createAuthClient({
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
usernameClient() // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
// ---cut---
|
||||||
|
|
||||||
|
const data = await client.signIn.username({
|
||||||
|
username: "test",
|
||||||
|
password: "password1234",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The username plugin doesn't require any configuration. It just needs to be added to the server and client.
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
The username plugin requires a `username` field in the user table. If you're using better auth migration tool it will automatically add the `username` field to the user table. If not you can add it manually.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const shcmea = {
|
||||||
|
user: {
|
||||||
|
username: {
|
||||||
|
type: "string",
|
||||||
|
unique: true,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -31,5 +31,6 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
|
|||||||
AnimatePresence,
|
AnimatePresence,
|
||||||
TypeTable,
|
TypeTable,
|
||||||
Features,
|
Features,
|
||||||
|
iframe: (props) => <iframe {...props} className="w-full h-[500px]" />,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
34844
pnpm-lock.yaml
generated
34844
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
52
rough-doc.md
52
rough-doc.md
@@ -1,52 +0,0 @@
|
|||||||
|
|
||||||
Basic Concepts
|
|
||||||
|
|
||||||
|
|
||||||
## Auth Server
|
|
||||||
|
|
||||||
the auth instance you create with `betterAuth` has 2 important properties:
|
|
||||||
|
|
||||||
- `auth.handler`
|
|
||||||
- `auth.api`
|
|
||||||
|
|
||||||
the `auth.handler` is a web standard handler that you mount on your server that handler api requests.
|
|
||||||
|
|
||||||
`auth.api` is a list of methods that you can call directly on the server, to interact with the auth server. Like `getSession` to get the current session. Plugins may add additional methods to the api.
|
|
||||||
|
|
||||||
**Example: Getting the current session on the server**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
/**
|
|
||||||
* Consider this as a random route endpoint that we want to protect
|
|
||||||
*/
|
|
||||||
export const POST = (request: Request)=> {
|
|
||||||
const session = await auth.api.getSession({
|
|
||||||
headers: request.headers // get session requires the headers to be passed
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Client
|
|
||||||
|
|
||||||
The client side of the library lets you interact with the auth server and includes built-in state management for specific methods, such as useSession.
|
|
||||||
|
|
||||||
You can import the client and use it to call these methods directly or export each method individually from the client.
|
|
||||||
|
|
||||||
If you add new plugins, they may also introduce their own methods. For instance, using the twoFactor plugin will add methods like twoFactor.enable. Check out the example below to see how to use the client:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const client = createAuthClient()
|
|
||||||
|
|
||||||
export const { signIn, signUp, signOut, useSession } = client
|
|
||||||
|
|
||||||
export function SignUp(){
|
|
||||||
async function handleSubmit(data){
|
|
||||||
await signUp.email({
|
|
||||||
name: data.name,
|
|
||||||
email: data.email,
|
|
||||||
password: data.password,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
//...your component
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user