diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a9390c1..250b76a0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,6 +21,6 @@ "editor.defaultFormatter": "biomejs.biome" }, "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3c3a78d..721b7bf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,10 +42,12 @@ If you see any security issue we prefer you to disclose it via an email (securit 1. Fork the repo 2. clone your fork. 3. install node.js (preferable latest LTS). -4. run pnpm i in your terminal to install dependencies. +4. run `pnpm i` in your terminal to install dependencies. 5. create a branch. -6. create a draft pull request. link the relevant issue by referring to it in the PR's description. Eg.closes #123 will link the PR to issue/pull request #123. -7. implement your changes. +6. build the project using `pnpm build` +7. run `pnpm -F docs` dev (to run the docs section) +8. create a draft pull request. link the relevant issue by referring to it in the PR's description. Eg.closes #123 will link the PR to issue/pull request #123. +9. implement your changes. ## Testing diff --git a/docs/components/sidebar-content.tsx b/docs/components/sidebar-content.tsx index 58765df7..5973b3f1 100644 --- a/docs/components/sidebar-content.tsx +++ b/docs/components/sidebar-content.tsx @@ -1,408 +1,408 @@ import { - Key, - LucideAArrowDown, - LucideIcon, - Mail, - Mailbox, - MailboxIcon, - Mails, - Phone, - ScanFace, - ShieldCheck, - UserCircle, - Users2, - UserSquare2, + Key, + LucideAArrowDown, + LucideIcon, + Mail, + Mailbox, + MailboxIcon, + Mails, + Phone, + ScanFace, + ShieldCheck, + UserCircle, + Users2, + UserSquare2, } from "lucide-react"; import { ReactNode, SVGProps } from "react"; import { Icons } from "./icons"; interface Content { - title: string; - href?: string; - Icon: ((props?: SVGProps) => ReactNode) | LucideIcon; - list: { - title: string; - href: string; - icon: ((props?: SVGProps) => ReactNode) | LucideIcon; - group?: boolean; - }[]; + title: string; + href?: string; + Icon: ((props?: SVGProps) => ReactNode) | LucideIcon; + list: { + title: string; + href: string; + icon: ((props?: SVGProps) => ReactNode) | LucideIcon; + group?: boolean; + }[]; } export const contents: Content[] = [ - { - title: "Get Started", - Icon: () => ( - - - - ), - list: [ - { - title: "Introduction", - href: "/docs/introduction", - icon: () => ( - - - - ), - }, - { - title: "Comparison", - href: "/docs/comparison", - icon: () => ( - - - - ), - }, - { - title: "Installation", - href: "/docs/installation", - icon: () => ( - - - - ), - }, - { - title: "Basic Usage", - href: "/docs/basic-usage", - icon: () => ( - - - - ), - }, - ], - }, - { - title: "Concepts", - list: [ - { - href: "/docs/concepts/api", - title: "API", - icon: () => ( - - - - ), - }, - { - title: "CLI", - icon: () => ( - - - - ), - href: "/docs/concepts/cli", - }, - { - title: "Client", - href: "/docs/concepts/client", - icon: () => ( - - - - ), - }, - { - title: "Cookies", - href: "/docs/concepts/cookies", - icon: () => ( - - - - ), - }, - { - title: "Database", - icon: (props?: SVGProps) => ( - - - - - - - - - ), - href: "/docs/concepts/database", - }, - { - href: "/docs/concepts/email", - title: "Email", - icon: (props?: SVGProps) => ( - - - - - ), - }, - { - href: "/docs/concepts/plugins", - title: "Plugins", - icon: (props?: SVGProps) => ( - - - - ), - }, - { - title: "Rate Limit", - icon: () => { - return ( - - - - ); - }, - href: "/docs/concepts/rate-limit", - }, - { - title: "Sessions", - href: "/docs/concepts/session-management", - icon: () => ( - - - - ), - }, - { - title: "Typescript", - href: "/docs/concepts/typescript", - icon: () => ( - - - - ), - }, - { - title: "Users & Accounts", - href: "/docs/concepts/users-accounts", - icon: () => ( - - - - ), - }, - ], - Icon: () => ( - - - - - ), - }, - { - title: "Authentication", - Icon: () => ( - - - - ), - list: [ - { - title: "Email & Password", - href: "/docs/authentication/email-password", - icon: () => ( - - - - ), - }, - { - title: "Social Sign-On", - group: true, - icon: LucideAArrowDown, - href: "/", - }, - { - title: "Apple", - href: "/docs/authentication/apple", - icon: () => ( - - - - ), - }, + { + title: "Get Started", + Icon: () => ( + + + + ), + list: [ + { + title: "Introduction", + href: "/docs/introduction", + icon: () => ( + + + + ), + }, + { + title: "Comparison", + href: "/docs/comparison", + icon: () => ( + + + + ), + }, + { + title: "Installation", + href: "/docs/installation", + icon: () => ( + + + + ), + }, + { + title: "Basic Usage", + href: "/docs/basic-usage", + icon: () => ( + + + + ), + }, + ], + }, + { + title: "Concepts", + list: [ + { + href: "/docs/concepts/api", + title: "API", + icon: () => ( + + + + ), + }, + { + title: "CLI", + icon: () => ( + + + + ), + href: "/docs/concepts/cli", + }, + { + title: "Client", + href: "/docs/concepts/client", + icon: () => ( + + + + ), + }, + { + title: "Cookies", + href: "/docs/concepts/cookies", + icon: () => ( + + + + ), + }, + { + title: "Database", + icon: (props?: SVGProps) => ( + + + + + + + + + ), + href: "/docs/concepts/database", + }, + { + href: "/docs/concepts/email", + title: "Email", + icon: (props?: SVGProps) => ( + + + + + ), + }, + { + href: "/docs/concepts/plugins", + title: "Plugins", + icon: (props?: SVGProps) => ( + + + + ), + }, + { + title: "Rate Limit", + icon: () => { + return ( + + + + ); + }, + href: "/docs/concepts/rate-limit", + }, + { + title: "Sessions", + href: "/docs/concepts/session-management", + icon: () => ( + + + + ), + }, + { + title: "Typescript", + href: "/docs/concepts/typescript", + icon: () => ( + + + + ), + }, + { + title: "Users & Accounts", + href: "/docs/concepts/users-accounts", + icon: () => ( + + + + ), + }, + ], + Icon: () => ( + + + + + ), + }, + { + title: "Authentication", + Icon: () => ( + + + + ), + list: [ + { + title: "Email & Password", + href: "/docs/authentication/email-password", + icon: () => ( + + + + ), + }, + { + title: "Social Sign-On", + group: true, + icon: LucideAArrowDown, + href: "/", + }, + { + title: "Apple", + href: "/docs/authentication/apple", + icon: () => ( + + + + ), + }, { title: "Discord", @@ -617,70 +617,70 @@ export const contents: Content[] = [ href: "/docs/integrations/astro", }, - { - title: "Remix", - icon: Icons.remix, - href: "/docs/integrations/remix", - }, - { - title: "Next.js", - icon: Icons.nextJS, - href: "/docs/integrations/next", - }, - { - title: "Nuxt", - icon: Icons.nuxt, - href: "/docs/integrations/nuxt", - }, - { - title: "SvelteKit", - icon: Icons.svelteKit, - href: "/docs/integrations/svelte-kit", - }, - { - title: "SolidStart", - icon: Icons.solidStart, - href: "/docs/integrations/solid-start", - }, - { - title: "TanStack Start", - icon: Icons.tanstack, - href: "/docs/integrations/tanstack", - }, - { - group: true, - title: "Backend", - href: "/docs/integrations", - icon: LucideAArrowDown, - }, - { - title: "Hono", - icon: Icons.hono, - href: "/docs/integrations/hono", - }, - { - title: "Node", - icon: Icons.node, - href: "/docs/integrations/node", - }, - { - title: "Elysia", - icon: Icons.elysia, - href: "/docs/integrations/elysia", - }, - { - group: true, - title: "Mobile & Desktop", - href: "/docs/integrations", - icon: LucideAArrowDown, - }, - { - title: "Expo", - icon: Icons.expo, - href: "/docs/integrations/expo", - }, - ], - }, + { + title: "Remix", + icon: Icons.remix, + href: "/docs/integrations/remix", + }, + { + title: "Next", + icon: Icons.nextJS, + href: "/docs/integrations/next", + }, + { + title: "Nuxt", + icon: Icons.nuxt, + href: "/docs/integrations/nuxt", + }, + { + title: "Svelte Kit", + icon: Icons.svelteKit, + href: "/docs/integrations/svelte-kit", + }, + { + title: "Solid Start", + icon: Icons.solidStart, + href: "/docs/integrations/solid-start", + }, + { + title: "TanStack Start", + icon: Icons.tanstack, + href: "/docs/integrations/tanstack", + }, + { + group: true, + title: "Backend", + href: "/docs/integrations", + icon: LucideAArrowDown, + }, + { + title: "Hono", + icon: Icons.hono, + href: "/docs/integrations/hono", + }, + { + title: "Node", + icon: Icons.node, + href: "/docs/integrations/node", + }, + { + title: "Elysia", + icon: Icons.elysia, + href: "/docs/integrations/elysia", + }, + { + group: true, + title: "Mobile & Desktop", + href: "/docs/integrations", + icon: LucideAArrowDown, + }, + { + title: "Expo", + icon: Icons.expo, + href: "/docs/integrations/expo", + }, + ], + }, { title: "Plugins", Icon: () => ( @@ -707,254 +707,411 @@ export const contents: Content[] = [ icon: () => , }, - { - title: "Two Factor", - icon: () => , - href: "/docs/plugins/2fa", - }, - { - title: "Username", - icon: () => , - href: "/docs/plugins/username", - }, - { - title: "Anonymous", - icon: () => , - href: "/docs/plugins/anonymous", - }, - { - title: "Phone Number", - icon: () => , - href: "/docs/plugins/phone-number", - }, - { - title: "Magic Link", - href: "/docs/plugins/magic-link", - icon: () => , - }, - { - title: "Email OTP", - href: "/docs/plugins/email-otp", - icon: () => , - }, - { - title: "Passkey", - href: "/docs/plugins/passkey", - icon: () => ( - - - - ), - }, - { - title: "Generic OAuth", - href: "/docs/plugins/generic-oauth", - icon: () => ( - - - - - - - ), - }, + { + title: "Two Factor", + icon: () => , + href: "/docs/plugins/2fa", + }, + { + title: "Username", + icon: () => , + href: "/docs/plugins/username", + }, + { + title: "Anonymous", + icon: () => , + href: "/docs/plugins/anonymous", + }, + { + title: "Phone Number", + icon: () => , + href: "/docs/plugins/phone-number", + }, + { + title: "Magic Link", + href: "/docs/plugins/magic-link", + icon: () => , + }, + { + title: "Email OTP", + href: "/docs/plugins/email-otp", + icon: () => , + }, + { + title: "Passkey", + href: "/docs/plugins/passkey", + icon: () => ( + + + + ), + }, + { + title: "Generic OAuth", + href: "/docs/plugins/generic-oauth", + icon: () => ( + + + + + + + ), + }, - { - title: "One Tap", - href: "/docs/plugins/one-tap", - icon: () => ( - - - - ), - }, + { + title: "One Tap", + href: "/docs/plugins/one-tap", + icon: () => ( + + + + ), + }, - { - title: "Authorization", - group: true, - href: "/docs/plugins/1st-party-plugins", - icon: () => , - }, - { - title: "Admin", - href: "/docs/plugins/admin", - icon: () => ( - - - - - - ), - }, - { - title: "Organization", - icon: () => , - href: "/docs/plugins/organization", - }, - { - title: "Utility", - group: true, - href: "/docs/plugins/1st-party-plugins", - icon: () => , - }, - { - title: "Bearer", - icon: () => , - href: "/docs/plugins/bearer", - }, - { - title: "Multi Session", - icon: () => ( - - - - ), - href: "/docs/plugins/multi-session", - }, - { - title: "OAuth Proxy", - href: "/docs/plugins/oauth-proxy", - icon: () => ( - - - - - ), - }, - { - title: "JWT", - icon: () => ( - - - - - - - - ), - href: "/docs/plugins/jwt", - }, - ], - }, - { - title: "Reference", - Icon: () => ( - - - - ), - list: [ - { - title: "Options", - href: "/docs/reference/options", - icon: () => ( - - - - ), - }, - { - title: "Security", - href: "/docs/reference/security", - icon: () => , - }, - ], - }, + { + title: "Authorization", + group: true, + href: "/docs/plugins/1st-party-plugins", + icon: () => , + }, + { + title: "Admin", + href: "/docs/plugins/admin", + icon: () => ( + + + + + + ), + }, + { + title: "Organization", + icon: () => , + href: "/docs/plugins/organization", + }, + { + title: "Utility", + group: true, + href: "/docs/plugins/1st-party-plugins", + icon: () => , + }, + { + title: "Bearer", + icon: () => , + href: "/docs/plugins/bearer", + }, + { + title: "Multi Session", + icon: () => ( + + + + ), + href: "/docs/plugins/multi-session", + }, + { + title: "OAuth Proxy", + href: "/docs/plugins/oauth-proxy", + icon: () => ( + + + + + ), + }, + { + title: "JWT", + icon: () => ( + + + + + + + + ), + href: "/docs/plugins/jwt", + }, + ], + }, + { + title: "Reference", + Icon: () => ( + + + + ), + list: [ + { + title: "Options", + href: "/docs/reference/options", + icon: () => ( + + + + ), + }, + { + title: "Security", + href: "/docs/reference/security", + icon: () => , + }, + ], + }, + { + title: "Guides", + href: "/docs/guides", + Icon: () => ( + + + + ), + list: [ + { + title: "Auth js (next-auth) Migration Guide", + href: "/docs/guides/next-auth-migration-guide", + icon: () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + ), + }, + ], + }, ]; export const examples: Content[] = [ + { + title: "Examples", + href: "/docs/examples/next", + Icon: () => ( + + + + + + ), + list: [ + { + title: "Astro + SolidJs", + href: "/docs/examples/astro", + icon: Icons.astro, + }, + { + title: "Remix", + href: "/docs/examples/remix", + icon: Icons.remix, + }, + { + title: "Next JS", + href: "/docs/examples/next-js", + icon: Icons.nextJS, + }, + { + title: "Nuxt", + href: "/docs/examples/nuxt", + icon: Icons.nuxt, + }, + { + title: "Svelte Kit", + href: "/docs/examples/svelte-kit", + icon: Icons.svelteKit, + }, + ], + }, { title: "Examples", href: "/docs/examples/next", diff --git a/docs/content/docs/guides/next-auth-migration-guide.mdx b/docs/content/docs/guides/next-auth-migration-guide.mdx new file mode 100644 index 00000000..ed607710 --- /dev/null +++ b/docs/content/docs/guides/next-auth-migration-guide.mdx @@ -0,0 +1,174 @@ +--- +title: Migrating from NextAuth to BetterAuth +description: A step-by-step guide to getting started with BetterAuth. +--- + +In this guide, we’ll explore how to seamlessly transition a project from Next Auth to Better Auth while ensuring that no data or functionality is lost. This guide assumes you’re using Next.js as your framework, but it should be applicable to other frameworks as well. + +## Before we get started + + Before we start the migration process, we need to setup BetterAuth in our project. You can use the [installation guide](/docs/guides/installation) to get started. + + + + ### Mapping Existing Columns + + Rather than replacing existing column names in your database, you can map them to Better Auth’s expected structure. This way, you can maintain your existing database schema. + + + #### User Schema + + Next Auth default uesr schema is the same as what is expected by Better Auth so there shouldn't be any problem there. + + #### Session Schema + + We need to map 2 fields in the session schema: + + - expires to expiresAt + - sessionToken to token + + ```ts title="auth.ts" + export const auth = betterAuth({ + //...Other configs + session: { + fields: { + expiresAt: "expires", // or "expires_at" or whatever your existing field is + token: "sessionToken" // or "session_token" or whatever your existing field is + } + }, + }); + ``` + + ### Accounts Schema + + We need to map some fields in the accounts schema. + + - providerAccountId to accountId + - refersh_token to refreshToken + - access_token to accessToken + - access_token_expires to accessTokenExpiresAt + - id_token to idToken + + and you can remove "session_state", "type" and "token_type" fields as they are not needed by Better Auth. + + + ```ts title="auth.ts" + export const auth = betterAuth({ + // Other configs + accounts: { + fields: { + accountId: "providerAccountId", + refreshToken: "refresh_token", + accessToken: "access_token", + accessTokenExpiresAt: "access_token_expires", + idToken: "id_token", + } + }, + }); + ``` + + **NOTE:** If you're using orm adapters, you can also map the fields in the adapter like + + **Example with Prisma** + ```prisma title="schema.prisma" + model Session { + id String @id @default(cuid()) + expires DateTime @map("expiresAt") // Map expires to your existing expires field // [!code highlight] + token String @@map("sessionToken") // Map token to your existing sessionToken field // [!code highlight] + userId String + user User @relation(fields: [userId], references: [id]) + } + ``` + + + + ## Change Route Handler + + If you haven't noticed this in the installation guide, navigate to the `app/api/auth` folder and rename the `[...nextauth]` file to `[...all]` to avoid confusion. Inside the `route.ts` file, add the following code: + + ```typescript title="app/api/auth/[...all]/route.ts" + import { toNextJsHandler } from "better-auth/next-js"; + import { auth } from "~/server/auth"; + + export const { POST, GET } = toNextJsHandler(auth); + ``` + + + + ### Client + + Next, create a file named `auth-client.ts` in the `lib` folder. Add the following code to the file: + + ```typescript + import { createAuthClient } from "better-auth/react"; + export const authClient = createAuthClient({ + baseURL: process.env.BASE_URL! // Your API base URL (optional if it's the same as the frontend) + }) + export const { signIn, signOut, useSession } = authClient; + ``` + + ### Add your social login functions + + change your signIn functions from NextAuth to Better Auth. Here is an example of how to do that for discord: + + ```typescript + import { signIn } from "~/lib/auth-client" + + export const signInDiscord = async () => { + const data = await signIn.social({ + provider: "discord" + }) + return data + } + ``` + + ### Change `useSession` calls + + Change your `useSession` calls from NextAuth to Better Auth. Here is an example of how to do that: + + ```tsx title="Profile.tsx" + import { useSession } from "~/lib/auth-client" + + export const Profile = () => { + const { data } = useSession() + return ( +
+
+                        {JSON.stringify(data, null, 2)}
+                    
+
+ ) + } + ``` +
+ + ### Get Server Session + + To get session data on the server, you can use the auth instance you created in the `auth.ts` file. Here is an example of how to do that: + + ```typescript title="actions.ts" + "use server"; + + import { auth } from "~/lib/auth"; + import { headers } from "next/headers"; + + export const protectedAction = ()=>{ + const session = auth.api.getSession({ + headers: await headers(); + }) + } + ``` + + + + ### Middleware + + To protect routes with middleware see [next middleware guide](/docs/integrations/next#middleware). + +
+ +## Wrapping up + +Congratulations! You’ve successfully migrated from NextAuth to BetterAuth. If you'd like to see a more complete code or a live demo, check out the full implementation with multiple auth added [here](https://github.com/Bekacru/t3-app-better-auth). + +Better Auth provides a lot more features and flexibility, so be sure to explore our docs to see what else you can do with it. diff --git a/packages/better-auth/src/db/schema.ts b/packages/better-auth/src/db/schema.ts index b9f12ce2..22ba4107 100644 --- a/packages/better-auth/src/db/schema.ts +++ b/packages/better-auth/src/db/schema.ts @@ -13,7 +13,15 @@ export const accountSchema = z.object({ /** * Access token expires at */ - expiresAt: z.date().nullish(), + accessTokenExpiresAt: z.date().nullish(), + /** + * Refresh token expires at + */ + refreshTokenExpiresAt: z.date().nullish(), + /** + * The scopes that the user has authorized + */ + scope: z.string().nullish(), /** * Password is only stored in the credential provider */