mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
docs: concepts
This commit is contained in:
@@ -1,97 +1,117 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function MaterialSymbolsLightContactMailOutlineRounded(
|
||||
props?: SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M15 11.192h5q.348 0 .578-.23t.23-.577v-3q0-.348-.23-.578T20 6.577h-5q-.348 0-.578.23t-.23.577v3q0 .349.23.578q.23.23.578.23m2.5-2.019l1.889-1.32q.18-.13.377-.018q.196.113.196.324q0 .064-.156.293l-1.847 1.282q-.217.143-.459.143t-.46-.143l-1.846-1.282q-.044-.044-.155-.293q0-.211.196-.324q.196-.112.377.019zM2.615 19.615q-.69 0-1.153-.462T1 18V6q0-.69.463-1.153t1.152-.463h18.77q.69 0 1.153.463T23 6v12q0 .69-.462 1.153t-1.153.463zm12.17-1h6.6q.23 0 .423-.192T22 18V6q0-.23-.192-.423q-.193-.192-.424-.192H2.616q-.231 0-.424.192T2 6v12q0 .23.192.423t.423.193h.6q1.05-1.356 2.554-2.178T9 15.616t3.23.822t2.555 2.178M9 14.23q1.039 0 1.77-.731t.73-1.77t-.73-1.768T9 9.23t-1.77.73t-.73 1.77t.73 1.769t1.77.73m-4.45 4.386h8.9q-.87-.95-2.022-1.475q-1.153-.525-2.428-.525t-2.425.525t-2.025 1.475M9 13.23q-.617 0-1.059-.441q-.441-.442-.441-1.06t.441-1.058T9 10.231t1.059.441t.441 1.059t-.441 1.059q-.442.44-1.059.44M12 12"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export const Icons = {
|
||||
nextJS: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m4-14h-1.35v4H16zM9.346 9.71l6.059 7.828l1.054-.809L9.683 8H8v7.997h1.346z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
nuxt: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M200.662 81.35L0 430.65h130.774l139.945-239.803zm134.256 40.313l-39.023 69.167l138.703 239.82H512zm-51.596 91.052L155.924 430.651h253.485z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
svelteKit: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 426 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M403.508 229.23C491.235 87.7 315.378-58.105 190.392 23.555L71.528 99.337c-57.559 37.487-82.55 109.513-47.45 183.53c-87.761 133.132 83.005 289.03 213.116 205.762l118.864-75.782c64.673-42.583 79.512-116.018 47.45-183.616m-297.592-80.886l118.69-75.739c77.973-46.679 167.756 34.942 135.388 110.992c-19.225-15.274-40.65-24.665-56.923-28.894c6.186-24.57-22.335-42.796-42.174-30.106l-118.95 75.48c-29.411 20.328 1.946 62.138 31.014 44.596l45.33-28.895c101.725-57.403 198 80.425 103.38 147.975l-118.692 75.739C131.455 485.225 34.11 411.96 67.592 328.5c17.786 13.463 36.677 23.363 56.923 28.894c-4.47 28.222 24.006 41.943 42.476 30.365L285.64 312.02c29.28-21.955-2.149-61.692-30.97-44.595l-45.504 28.894c-100.56 58.77-199.076-80.42-103.25-147.975"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
solidStart: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M61.832 4.744c-3.205.058-6.37.395-9.45 1.07l-2.402.803c-4.806 1.603-8.813 4.005-11.216 7.21l-1.602 2.404l-12.017 20.828l.166.031c-4.785 5.823-5.007 14.07-.166 21.6c1.804 2.345 4.073 4.431 6.634 6.234l-15.445 4.982L.311 97.946s42.46 32.044 75.306 24.033l2.403-.801c5.322-1.565 9.292-4.48 11.683-8.068l.334.056l16.022-28.84c3.204-5.608 2.404-12.016-1.602-18.425a36 36 0 0 0-7.059-6.643l15.872-5.375l14.42-24.033S92.817 4.19 61.831 4.744z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
react: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 15 15"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M5.315 1.837c-.4-.116-.695-.085-.91.032c-.216.116-.404.347-.526.745c-.122.401-.163.936-.104 1.582q.015.157.037.321a14 14 0 0 1 1.676-.311a13 13 0 0 1 1.275-1.54l-.066-.053c-.508-.402-.98-.66-1.382-.776m2.185.14q-.09-.076-.182-.148C6.746 1.377 6.16 1.04 5.594.876C5.024.711 4.441.711 3.928.99s-.833.767-1.005 1.334c-.172.564-.21 1.238-.144 1.965q.023.255.065.523q-.256.09-.49.192c-.671.287-1.246.642-1.66 1.062C.278 6.487 0 7 0 7.584S.278 8.68.694 9.103c.414.42.989.774 1.66 1.062q.235.1.49.192a9 9 0 0 0-.065.523c-.066.726-.028 1.4.144 1.965c.172.567.492 1.056 1.005 1.333c.513.278 1.097.279 1.666.114c.566-.165 1.152-.5 1.724-.953l.182-.149q.09.076.182.149c.572.452 1.158.788 1.724.953c.569.165 1.153.164 1.666-.114c.513-.277.833-.766 1.005-1.333c.172-.564.21-1.239.144-1.965a9 9 0 0 0-.065-.523q.255-.09.49-.192c.671-.288 1.246-.643 1.66-1.062c.416-.422.694-.936.694-1.52c0-.582-.278-1.096-.694-1.518c-.414-.42-.989-.775-1.66-1.062a9 9 0 0 0-.49-.192q.04-.268.065-.523c.066-.727.028-1.4-.144-1.965c-.172-.567-.492-1.056-1.005-1.334S9.975.711 9.406.876c-.566.164-1.152.5-1.724.953zm0 1.365q-.338.346-.672.755a17 17 0 0 1 1.344 0a11 11 0 0 0-.672-.755m2.012.864c-.41-.574-.84-1.092-1.275-1.54l.065-.053c.51-.402.98-.66 1.383-.776c.399-.116.695-.085.91.032c.216.116.404.347.525.745c.122.401.164.936.105 1.582q-.015.158-.037.32a14 14 0 0 0-1.676-.31m-.563.944a15.6 15.6 0 0 0-2.898 0A15.6 15.6 0 0 0 4.72 7.584a15.7 15.7 0 0 0 1.33 2.433a15.6 15.6 0 0 0 2.9 0a15.6 15.6 0 0 0 1.33-2.433A15.7 15.7 0 0 0 8.95 5.15m1.824 1.138a17 17 0 0 0-.527-.956q.39.075.752.168q-.094.385-.225.788m0 2.591a17 17 0 0 1-.527.957q.39-.075.752-.169a12 12 0 0 0-.225-.788m1.18.487a14 14 0 0 0-.588-1.782c.246-.61.443-1.209.588-1.782q.154.058.3.12c.596.256 1.047.547 1.341.845c.292.296.406.572.406.817s-.114.52-.406.816c-.294.299-.745.59-1.341.846a8 8 0 0 1-.3.12m-.765 1.285a14 14 0 0 1-1.676.311c-.41.574-.84 1.091-1.275 1.54l.066.052c.508.403.98.66 1.382.777c.399.116.695.085.91-.032s.404-.348.525-.746c.123-.4.164-.936.105-1.582a7 7 0 0 0-.037-.32M7.5 11.826q.338-.346.672-.755a17 17 0 0 1-1.344 0q.334.408.672.755m-2.746-1.99a17 17 0 0 1-.527-.957q-.13.404-.225.788q.361.094.752.169m-.942.815a14 14 0 0 0 1.676.311c.41.574.839 1.091 1.275 1.54l-.066.052c-.508.403-.98.66-1.382.777c-.4.116-.695.085-.911-.032s-.403-.348-.525-.746c-.122-.4-.163-.936-.104-1.582a8 8 0 0 1 .037-.32m-.765-1.285c.145-.574.341-1.172.588-1.782a14 14 0 0 1-.588-1.782q-.155.058-.3.12c-.596.256-1.047.547-1.341.845c-.292.296-.406.572-.406.817s.114.52.406.816c.294.299.745.59 1.341.846q.146.061.3.12m.955-3.865q.094.384.225.787a17 17 0 0 1 .527-.956q-.39.075-.752.169M6 7.584a1.5 1.5 0 1 1 3 0a1.5 1.5 0 0 1-3 0m1.5-.5a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
hono: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={cn(props?.className)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 256 330"
|
||||
>
|
||||
<path
|
||||
fill="#9E9494"
|
||||
className="dark:fill-white"
|
||||
d="M134.129.029q1.315-.17 2.319.662a1256 1256 0 0 1 69.573 93.427q24.141 36.346 41.082 76.862q27.055 72.162-28.16 125.564q-48.313 40.83-111.318 31.805q-75.312-15.355-102.373-87.133Q-1.796 217.85.614 193.51q4.014-41.896 19.878-80.838q6.61-15.888 17.228-29.154a382 382 0 0 1 16.565 21.203q3.66 3.825 7.62 7.289Q92.138 52.013 134.13.029"
|
||||
opacity=".993"
|
||||
></path>
|
||||
<path
|
||||
fill="#3C2020"
|
||||
d="M129.49 53.7q36.47 42.3 65.93 90.114a187.3 187.3 0 0 1 15.24 33.13q12.507 49.206-26.836 81.169q-38.05 26.774-83.488 15.902q-48.999-15.205-56.653-65.929q-1.857-15.993 3.314-31.142a225.4 225.4 0 0 1 17.89-35.78l19.878-29.155a5510 5510 0 0 0 44.726-58.31"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
nextJS: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m4-14h-1.35v4H16zM9.346 9.71l6.059 7.828l1.054-.809L9.683 8H8v7.997h1.346z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
nuxt: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M200.662 81.35L0 430.65h130.774l139.945-239.803zm134.256 40.313l-39.023 69.167l138.703 239.82H512zm-51.596 91.052L155.924 430.651h253.485z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
svelteKit: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 426 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M403.508 229.23C491.235 87.7 315.378-58.105 190.392 23.555L71.528 99.337c-57.559 37.487-82.55 109.513-47.45 183.53c-87.761 133.132 83.005 289.03 213.116 205.762l118.864-75.782c64.673-42.583 79.512-116.018 47.45-183.616m-297.592-80.886l118.69-75.739c77.973-46.679 167.756 34.942 135.388 110.992c-19.225-15.274-40.65-24.665-56.923-28.894c6.186-24.57-22.335-42.796-42.174-30.106l-118.95 75.48c-29.411 20.328 1.946 62.138 31.014 44.596l45.33-28.895c101.725-57.403 198 80.425 103.38 147.975l-118.692 75.739C131.455 485.225 34.11 411.96 67.592 328.5c17.786 13.463 36.677 23.363 56.923 28.894c-4.47 28.222 24.006 41.943 42.476 30.365L285.64 312.02c29.28-21.955-2.149-61.692-30.97-44.595l-45.504 28.894c-100.56 58.77-199.076-80.42-103.25-147.975"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
solidStart: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M61.832 4.744c-3.205.058-6.37.395-9.45 1.07l-2.402.803c-4.806 1.603-8.813 4.005-11.216 7.21l-1.602 2.404l-12.017 20.828l.166.031c-4.785 5.823-5.007 14.07-.166 21.6c1.804 2.345 4.073 4.431 6.634 6.234l-15.445 4.982L.311 97.946s42.46 32.044 75.306 24.033l2.403-.801c5.322-1.565 9.292-4.48 11.683-8.068l.334.056l16.022-28.84c3.204-5.608 2.404-12.016-1.602-18.425a36 36 0 0 0-7.059-6.643l15.872-5.375l14.42-24.033S92.817 4.19 61.831 4.744z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
react: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={props?.className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 15 15"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M5.315 1.837c-.4-.116-.695-.085-.91.032c-.216.116-.404.347-.526.745c-.122.401-.163.936-.104 1.582q.015.157.037.321a14 14 0 0 1 1.676-.311a13 13 0 0 1 1.275-1.54l-.066-.053c-.508-.402-.98-.66-1.382-.776m2.185.14q-.09-.076-.182-.148C6.746 1.377 6.16 1.04 5.594.876C5.024.711 4.441.711 3.928.99s-.833.767-1.005 1.334c-.172.564-.21 1.238-.144 1.965q.023.255.065.523q-.256.09-.49.192c-.671.287-1.246.642-1.66 1.062C.278 6.487 0 7 0 7.584S.278 8.68.694 9.103c.414.42.989.774 1.66 1.062q.235.1.49.192a9 9 0 0 0-.065.523c-.066.726-.028 1.4.144 1.965c.172.567.492 1.056 1.005 1.333c.513.278 1.097.279 1.666.114c.566-.165 1.152-.5 1.724-.953l.182-.149q.09.076.182.149c.572.452 1.158.788 1.724.953c.569.165 1.153.164 1.666-.114c.513-.277.833-.766 1.005-1.333c.172-.564.21-1.239.144-1.965a9 9 0 0 0-.065-.523q.255-.09.49-.192c.671-.288 1.246-.643 1.66-1.062c.416-.422.694-.936.694-1.52c0-.582-.278-1.096-.694-1.518c-.414-.42-.989-.775-1.66-1.062a9 9 0 0 0-.49-.192q.04-.268.065-.523c.066-.727.028-1.4-.144-1.965c-.172-.567-.492-1.056-1.005-1.334S9.975.711 9.406.876c-.566.164-1.152.5-1.724.953zm0 1.365q-.338.346-.672.755a17 17 0 0 1 1.344 0a11 11 0 0 0-.672-.755m2.012.864c-.41-.574-.84-1.092-1.275-1.54l.065-.053c.51-.402.98-.66 1.383-.776c.399-.116.695-.085.91.032c.216.116.404.347.525.745c.122.401.164.936.105 1.582q-.015.158-.037.32a14 14 0 0 0-1.676-.31m-.563.944a15.6 15.6 0 0 0-2.898 0A15.6 15.6 0 0 0 4.72 7.584a15.7 15.7 0 0 0 1.33 2.433a15.6 15.6 0 0 0 2.9 0a15.6 15.6 0 0 0 1.33-2.433A15.7 15.7 0 0 0 8.95 5.15m1.824 1.138a17 17 0 0 0-.527-.956q.39.075.752.168q-.094.385-.225.788m0 2.591a17 17 0 0 1-.527.957q.39-.075.752-.169a12 12 0 0 0-.225-.788m1.18.487a14 14 0 0 0-.588-1.782c.246-.61.443-1.209.588-1.782q.154.058.3.12c.596.256 1.047.547 1.341.845c.292.296.406.572.406.817s-.114.52-.406.816c-.294.299-.745.59-1.341.846a8 8 0 0 1-.3.12m-.765 1.285a14 14 0 0 1-1.676.311c-.41.574-.84 1.091-1.275 1.54l.066.052c.508.403.98.66 1.382.777c.399.116.695.085.91-.032s.404-.348.525-.746c.123-.4.164-.936.105-1.582a7 7 0 0 0-.037-.32M7.5 11.826q.338-.346.672-.755a17 17 0 0 1-1.344 0q.334.408.672.755m-2.746-1.99a17 17 0 0 1-.527-.957q-.13.404-.225.788q.361.094.752.169m-.942.815a14 14 0 0 0 1.676.311c.41.574.839 1.091 1.275 1.54l-.066.052c-.508.403-.98.66-1.382.777c-.4.116-.695.085-.911-.032s-.403-.348-.525-.746c-.122-.4-.163-.936-.104-1.582a8 8 0 0 1 .037-.32m-.765-1.285c.145-.574.341-1.172.588-1.782a14 14 0 0 1-.588-1.782q-.155.058-.3.12c-.596.256-1.047.547-1.341.845c-.292.296-.406.572-.406.817s.114.52.406.816c.294.299.745.59 1.341.846q.146.061.3.12m.955-3.865q.094.384.225.787a17 17 0 0 1 .527-.956q-.39.075-.752.169M6 7.584a1.5 1.5 0 1 1 3 0a1.5 1.5 0 0 1-3 0m1.5-.5a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
hono: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={cn(props?.className)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 256 330"
|
||||
>
|
||||
<path
|
||||
fill="#9E9494"
|
||||
className="dark:fill-white"
|
||||
d="M134.129.029q1.315-.17 2.319.662a1256 1256 0 0 1 69.573 93.427q24.141 36.346 41.082 76.862q27.055 72.162-28.16 125.564q-48.313 40.83-111.318 31.805q-75.312-15.355-102.373-87.133Q-1.796 217.85.614 193.51q4.014-41.896 19.878-80.838q6.61-15.888 17.228-29.154a382 382 0 0 1 16.565 21.203q3.66 3.825 7.62 7.289Q92.138 52.013 134.13.029"
|
||||
opacity=".993"
|
||||
></path>
|
||||
<path
|
||||
fill="#3C2020"
|
||||
d="M129.49 53.7q36.47 42.3 65.93 90.114a187.3 187.3 0 0 1 15.24 33.13q12.507 49.206-26.836 81.169q-38.05 26.774-83.488 15.902q-48.999-15.205-56.653-65.929q-1.857-15.993 3.314-31.142a225.4 225.4 0 0 1 17.89-35.78l19.878-29.155a5510 5510 0 0 0 44.726-58.31"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
MailOutlineRounded: MaterialSymbolsLightContactMailOutlineRounded,
|
||||
};
|
||||
|
||||
@@ -199,7 +199,10 @@ export default function Hero() {
|
||||
code={code}
|
||||
language={"javascript"}
|
||||
theme={{
|
||||
...themes.synthwave84,
|
||||
...(theme.resolvedTheme === "dark"
|
||||
? themes.synthwave84
|
||||
: themes.oneLight),
|
||||
|
||||
plain: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Key,
|
||||
LucideAArrowDown,
|
||||
LucideIcon,
|
||||
Mailbox,
|
||||
PlusCircle,
|
||||
ScanFace,
|
||||
Users2,
|
||||
@@ -99,6 +100,62 @@ export const contents: Content[] = [
|
||||
{
|
||||
title: "Concepts",
|
||||
list: [
|
||||
{
|
||||
title: "Account Linking",
|
||||
href: "/docs/concepts/account-linking",
|
||||
icon: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
className="fill-foreground"
|
||||
d="M21.4 7.5c.8.8.8 2.1 0 2.8l-2.8 2.8l-7.8-7.8l2.8-2.8c.8-.8 2.1-.8 2.8 0l1.8 1.8l3-3l1.4 1.4l-3 3zm-5.8 5.8l-1.4-1.4l-2.8 2.8l-2.1-2.1l2.8-2.8l-1.4-1.4l-2.8 2.8l-1.5-1.4l-2.8 2.8c-.8.8-.8 2.1 0 2.8l1.8 1.8l-4 4l1.4 1.4l4-4l1.8 1.8c.8.8 2.1.8 2.8 0l2.8-2.8l-1.4-1.4z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: "/docs/concepts/api",
|
||||
title: "API",
|
||||
icon: () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
className="fill-foreground"
|
||||
fillRule="evenodd"
|
||||
d="M2.6 13.25a1.35 1.35 0 0 0-1.35 1.35v6.8c0 .746.604 1.35 1.35 1.35h18.8a1.35 1.35 0 0 0 1.35-1.35v-6.8a1.35 1.35 0 0 0-1.35-1.35zm3.967 5.25a.75.75 0 0 0-1.114-1.003l-.01.011a.75.75 0 0 0 1.114 1.004zM2.6 1.25A1.35 1.35 0 0 0 1.25 2.6v6.8c0 .746.604 1.35 1.35 1.35h18.8a1.35 1.35 0 0 0 1.35-1.35V2.6a1.35 1.35 0 0 0-1.35-1.35zM6.567 6.5a.75.75 0 0 0-1.114-1.003l-.01.011a.75.75 0 1 0 1.114 1.004z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Client",
|
||||
href: "/docs/concepts/client",
|
||||
icon: () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
className="stroke-foreground"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 17h4v4H3zm7 0h4v4h-4zm7 0h4v4h-4zm0-7h4v4h-4zm0-7h4v4h-4zm-7 7h4v4h-4zm0-7h4v4h-4zM3 3h4v4H3z"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Database",
|
||||
icon: (props?: SVGProps<any>) => (
|
||||
@@ -115,7 +172,29 @@ export const contents: Content[] = [
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
href: "/docs/database",
|
||||
href: "/docs/concepts/database",
|
||||
},
|
||||
|
||||
{
|
||||
href: "/docs/concepts/plugins",
|
||||
title: "Plugins",
|
||||
icon: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none">
|
||||
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M2 9a3 3 0 0 1 3-3h2.853c.297 0 .48-.309.366-.583A2.5 2.5 0 0 1 8.083 5c-.331-1.487.792-3 2.417-3c1.626 0 2.748 1.513 2.417 3a2.5 2.5 0 0 1-.136.417c-.115.274.069.583.366.583H15a3 3 0 0 1 3 3v1.853c0 .297.308.48.583.366c.135-.056.273-.104.417-.136c1.487-.331 3 .791 3 2.417s-1.513 2.748-3 2.417a2.5 2.5 0 0 1-.417-.136c-.274-.115-.583.069-.583.366V19a3 3 0 0 1-3 3h-1.893c-.288 0-.473-.291-.39-.566q.063-.21.085-.434a2.31 2.31 0 1 0-4.604 0q.021.224.086.434c.082.275-.103.566-.39.566H5a3 3 0 0 1-3-3v-2.893c0-.288.291-.473.566-.39q.21.063.434.085a2.31 2.31 0 1 0 0-4.604q-.224.021-.434.086c-.275.082-.566-.103-.566-.39z"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
Icon: () => (
|
||||
@@ -404,6 +483,11 @@ export const contents: Content[] = [
|
||||
icon: UserSquare2,
|
||||
href: "/docs/plugins/username",
|
||||
},
|
||||
{
|
||||
title: "Magic Link",
|
||||
href: "/docs/plugins/magic-link",
|
||||
icon: Mailbox,
|
||||
},
|
||||
{
|
||||
title: "Passkey",
|
||||
href: "/docs/plugins/passkey",
|
||||
@@ -415,8 +499,8 @@ export const contents: Content[] = [
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18 16.663a3.5 3.5 0 0 1-2-3.163a3.5 3.5 0 1 1 4.5 3.355V17l1.146 1.146a.5.5 0 0 1 0 .708L20.5 20l1.161 1.161a.5.5 0 0 1 .015.692l-1.823 1.984a.5.5 0 0 1-.722.015l-.985-.984a.5.5 0 0 1-.146-.354zM20.5 13a1 1 0 1 0-2 0a1 1 0 0 0 2 0M17 17.242v3.69c-1.36.714-3.031 1.07-5 1.07c-3.42 0-5.944-1.073-7.486-3.237a2.75 2.75 0 0 1-.51-1.596v-.92a2.25 2.25 0 0 1 2.249-2.25h8.775A4.5 4.5 0 0 0 17 17.243M12 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10"
|
||||
className="fill-foreground"
|
||||
d="M3.25 9.65q-.175-.125-.213-.312t.113-.388q1.55-2.125 3.888-3.3t4.987-1.175q2.65 0 5 1.138T20.95 8.9q.175.225.113.4t-.213.3q-.15.125-.35.113t-.35-.213q-1.375-1.95-3.537-2.987t-4.588-1.038q-2.425 0-4.55 1.038T3.95 9.5q-.15.225-.35.25t-.35-.1m11.6 12.325q-2.6-.65-4.25-2.588T8.95 14.65q0-1.25.9-2.1t2.175-.85q1.275 0 2.175.85t.9 2.1q0 .825.625 1.388t1.475.562q.85 0 1.45-.562t.6-1.388q0-2.9-2.125-4.875T12.05 7.8q-2.95 0-5.075 1.975t-2.125 4.85q0 .6.113 1.5t.537 2.1q.075.225-.012.4t-.288.25q-.2.075-.387-.012t-.263-.288q-.375-.975-.537-1.937T3.85 14.65q0-3.325 2.413-5.575t5.762-2.25q3.375 0 5.8 2.25t2.425 5.575q0 1.25-.887 2.087t-2.163.838q-1.275 0-2.187-.837T14.1 14.65q0-.825-.612-1.388t-1.463-.562q-.85 0-1.463.563T9.95 14.65q0 2.425 1.438 4.05t3.712 2.275q.225.075.3.25t.025.375q-.05.175-.2.3t-.375.075M6.5 4.425q-.2.125-.4.063t-.3-.263q-.1-.2-.05-.362T6 3.575q1.4-.75 2.925-1.15t3.1-.4q1.6 0 3.125.388t2.95 1.112q.225.125.263.3t-.038.35q-.075.175-.25.275t-.425-.025q-1.325-.675-2.738-1.037t-2.887-.363q-1.45 0-2.85.338T6.5 4.425m2.95 17.2q-1.475-1.55-2.262-3.162T6.4 14.65q0-2.275 1.65-3.838t3.975-1.562q2.325 0 4 1.563T17.7 14.65q0 .225-.137.363t-.363.137q-.2 0-.35-.137t-.15-.363q0-1.875-1.388-3.137t-3.287-1.263q-1.9 0-3.262 1.263T7.4 14.65q0 2.025.7 3.438t2.05 2.837q.15.15.15.35t-.15.35q-.15.15-.35.15t-.35-.15m7.55-1.7q-2.225 0-3.863-1.5T11.5 14.65q0-.2.138-.35t.362-.15q.225 0 .363.15t.137.35q0 1.875 1.35 3.075t3.15 1.2q.15 0 .425-.025t.575-.075q.225-.05.388.063t.212.337q.05.2-.075.35t-.325.2q-.45.125-.787.138t-.413.012"
|
||||
></path>
|
||||
</svg>
|
||||
),
|
||||
@@ -448,12 +532,6 @@ export const contents: Content[] = [
|
||||
icon: Key,
|
||||
href: "/docs/plugins/bearer",
|
||||
},
|
||||
// {
|
||||
// title: "Guide",
|
||||
// group: true,
|
||||
// href: "/docs/plugins/guide",
|
||||
// icon: LucideAArrowDown,
|
||||
// },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -102,12 +102,6 @@ To enable email verification, you need to configure the email and password authe
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = await betterAuth({
|
||||
// ---cut-start---
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite"
|
||||
},
|
||||
// ---cut-end---
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
async sendVerificationEmail(email, url){
|
||||
@@ -120,9 +114,6 @@ export const auth = await betterAuth({
|
||||
on the client side you can use `sendVerificationEmail` function to send verification link to user.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
const client = createAuthClient()
|
||||
// ---cut---
|
||||
const verifyEmail = async () => {
|
||||
const data = await client.sendVerificationEmail({
|
||||
email: "test@example.com",
|
||||
@@ -140,10 +131,6 @@ to reset a password first you need to provider `sendResetPasswordToken` function
|
||||
- `token`: The token that was generated.
|
||||
|
||||
```ts title="auth.ts"
|
||||
async function sendResetEmail(email: string, url: string){
|
||||
// send email to user
|
||||
}
|
||||
// ---cut---
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = await betterAuth({
|
||||
@@ -165,9 +152,6 @@ export const auth = await betterAuth({
|
||||
once you configured your server you can call `forgetPassword` function to send reset password link to user.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
const client = createAuthClient()
|
||||
// ---cut---
|
||||
const forgetPassword = async () => {
|
||||
const data = await client.forgetPassword({
|
||||
email: "test@example.com",
|
||||
@@ -181,15 +165,6 @@ When user click on the link in the email he will be redirected to the reset pass
|
||||
- `newPassword`: The new password of the user.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
const client = createAuthClient()
|
||||
function useSearchParams() {
|
||||
return {
|
||||
get: (key: string) => "" as string | null,
|
||||
}
|
||||
}
|
||||
// ---cut---
|
||||
const token = useSearchParams().get("token") // get token from url
|
||||
const resetPassword = async () => {
|
||||
if(!token) return
|
||||
@@ -200,8 +175,30 @@ const resetPassword = async () => {
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
### Configuration
|
||||
|
||||
**Password**
|
||||
|
||||
Better auth stores passwords inside the `account` table with `providerId` set to `credential`.
|
||||
|
||||
**Password Hashing**: Better Auth uses `scrypt` to hash passwords. The `scrypt` algorithm is designed to be slow and memory-intensive to make it difficult for attackers to brute force passwords. OWSAP recommends using `scrypt if `argon2id` is not available. We decided to use `scrypt` because it's natively supported by Node.js.
|
||||
|
||||
You can pass custom password hashing algothim by setting `passwordHasher` option in the `auth` configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { scrypt } from "scrypt"
|
||||
|
||||
export const auth = betterAuth({
|
||||
//...rest of the options
|
||||
emailAndPassword: {
|
||||
password: {
|
||||
hash: // your custom password hashing function
|
||||
verify: // your custom password verification function
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
@@ -227,6 +224,20 @@ const resetPassword = async () => {
|
||||
},
|
||||
sendVerificationEmail: {
|
||||
description: 'send verification email. It takes a functions that takes two parameters: email and url.',
|
||||
},
|
||||
password: {
|
||||
description: 'password configuration',
|
||||
type: 'object',
|
||||
properties: {
|
||||
hash: {
|
||||
description: 'custom password hashing function',
|
||||
type: 'function',
|
||||
},
|
||||
verify: {
|
||||
description: 'custom password verification function',
|
||||
type: 'function',
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
13
docs/content/docs/concepts/account-linking.mdx
Normal file
13
docs/content/docs/concepts/account-linking.mdx
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Account Linking
|
||||
description: Account linking allows users to link multiple authentication methods to the same account
|
||||
---
|
||||
|
||||
Account linking allows users to link multiple authentication methods to the same account. This is useful when users want to sign in using different methods.
|
||||
|
||||
Better auth automatically handles account linking when users sign in using different methods. This works by checking if the provider returns `emailVerified` field. If the field is present, Better Auth will link the account to the existing user account.
|
||||
|
||||
|
||||
<Callout>
|
||||
more sophisticated account linking feature will be added in the future.
|
||||
</Callout>
|
||||
67
docs/content/docs/concepts/api.mdx
Normal file
67
docs/content/docs/concepts/api.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: API
|
||||
description: Better Auth API
|
||||
---
|
||||
|
||||
When you create a new Better Auth instance, you get an object called `api` with a set of functions that you can use to interact with the server.
|
||||
|
||||
Any endpoint added on the server will be available on the `api` object.
|
||||
|
||||
## Calling API on the Server
|
||||
|
||||
Better auth uses a library called [better-call](https://github.com/bekacru/better-call) to make API endpoints. It's a library made by the same team behind Better Auth and is designed to work seamlessly with it.Better call endpoints allow you to call `rest` api handlers as if they were normal functions.
|
||||
|
||||
```ts title="server.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
plugins: [
|
||||
// add your plugins here
|
||||
]
|
||||
})
|
||||
|
||||
// calling sign in email on the server
|
||||
await auth.api.signInEmail({
|
||||
body: {
|
||||
email: "",
|
||||
password: ""
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Example: Getting the current Session**
|
||||
|
||||
```ts title="server.ts"
|
||||
import { headers } from "next/server";
|
||||
|
||||
// calling get session on the server
|
||||
await auth.api.getSession({
|
||||
headers: headers()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Handling Errors
|
||||
|
||||
Unlike the client, the server doesn't return error as a value, instead it throws an error. You can use a try/catch block to handle errors.
|
||||
|
||||
```ts title="server.ts"
|
||||
try {
|
||||
await auth.api.signInEmail({
|
||||
body: {
|
||||
email: "",
|
||||
password: ""
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
On some cases, the server will return an error object or just a value that indicates non successful value. Mostly, the server will throw an error.
|
||||
</Callout>
|
||||
286
docs/content/docs/concepts/client.mdx
Normal file
286
docs/content/docs/concepts/client.mdx
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
title: Client
|
||||
description: Better Auth client library for authentication
|
||||
---
|
||||
|
||||
Better auth provides a client library that you can use with different popular frontend frameworks such as React, Vue, Svelte, and more. The client library provides a set of functions to interact with the Better Auth server.
|
||||
|
||||
## Installation
|
||||
|
||||
If you haven't already, install better-auth.
|
||||
|
||||
```package-install
|
||||
npm i better-auth
|
||||
```
|
||||
|
||||
## Create Client Instance
|
||||
|
||||
Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). Call the function to create your client. Pass the base URL of your auth server. If the auth server is running on the same domain as your client, you can skip this step.
|
||||
|
||||
<Callout type="info">
|
||||
If you're using a different base path other than `/api/auth`, make sure to pass the whole URL, including the path. (e.g., `http://localhost:3000/custom-path/auth`)
|
||||
</Callout>
|
||||
|
||||
|
||||
<Tabs items={["react", "vue", "svelte", "solid",
|
||||
"vanilla"]} defaultValue="react">
|
||||
<Tab value="vanilla">
|
||||
```ts title="lib/auth-client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
export const client = createAuthClient({
|
||||
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="react" title="lib/auth-client.ts">
|
||||
```ts title="lib/auth-client.ts"
|
||||
import { createAuthClient } from "better-auth/react"
|
||||
export const client = createAuthClient({
|
||||
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="vue" title="lib/auth-client.ts">
|
||||
```ts title="lib/auth-client.ts"
|
||||
import { createAuthClient } from "better-auth/vue"
|
||||
export const client = createAuthClient({
|
||||
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="svelte" title="lib/auth-client.ts">
|
||||
```ts title="lib/auth-client.ts"
|
||||
import { createAuthClient } from "better-auth/svelte"
|
||||
export const client = createAuthClient({
|
||||
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="solid" title="lib/auth-client.ts">
|
||||
```ts title="lib/auth-client.ts"
|
||||
import { createAuthClient } from "better-auth/solid"
|
||||
export const client = createAuthClient({
|
||||
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
Once you've created your client instance, you can use the client to interact with the Better Auth server. The client provides a set of functions by default and they can be extended with plugins.
|
||||
|
||||
**Example: Sign In**
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
const client = createAuthClient()
|
||||
|
||||
await client.signIn.email({
|
||||
email: "test@user.com",
|
||||
password: "password1234"
|
||||
})
|
||||
```
|
||||
|
||||
### Session
|
||||
|
||||
The client provides a `useSession` hook to access the current user session. The `useSession` hook returns the current user session and updates automatically when the session changes.
|
||||
|
||||
|
||||
Better Auth provides a `useSession` hook to easily access session data on the client side. This hook is implemented in a reactive way for each supported framework, ensuring that any changes to the session (such as signing out) are immediately reflected in your UI.
|
||||
|
||||
<Tabs items={["React", "Vue","Svelte", "Solid"]} defaultValue="React">
|
||||
<Tab value="React">
|
||||
```tsx title="user.tsx"
|
||||
//make sure you're using the react client
|
||||
import { createAuthClient } from "better-auth/react"
|
||||
const { useSession } = createAuthClient() // [!code highlight]
|
||||
|
||||
export function User(){
|
||||
const {
|
||||
data: session,
|
||||
isPending, //loading state
|
||||
error //error object
|
||||
} = useSession()
|
||||
returns (
|
||||
//...
|
||||
)
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="Vue">
|
||||
```vue title="user.vue"
|
||||
<template>
|
||||
<div>
|
||||
<button v-if="!client.useSession().value" @click="() => client.signIn.social({
|
||||
provider: 'github'
|
||||
})">
|
||||
Continue with github
|
||||
</button>
|
||||
<div>
|
||||
<pre>{{ client.useSession().value }}</pre>
|
||||
<button v-if="client.useSession().value" @click="client.signOut()">
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="Svelte">
|
||||
```svelte title="user.svelte"
|
||||
<script lang="ts">
|
||||
import { client } from "$lib/client";
|
||||
const session = client.useSsession;
|
||||
</script>
|
||||
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 10px; border-radius: 10px; border: 1px solid #4B453F; padding: 20px; margin-top: 10px;"
|
||||
>
|
||||
<div>
|
||||
{#if $session}
|
||||
<div>
|
||||
<p>
|
||||
{$session?.user.name}
|
||||
</p>
|
||||
<p>
|
||||
{$session?.user.email}
|
||||
</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>
|
||||
</div>
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="Solid">
|
||||
```tsx title="user.tsx"
|
||||
import { client } from "~/lib/client";
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
export default function Home() {
|
||||
const session = client.useSession()
|
||||
return (
|
||||
<Show
|
||||
when={session()}
|
||||
fallback={<button onClick={toggle}>Log in</button>}
|
||||
>
|
||||
<button onClick={toggle}>Log out</button>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Fetch Options
|
||||
|
||||
The client uses a libray called [better fetch](https://better-fetch.vercel.app) to make requests to the server.
|
||||
|
||||
Better fetch is a wrapper around the native fetch API that provides a more convenient way to make requests. It's created by the same team behind Better Auth and is designed to work seamlessly with it.
|
||||
|
||||
You can pass any defualt fetch options to the client by passing `fetchOptions` object to the `createAuthClient` function.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
const client = createAuthClient({
|
||||
fetchOptions: {
|
||||
//any better-fetch options
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can also pass fetch options to most of the client functions. For example, you can pass `fetchOptions` to the `signIn` function to customize the request.
|
||||
|
||||
```ts title="client.ts"
|
||||
await client.signIn.email({
|
||||
email: "email@email.com",
|
||||
password: "password1234",
|
||||
fetchOptions: {
|
||||
onSuccess(ctx){
|
||||
//
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Handling Errors
|
||||
|
||||
Most of the client functions return a response object with the following properties:
|
||||
- `data`: The response data.
|
||||
- `error`: The error object if there was an error.
|
||||
|
||||
the error object contains the following properties:
|
||||
- `message`: The error message. (e.g., "Invalid email or password")
|
||||
- `status`: The HTTP status code.
|
||||
- `statusText`: The HTTP status text.
|
||||
|
||||
```ts title="client.ts"
|
||||
const { data, error } = await client.signIn.email({
|
||||
email: "email@email.com",
|
||||
password: "password1234"
|
||||
})
|
||||
if(error){
|
||||
//handle error
|
||||
}
|
||||
```
|
||||
|
||||
If the functions accepte a `fetchOptions` object, you can pass an `onError` function to handle errors.
|
||||
|
||||
```ts title="client.ts"
|
||||
await client.signIn.email({
|
||||
email: "email@email.com",
|
||||
password: "password1234",
|
||||
fetchOptions: {
|
||||
onError(ctx){
|
||||
//handle error
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Hooks like `useSession` also return an error object if there was an error fetching the session. On top of that, they also return a `isPending` property to indicate if the request is still pending.
|
||||
|
||||
```ts title="client.ts"
|
||||
const { data, error, isPending } = useSession()
|
||||
if(error){
|
||||
//handle error
|
||||
}
|
||||
```
|
||||
|
||||
### Plugins
|
||||
|
||||
You can extend the client with plugins to add more functionality. Plugins can add new functions to the client or modify existing ones.
|
||||
|
||||
**Example: Magic Link Plugin**
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
import { magicLinkClient } from "better-auth/client/plugins"
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [
|
||||
magicLinkClient()
|
||||
]
|
||||
})
|
||||
```
|
||||
123
docs/content/docs/concepts/database.mdx
Normal file
123
docs/content/docs/concepts/database.mdx
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Database
|
||||
description: Learn how to use a database with BetterAuth
|
||||
---
|
||||
|
||||
Better auth requires a database connection to store data. Better auth comes with a built in database mangment using <Link href="https://kysely.dev/"> Kysley </Link> to manage the database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data.
|
||||
|
||||
You can pass a database connection to better auth by passing a `database` object to the `betterAuth` function. The `database` object should have a `provider` key that specifies the database provider to use. By default, better auth support `postgres`, `mysql` and `sqlite`. You can also pass a kysley dialect directly to use other databases supported by kysley. The `database` object can also have a `url` key that specifies the database connection to use. If you're using `sqlite`, you can pass a file path in the `url` key.
|
||||
|
||||
**Example: Sqlite**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "path/to/database.sqlite"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Example: Postgres**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "postgres",
|
||||
url: "postgres://user:password@localhost:5432/database"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Exmaple: Mysql**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "mysql",
|
||||
url: "mysql://user:password@localhost:3306/database"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Exmaple: Custom Dialect using libsql**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: new LibsqlDialect({
|
||||
url: process.env.TURSO_DATABASE_URL || "",
|
||||
authToken: process.env.TURSO_AUTH_TOKEN || "",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
<Callout>
|
||||
See <Link href="https://kysely.dev/docs/dialects" target="_blank"> Kysley Dialects </Link> for more dialects supported by kysley.
|
||||
</Callout>
|
||||
|
||||
|
||||
## Running Migrations
|
||||
|
||||
Better auth includes a CLI tool to manage database migrations. Use the `migrate` command to create or update tables as needed.
|
||||
|
||||
The cli checks your database and prompts you to add missing tables or update existing ones with new columns.
|
||||
|
||||
```bash
|
||||
npx better-auth migrate
|
||||
```
|
||||
|
||||
## Core Schema
|
||||
|
||||
Better auth requires the following tables to be present in the database:
|
||||
|
||||
**user**:
|
||||
|
||||
- `id`: The unique identifier of the user.
|
||||
- `email`: The email address of the user.
|
||||
- `name`: The name of the user.
|
||||
- `image`: The image of the user.
|
||||
|
||||
**session**:
|
||||
- `id`: The unique identifier of the session. Also used as the session token.
|
||||
- `userId`: The id of the user.
|
||||
- `expiresAt`: The time when the session expires.
|
||||
- `ipAddress`: The IP address of the user.
|
||||
- `userAgent`: The user agent of the user.
|
||||
|
||||
**account**:
|
||||
- `id`: The unique identifier of the account.
|
||||
- `userId`: The id of the user.
|
||||
- `accountId`: The id of the account. (optional)
|
||||
- `providerId`: The id of the provider. (optional)
|
||||
- `accessToken`: The access token of the account. Returned by the provider. (optional)
|
||||
- `refreshToken`: The refresh token of the account. Returned by the provider. (optional)
|
||||
- `accessTokenExpiresAt`: The time when the access token expires. (optional)
|
||||
- `refreshTokenExpiresAt`: The time when the refresh token expires. (optional)
|
||||
- `password`: The password of the account. Mainly used for email and password authentication. (optional)
|
||||
|
||||
|
||||
## Plugins Schema
|
||||
|
||||
Plugins can define their own tables in the database to store additional data. They can also add columns to the core tables to store additional data. For example, the two factor authentication plugin adds the following columns to the `user` table:
|
||||
|
||||
- `twoFactorEnabled`: Whether two factor authentication is enabled for the user.
|
||||
- `twoFactorSecret`: The secret key used to generate TOTP codes.
|
||||
- `twoFactorBackupCodes`: Encrypted backup codes for account recovery.
|
||||
|
||||
To add additional tables and columns to the database, you can run the `migrate` command provided by better auth. The `migrate` command will run the migrations defined by the plugins to add the required tables and columns to the database.
|
||||
|
||||
```bash
|
||||
npx better-auth migrate
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The `migrate` command should be run whenever you add plugins that require additional tables or columns in the database.
|
||||
</Callout>
|
||||
|
||||
## Extending the Schema
|
||||
|
||||
Better auth currently does not provide a direct method to extend the core schema. However, you can add extra columns to the database and manage them independently. For instance, to store additional user data, you can either create a new table or add columns to the `user` table.
|
||||
|
||||
We suggest creating a new table for additional data to keep the core tables clean and maintainable. You can link this new table to the `user` table using a foreign key.
|
||||
324
docs/content/docs/concepts/plugins.mdx
Normal file
324
docs/content/docs/concepts/plugins.mdx
Normal file
@@ -0,0 +1,324 @@
|
||||
---
|
||||
title: Plugins
|
||||
description: Learn how to use plugins with BetterAuth
|
||||
---
|
||||
|
||||
Plugins are a key part of Better Auth, letting you extend its functionality. Use them to add new authentication methods, features, or customize behavior.
|
||||
|
||||
Better Auth offers many built-in plugins ready to use. Check the plugins section for details. You can also create your own plugins.
|
||||
|
||||
## Using a Plugin
|
||||
|
||||
Plugins can be a server-side plugin, a client-side plugin, or both.
|
||||
|
||||
To add a plugin on the server, include it in the `plugins` array in the Better Auth configuration. The plugin will initialize with the provided options.
|
||||
|
||||
```ts title="server.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
plugins: [
|
||||
// Add your plugins here
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
Client plugins are added when creating the client. Include the plugin in the `plugins` array when creating the client. Most clients require both server and client plugins to work correctly.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [
|
||||
// Add your client plugins here
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Creating a Plugin
|
||||
|
||||
To create a plugin you need to pass an object that satsfies the `BetterAuthPlugin` interface.
|
||||
|
||||
The only required key is `id`, which is a unique identifier for the plugin.
|
||||
|
||||
```ts title="plugin.ts"
|
||||
const myPlugin = {
|
||||
id: "my-plugin",
|
||||
}
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
|
||||
To add endpoints to the server, you can pass `endpoints` that requires an object with the key being any `string` and teh value being `AuthEndpoint`.
|
||||
|
||||
To create an Auth Endpoint you'll need to import `createAuthEndpoint` from `better-auth`.
|
||||
|
||||
Better auth uses a library called <Link href="https://github.com/bekacru/better-call"> Better Call </Link> to create endpoints. Better call is a simple web framework made by the same author of Better Auth.
|
||||
|
||||
```ts title="plugin.ts"
|
||||
import { createAuthEndpoint } from "better-auth";
|
||||
|
||||
const myPlugin = {
|
||||
id: "my-plugin",
|
||||
endpoints: {
|
||||
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
|
||||
method: "GET",
|
||||
}, async(req)=>{
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `req` object, it'll provide you with an object called `context` that give you access better-auth specefic contexts including `options`, `db`, `baseURL` and more.
|
||||
|
||||
**Context Object**
|
||||
|
||||
- `appName`: The name of the application. Defaults to "Better Auth".
|
||||
- `options`: The options passed to the Better Auth instance.
|
||||
- `tables`: Core tables defination. It is an object which has the table name as the key and the schema defination as the value.
|
||||
- `baseURL`: the baseURL of the auth server. This includes the path. For example, if the server is running on `http://localhost:3000`, the baseURL will be `http://localhost:3000/api/auth` by default unless changed by the user.
|
||||
- `session`: The session configuration. Includes `updateAge` and `expiresIn` values.
|
||||
- `secret`: The secret key used for various purposes. This is defined by the user.
|
||||
- `authCookie`: The defualt cookie configuration for core auth cookies.
|
||||
- `logger`: The logger instance used by Better Auth.
|
||||
- `db`: The kysely instance used by Better Auth to interact with the database.
|
||||
- `password`: The password configuration. Includes `hash` and `verify` values for hashing and verifying passwords.
|
||||
- `adapter`: This is the same as db but it give you `orm` like functions to interact with the database. (we recommend using this over `db` unless you need raw sql queries or for peroframnce reasons)
|
||||
- `intenralAdapter`: this are intenral db calls that are used by better auth. You can use this calls for example to create session instead of using `adapter` directly. `intenralAdapter.createSession(userId)`
|
||||
- `createAuthCookie`: This is a helper function that let's you get a cookie `name` and `options` for either to `set` or `get` cookies. It implements things like `__secure` prefix and `__host` prefix for cookies based on
|
||||
|
||||
For other properties, you can check the <Link href="https://github.com/bekacru/better-call">Better Call</Link> documentation.
|
||||
|
||||
**Rules**
|
||||
|
||||
- Makes sure you use kebab-case for the endpoint path.
|
||||
- Make sure to only use `POST` or `GET` methods for the endpoints.
|
||||
- Any function that modifies a data should be a `POST` method.
|
||||
- Any function that fetches data should be a `GET` method.
|
||||
- Make sure to use the `createAuthEndpoint` function to create the endpoint.
|
||||
- Make sure your paths are unique to avoid conflicts with other plugins.
|
||||
|
||||
### Schema
|
||||
|
||||
You can define a database schema for your plugin by passing a `schema` object. The schema object should have the table name as the key and the schema definition as the value.
|
||||
|
||||
```ts title="plugin.ts"
|
||||
const myPlugin = {
|
||||
id: "my-plugin",
|
||||
schema: {
|
||||
myTable: {
|
||||
fields: {
|
||||
name: {
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**
|
||||
|
||||
By default better-auth will create an `id` field for each table. You can add additional fields to the table by adding them to the `fields` object.
|
||||
|
||||
The key is the column name and the value is the column definition. The column definition can have the following properties:
|
||||
|
||||
`type`: The type of the filed. It can be `string`, `number`, `boolean`, `date`.
|
||||
|
||||
`required`: if the field should be required on a new record. (default: `false`)
|
||||
|
||||
`returned`: if the field should be returned when fetching records. (default: `true`)
|
||||
|
||||
`unique`: if the field should be unique. (default: `false`)
|
||||
|
||||
`reference`: if the field is a reference to another table. (default: `null`) It takes an object with the following properties:
|
||||
- `model`: The table name to reference.
|
||||
- `field`: The field name to reference.
|
||||
- `onDelete`: The action to take when the referenced record is deleted. (default: `null`)
|
||||
|
||||
**Other Schema Properties**
|
||||
|
||||
`disableMigration`: if the table should not be migrated. (default: `false`)
|
||||
|
||||
```ts title="plugin.ts"
|
||||
const myPlugin = (opts: PluginOptions)=>{
|
||||
return {
|
||||
id: "my-plugin",
|
||||
schema: {
|
||||
rateLimit: {
|
||||
fields: {
|
||||
key: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
disableMigration: opts.storage.provider !== "database", // [!code highlight]
|
||||
},
|
||||
},
|
||||
} satisfies BetterAuthPlugin
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
You can add middleware to the server by passing a `middleware` array. The middleware array should contain an array of middleware functions created by `createAuthMiddelware`.
|
||||
|
||||
`createAuthMiddelware` wraps around `createMiddleware` from Better Call. It takes a function that returns a middleware function. The function will be called with the context object.
|
||||
|
||||
```ts title="plugin.ts"
|
||||
const myPlugin = {
|
||||
id: "my-plugin",
|
||||
middleware: [
|
||||
createAuthMiddelware(async(ctx)=>{
|
||||
//do something
|
||||
})
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If you throw an `APIError` from the middleware or returned a `Response` object, the request will be stopped and the response will be sent to the client.
|
||||
|
||||
### Hooks
|
||||
|
||||
Hooks and middleware are similar but middlewares are handled by better-call and hooks are handled by better-auth. Hooks allows you to execture a code `before` or `after` a specific action. Hooks takes an array of `after` or `before` hooks.
|
||||
|
||||
```ts title="plugin.ts"
|
||||
const myPlugin = {
|
||||
id: "my-plugin",
|
||||
hooks: {
|
||||
before: {
|
||||
[{
|
||||
matcher: (context)=>{
|
||||
return context.req.method === "POST"
|
||||
},
|
||||
handler: createAuthMiddelware(async(ctx)=>{
|
||||
//do something before the request
|
||||
})
|
||||
}]
|
||||
},
|
||||
after: [{
|
||||
matcher: (context)=>{
|
||||
return context.req.method === "POST"
|
||||
},
|
||||
handler: createAuthMiddelware(async(ctx)=>{
|
||||
//this is the returned response object from the actual endpoint
|
||||
const returned = ctx.context.returned
|
||||
//do something after the request
|
||||
})
|
||||
}]
|
||||
}
|
||||
} satisfies BetterAuthPlugin
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
If your endpoints needs to be called from the client, you'll need to also create a client plugin. Better auth clients can infer the endpoints from the server plugins. You can also add additional client side logic.
|
||||
|
||||
**Endpoint Inference**
|
||||
|
||||
Endpoints are inferred from the server plugin by adding a `$InferServerPlugin` key to the client plugin.
|
||||
|
||||
The client infers the `path` as an object and converts kebab-case to camelCase. For example, `/my-plugin/hello-world` becomes `myPlugin.helloWorld`.
|
||||
|
||||
```ts title="client-plugin.ts"
|
||||
import type { AuthClientPlugin } from "better-auth/client";
|
||||
import type { myPlugin } from ".//plugin";
|
||||
|
||||
const myPluginClient = {
|
||||
id: "my-plugin",
|
||||
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
|
||||
} satisfies AuthClientPlugin
|
||||
```
|
||||
|
||||
**getActions**
|
||||
|
||||
If you need to add additional methods or what not to the client you can use the `getActions` function. This function is called with the `fetch` function from the client.
|
||||
|
||||
Better auth uses <Link href="https://bette-fetch.vercel.app"> Better fetch </Link> to make requests. Better fetch is a simple fetch wrapper made by the same author of Better Auth.
|
||||
|
||||
```ts title="client-plugin.ts"
|
||||
import type { AuthClientPlugin } from "better-auth/client";
|
||||
import type { myPlugin } from "./plugin";
|
||||
import { BetteFetchOptions } from "better-fetch";
|
||||
|
||||
const myPluginClient = {
|
||||
id: "my-plugin",
|
||||
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
|
||||
getActions: ($fetch)=>{
|
||||
return {
|
||||
myCustomAction: async (data: {
|
||||
foo: string,
|
||||
fetchOptions?: BetteFetchOptions
|
||||
})=>{
|
||||
const res = $fetch("/custom/action", {
|
||||
method: "POST",
|
||||
body: {
|
||||
foo: data.foo
|
||||
}
|
||||
...data.fetchOptions
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
} satisfies AuthClientPlugin
|
||||
```
|
||||
|
||||
<Callout>
|
||||
As a general rule, make sure to accept only one argument in the function and make sure you also optionally accept a `fetchOptions` object. This is to make sure the user can pass additional options to the fetch call. And return and object with the `data` and `error` key.
|
||||
</Callout>
|
||||
|
||||
**getAtoms**
|
||||
|
||||
This is only useful if you want to provide `hooks` like `useSession`.
|
||||
|
||||
Get atoms is called with the `fetch` function from better fetch and it should return an object with the atoms. This atoms should be created using <Link href="https://github.com/nanostores/nanostores">nanostore</Link>. The atoms will be resolved by each framework `useStore` hook provided by nanostore.
|
||||
|
||||
```ts title="client-plugin.ts"
|
||||
import { atom } from "nanostores";
|
||||
import type { AuthClientPlugin } from "better-auth/client";
|
||||
|
||||
const myPluginClient = {
|
||||
id: "my-plugin",
|
||||
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
|
||||
getAtoms: ($fetch)=>{
|
||||
const myAtom = atom<null>()
|
||||
return {
|
||||
myAtom
|
||||
}
|
||||
}
|
||||
} satisfies AuthClientPlugin
|
||||
```
|
||||
|
||||
See built in plugins for examples of how to use atoms properly.
|
||||
|
||||
**pathMethods**
|
||||
|
||||
by default, infered paths use `GET` method if they don't require a body and `POST` if they do. You can override this by passing a `pathMethods` object. The key should be the path and the value should be the method ("POST" | "GET").
|
||||
|
||||
```ts title="client-plugin.ts"
|
||||
import type { AuthClientPlugin } from "better-auth/client";
|
||||
import type { myPlugin } from "./plugin";
|
||||
|
||||
const myPluginClient = {
|
||||
id: "my-plugin",
|
||||
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
|
||||
pathMethods: {
|
||||
"/my-plugin/hello-world": "POST"
|
||||
}
|
||||
} satisfies AuthClientPlugin
|
||||
```
|
||||
|
||||
**fetchPlugins**
|
||||
|
||||
If you need to use better fetch plugins you can pass them to the `fetchPlugins` array. You can read more about better fetch plugins in the <Link href="https://better-fetch.vercel.app/docs/plugins">better fetch documentation</Link>.
|
||||
|
||||
**atomListners**
|
||||
|
||||
This is only useful if you want to provide `hooks` like `useSession` and you want to listen to the atoms and re-evalue the atoms when the atom changes.
|
||||
|
||||
You can see how this is used in the built in plugins.
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
title: Database
|
||||
description: Learn how to use a database with BetterAuth
|
||||
---
|
||||
|
||||
Better auth requires a database connection to store data. Better auth comes with a built in database mangment using <Link href="https://kysely.dev/"> Kysley </Link> to manage the database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data.
|
||||
|
||||
You can pass a database connection to better auth by passing a `database` object to the `betterAuth` function. The `database` object should have a `provider` key that specifies the database provider to use. By default, better auth support `postgres`, `mysql` and `sqlite`. You can also pass a kysley dialect directly to use other databases supported by kysley. The `database` object can also have a `url` key that specifies the database connection to use. If you're using `sqlite`, you can pass a file path in the `url` key.
|
||||
|
||||
**Example: Sqlite**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "path/to/database.sqlite"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Example: Postgres**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "postgres",
|
||||
url: "postgres://user:password@localhost:5432/database"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Exmaple: Mysql**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "mysql",
|
||||
url: "mysql://user:password@localhost:3306/database"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Exmaple: Custom Dialect**
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: new LibsqlDialect({
|
||||
url: process.env.TURSO_DATABASE_URL || "",
|
||||
authToken: process.env.TURSO_AUTH_TOKEN || "",
|
||||
}),
|
||||
})
|
||||
```
|
||||
@@ -24,7 +24,7 @@ This plugin offers two main methods of 2FA:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
### Add the plugin to your auth config
|
||||
### Add the plugin to your auth config
|
||||
|
||||
Add the two-factor plugin to your auth configuration and specify your app name as the issuer.
|
||||
|
||||
@@ -161,9 +161,7 @@ const verifyTotp = async (code: string) => {
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
OTP is a one-time password sent to the user's email or phone.
|
||||
Before using OTP, you need to setup `sendOTP` function.
|
||||
|
||||
```ts title="auth.ts" twoslash
|
||||
@@ -179,7 +177,7 @@ export const auth = await betterAuth({
|
||||
twoFactor({
|
||||
otpOptions: {
|
||||
async sendOTP(user, otp) {
|
||||
console.log({ user, otp });
|
||||
// send otp to user
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
96
docs/content/docs/plugins/magic-link.mdx
Normal file
96
docs/content/docs/plugins/magic-link.mdx
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: Magic link
|
||||
description: Magic link plugin
|
||||
---
|
||||
|
||||
Magic link or email link is a way to authenticate users without a password. When a user enters their email, a link is sent to their email. When the user clicks on the link, they are authenticated.
|
||||
|
||||
## Installation
|
||||
<Steps>
|
||||
<Step>
|
||||
### Add the server Plugin
|
||||
|
||||
Add the magic link plugin to your server:
|
||||
|
||||
```ts title="server.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
import { magicLink } from "better-auth/plugin";
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
plugins: [
|
||||
magicLink({
|
||||
sendMagicLink: async (data: {
|
||||
email: string,
|
||||
token: string,
|
||||
url: string
|
||||
}) => {
|
||||
// send email to user
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Add the client Plugin
|
||||
|
||||
Add the magic link plugin to your client:
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
import { magicLinkClient } from "better-auth/client/plugins";
|
||||
const client = createAuthClient({
|
||||
plugins: [
|
||||
magicLinkClient()
|
||||
]
|
||||
});
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Sign In with Magic Link
|
||||
|
||||
To sign in with a magic link, you need to call `signIn.magicLink` with the user's email address. The `sendMagicLink` function is called to send the magic link to the user's email.
|
||||
|
||||
```ts title="magic-link.ts"
|
||||
const { data, error } = await client.signIn.magicLink({
|
||||
email: "user@email.com",
|
||||
callbackURL: "/dashboard" //redirect after successful login (optional)
|
||||
});
|
||||
```
|
||||
|
||||
### Verify Magic Link
|
||||
|
||||
When you send the URL generated by the `sendMagicLink` function to a user, clicking the link will authenticate them and redirect them to the `callbackURL` specified in the `signIn.magicLink` function. If an error occurs, the user will be redirected to the `callbackURL` with an error query parameter.
|
||||
|
||||
<Callout type="warn">
|
||||
If no `callbackURL` is provided, the user will not be redirected and will see a JSON response instead. Therefore, always ensure to provide a `callbackURL` when sending the URL directly to the user.
|
||||
</Callout>
|
||||
|
||||
If you want to handle the verification manually, (e.g, if you send the user a differnt url), you can use the `verify` function.
|
||||
|
||||
```ts title="magic-link.ts"
|
||||
const { data, error } = await client.magicLink.verify({
|
||||
query: {
|
||||
token
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
**sendMagicLink**: The `sendMagicLink` function is called when a user requests a magic link. It takes an object with the following properties:
|
||||
|
||||
- `email`: The email address of the user.
|
||||
- `url`: The url to be sent to the user. This url contains the token.
|
||||
- `token`: The token if you want to send the token with custom url.
|
||||
|
||||
**expiresIn**: specifies the time in seconds after which the magic link will expire. The default value is `300` seconds (5 minutes).
|
||||
|
||||
@@ -209,27 +209,4 @@ export const auth = await betterAuth({
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Rate Limiter
|
||||
|
||||
you can also pass a custom rate limiter that'll overtake the default plugins rate llimiting implementation.
|
||||
|
||||
The custom rate limiter function should accept a Request object and return a boolean value indicating whether the request should be allowed or not.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { rateLimiter } from "better-auth/plugins"
|
||||
|
||||
export const auth = await betterAuth({
|
||||
//
|
||||
plugins: [
|
||||
rateLimit({
|
||||
enabled: true,
|
||||
rateLimiter: async (request) => { // [!code highlight]
|
||||
return true; // [!code highlight]
|
||||
}, // [!code highlight]
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
@@ -92,3 +92,8 @@ export type FieldAttributeConfig<T extends FieldType = FieldType> = {
|
||||
*/
|
||||
validator?: ZodSchema;
|
||||
};
|
||||
|
||||
export type PluginFieldAttribute = Omit<
|
||||
FieldAttribute,
|
||||
"transform" | "defaultValue" | "hashValue"
|
||||
>;
|
||||
|
||||
@@ -7,17 +7,22 @@ import { createEmailVerificationToken } from "../../api/routes";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { validateJWT, type JWT } from "oslo/jwt";
|
||||
import { setSessionCookie } from "../../utils/cookies";
|
||||
import { off } from "process";
|
||||
|
||||
interface MagicLinkOptions {
|
||||
/**
|
||||
* Time in seconds until the magic link expires.
|
||||
* @default (60 * 60) // 1 hour
|
||||
* @default (60 * 5) // 5 minutes
|
||||
*/
|
||||
expiresIn?: number;
|
||||
/**
|
||||
* Send magic link implementation.
|
||||
*/
|
||||
sendMagicLink: (email: string, url: string) => Promise<void> | void;
|
||||
sendMagicLink: (data: {
|
||||
email: string;
|
||||
url: string;
|
||||
token: string;
|
||||
}) => Promise<void> | void;
|
||||
}
|
||||
|
||||
export const magicLink = (options: MagicLinkOptions) => {
|
||||
@@ -53,7 +58,11 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
ctx.body.callbackURL || ctx.body.currentURL
|
||||
}`;
|
||||
try {
|
||||
await options.sendMagicLink(email, url);
|
||||
await options.sendMagicLink({
|
||||
email,
|
||||
url,
|
||||
token,
|
||||
});
|
||||
} catch (e) {
|
||||
ctx.context.logger.error("Failed to send magic link", e);
|
||||
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||
@@ -86,6 +95,9 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
);
|
||||
} catch (e) {
|
||||
ctx.context.logger.error("Failed to verify email", e);
|
||||
if (callbackURL) {
|
||||
throw ctx.redirect(`${callbackURL}?error=INVALID_TOKEN`);
|
||||
}
|
||||
return ctx.json(null, {
|
||||
status: 400,
|
||||
statusText: "INVALID_TOKEN",
|
||||
@@ -102,6 +114,9 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
parsed.email,
|
||||
);
|
||||
if (!user) {
|
||||
if (callbackURL) {
|
||||
throw ctx.redirect(`${callbackURL}?error=USER_NOT_FOUND`);
|
||||
}
|
||||
return ctx.json(null, {
|
||||
status: 400,
|
||||
statusText: "USER_NOT_FOUND",
|
||||
@@ -115,6 +130,9 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
ctx.headers,
|
||||
);
|
||||
if (!session) {
|
||||
if (callbackURL) {
|
||||
throw ctx.redirect(`${callbackURL}?error=SESSION_NOT_CREATED`);
|
||||
}
|
||||
return ctx.json(null, {
|
||||
status: 400,
|
||||
statusText: "SESSION NOT CREATED",
|
||||
|
||||
@@ -7,14 +7,15 @@ import { magicLinkClient } from "./client";
|
||||
describe("magic link", async () => {
|
||||
let verificationEmail = {
|
||||
email: "",
|
||||
token: "",
|
||||
url: "",
|
||||
};
|
||||
const { auth, customFetchImpl, testUser, sessionSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [
|
||||
magicLink({
|
||||
async sendMagicLink(email, url) {
|
||||
verificationEmail = { email, url };
|
||||
async sendMagicLink(data) {
|
||||
verificationEmail = data;
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -117,24 +117,6 @@ export const rateLimiter = (options: RateLimitOptions) => {
|
||||
],
|
||||
...options,
|
||||
} satisfies RateLimitOptions;
|
||||
const schema =
|
||||
opts.storage.provider === "database"
|
||||
? ({
|
||||
rateLimit: {
|
||||
fields: {
|
||||
key: {
|
||||
type: "string",
|
||||
},
|
||||
count: {
|
||||
type: "number",
|
||||
},
|
||||
lastRequest: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const)
|
||||
: undefined;
|
||||
|
||||
function createDBStorage(ctx: GenericEndpointContext) {
|
||||
const db = ctx.context.db;
|
||||
@@ -263,6 +245,21 @@ export const rateLimiter = (options: RateLimitOptions) => {
|
||||
}),
|
||||
},
|
||||
],
|
||||
schema,
|
||||
schema: {
|
||||
rateLimit: {
|
||||
fields: {
|
||||
key: {
|
||||
type: "string",
|
||||
},
|
||||
count: {
|
||||
type: "number",
|
||||
},
|
||||
lastRequest: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
disableMigration: opts.storage.provider !== "database",
|
||||
},
|
||||
},
|
||||
} satisfies BetterAuthPlugin;
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const twoFactorClient = (
|
||||
},
|
||||
],
|
||||
pathMethods: {
|
||||
"enable/totp": "POST",
|
||||
"/two-factor/disable": "POST",
|
||||
"/two-factor/enable": "POST",
|
||||
"/two-factor/send-otp": "POST",
|
||||
|
||||
Reference in New Issue
Block a user