#
tokens: 49837/50000 31/1099 files (page 9/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 9 of 69. Use http://codebase.md/better-auth/better-auth?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── renovate.json5
│   └── workflows
│       ├── ci.yml
│       ├── e2e.yml
│       ├── preview.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
│   └── settings.json
├── banner-dark.png
├── banner.png
├── biome.json
├── bump.config.ts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── demo
│   ├── expo-example
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app.config.ts
│   │   ├── assets
│   │   │   ├── bg-image.jpeg
│   │   │   ├── fonts
│   │   │   │   └── SpaceMono-Regular.ttf
│   │   │   ├── icon.png
│   │   │   └── images
│   │   │       ├── adaptive-icon.png
│   │   │       ├── favicon.png
│   │   │       ├── logo.png
│   │   │       ├── partial-react-logo.png
│   │   │       ├── react-logo.png
│   │   │       ├── [email protected]
│   │   │       ├── [email protected]
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   └── nextjs
│       ├── .env.example
│       ├── .gitignore
│       ├── app
│       │   ├── (auth)
│       │   │   ├── forget-password
│       │   │   │   └── page.tsx
│       │   │   ├── reset-password
│       │   │   │   └── page.tsx
│       │   │   ├── sign-in
│       │   │   │   ├── loading.tsx
│       │   │   │   └── page.tsx
│       │   │   └── two-factor
│       │   │       ├── otp
│       │   │       │   └── page.tsx
│       │   │       └── page.tsx
│       │   ├── accept-invitation
│       │   │   └── [id]
│       │   │       ├── invitation-error.tsx
│       │   │       └── page.tsx
│       │   ├── admin
│       │   │   └── page.tsx
│       │   ├── api
│       │   │   └── auth
│       │   │       └── [...all]
│       │   │           └── route.ts
│       │   ├── apps
│       │   │   └── register
│       │   │       └── page.tsx
│       │   ├── client-test
│       │   │   └── page.tsx
│       │   ├── dashboard
│       │   │   ├── change-plan.tsx
│       │   │   ├── client.tsx
│       │   │   ├── organization-card.tsx
│       │   │   ├── page.tsx
│       │   │   ├── upgrade-button.tsx
│       │   │   └── user-card.tsx
│       │   ├── device
│       │   │   ├── approve
│       │   │   │   └── page.tsx
│       │   │   ├── denied
│       │   │   │   └── page.tsx
│       │   │   ├── layout.tsx
│       │   │   ├── page.tsx
│       │   │   └── success
│       │   │       └── page.tsx
│       │   ├── favicon.ico
│       │   ├── features.tsx
│       │   ├── fonts
│       │   │   ├── GeistMonoVF.woff
│       │   │   └── GeistVF.woff
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── oauth
│       │   │   └── authorize
│       │   │       ├── concet-buttons.tsx
│       │   │       └── page.tsx
│       │   ├── page.tsx
│       │   └── pricing
│       │       └── page.tsx
│       ├── components
│       │   ├── account-switch.tsx
│       │   ├── blocks
│       │   │   └── pricing.tsx
│       │   ├── logo.tsx
│       │   ├── one-tap.tsx
│       │   ├── sign-in-btn.tsx
│       │   ├── sign-in.tsx
│       │   ├── sign-up.tsx
│       │   ├── theme-provider.tsx
│       │   ├── theme-toggle.tsx
│       │   ├── tier-labels.tsx
│       │   ├── ui
│       │   │   ├── accordion.tsx
│       │   │   ├── alert-dialog.tsx
│       │   │   ├── alert.tsx
│       │   │   ├── aspect-ratio.tsx
│       │   │   ├── avatar.tsx
│       │   │   ├── badge.tsx
│       │   │   ├── breadcrumb.tsx
│       │   │   ├── button.tsx
│       │   │   ├── calendar.tsx
│       │   │   ├── card.tsx
│       │   │   ├── carousel.tsx
│       │   │   ├── chart.tsx
│       │   │   ├── checkbox.tsx
│       │   │   ├── collapsible.tsx
│       │   │   ├── command.tsx
│       │   │   ├── context-menu.tsx
│       │   │   ├── copy-button.tsx
│       │   │   ├── dialog.tsx
│       │   │   ├── drawer.tsx
│       │   │   ├── dropdown-menu.tsx
│       │   │   ├── form.tsx
│       │   │   ├── hover-card.tsx
│       │   │   ├── input-otp.tsx
│       │   │   ├── input.tsx
│       │   │   ├── label.tsx
│       │   │   ├── menubar.tsx
│       │   │   ├── navigation-menu.tsx
│       │   │   ├── pagination.tsx
│       │   │   ├── password-input.tsx
│       │   │   ├── popover.tsx
│       │   │   ├── progress.tsx
│       │   │   ├── radio-group.tsx
│       │   │   ├── resizable.tsx
│       │   │   ├── scroll-area.tsx
│       │   │   ├── select.tsx
│       │   │   ├── separator.tsx
│       │   │   ├── sheet.tsx
│       │   │   ├── skeleton.tsx
│       │   │   ├── slider.tsx
│       │   │   ├── sonner.tsx
│       │   │   ├── switch.tsx
│       │   │   ├── table.tsx
│       │   │   ├── tabs.tsx
│       │   │   ├── tabs2.tsx
│       │   │   ├── textarea.tsx
│       │   │   ├── toast.tsx
│       │   │   ├── toaster.tsx
│       │   │   ├── toggle-group.tsx
│       │   │   ├── toggle.tsx
│       │   │   └── tooltip.tsx
│       │   └── wrapper.tsx
│       ├── components.json
│       ├── hooks
│       │   └── use-toast.ts
│       ├── lib
│       │   ├── auth-client.ts
│       │   ├── auth-types.ts
│       │   ├── auth.ts
│       │   ├── email
│       │   │   ├── invitation.tsx
│       │   │   ├── resend.ts
│       │   │   └── reset-password.tsx
│       │   ├── metadata.ts
│       │   ├── shared.ts
│       │   └── utils.ts
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── proxy.ts
│       ├── public
│       │   ├── __og.png
│       │   ├── _og.png
│       │   ├── favicon
│       │   │   ├── android-chrome-192x192.png
│       │   │   ├── android-chrome-512x512.png
│       │   │   ├── apple-touch-icon.png
│       │   │   ├── favicon-16x16.png
│       │   │   ├── favicon-32x32.png
│       │   │   ├── favicon.ico
│       │   │   ├── light
│       │   │   │   ├── android-chrome-192x192.png
│       │   │   │   ├── android-chrome-512x512.png
│       │   │   │   ├── apple-touch-icon.png
│       │   │   │   ├── favicon-16x16.png
│       │   │   │   ├── favicon-32x32.png
│       │   │   │   ├── favicon.ico
│       │   │   │   └── site.webmanifest
│       │   │   └── site.webmanifest
│       │   ├── logo.svg
│       │   └── og.png
│       ├── README.md
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       └── turbo.json
├── docker-compose.yml
├── docs
│   ├── .env.example
│   ├── .gitignore
│   ├── app
│   │   ├── api
│   │   │   ├── ai-chat
│   │   │   │   └── route.ts
│   │   │   ├── analytics
│   │   │   │   ├── conversation
│   │   │   │   │   └── route.ts
│   │   │   │   ├── event
│   │   │   │   │   └── route.ts
│   │   │   │   └── feedback
│   │   │   │       └── route.ts
│   │   │   ├── chat
│   │   │   │   └── route.ts
│   │   │   ├── og
│   │   │   │   └── route.tsx
│   │   │   ├── og-release
│   │   │   │   └── route.tsx
│   │   │   ├── search
│   │   │   │   └── route.ts
│   │   │   └── support
│   │   │       └── route.ts
│   │   ├── blog
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── blog-list.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   ├── stat-field.tsx
│   │   │   │   └── support.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── changelogs
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── grid-pattern.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   └── stat-field.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── community
│   │   │   ├── _components
│   │   │   │   ├── header.tsx
│   │   │   │   └── stats.tsx
│   │   │   └── page.tsx
│   │   ├── docs
│   │   │   ├── [[...slug]]
│   │   │   │   ├── page.client.tsx
│   │   │   │   └── page.tsx
│   │   │   ├── layout.tsx
│   │   │   └── lib
│   │   │       └── get-llm-text.ts
│   │   ├── global.css
│   │   ├── layout.config.tsx
│   │   ├── layout.tsx
│   │   ├── llms.txt
│   │   │   ├── [...slug]
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   ├── reference
│   │   │   └── route.ts
│   │   ├── sitemap.xml
│   │   ├── static.json
│   │   │   └── route.ts
│   │   └── v1
│   │       ├── _components
│   │       │   └── v1-text.tsx
│   │       ├── bg-line.tsx
│   │       └── page.tsx
│   ├── assets
│   │   ├── Geist.ttf
│   │   └── GeistMono.ttf
│   ├── components
│   │   ├── ai-chat-modal.tsx
│   │   ├── anchor-scroll-fix.tsx
│   │   ├── api-method-tabs.tsx
│   │   ├── api-method.tsx
│   │   ├── banner.tsx
│   │   ├── blocks
│   │   │   └── features.tsx
│   │   ├── builder
│   │   │   ├── beam.tsx
│   │   │   ├── code-tabs
│   │   │   │   ├── code-editor.tsx
│   │   │   │   ├── code-tabs.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── tab-bar.tsx
│   │   │   │   └── theme.ts
│   │   │   ├── index.tsx
│   │   │   ├── sign-in.tsx
│   │   │   ├── sign-up.tsx
│   │   │   ├── social-provider.tsx
│   │   │   ├── store.ts
│   │   │   └── tabs.tsx
│   │   ├── display-techstack.tsx
│   │   ├── divider-text.tsx
│   │   ├── docs
│   │   │   ├── docs.client.tsx
│   │   │   ├── docs.tsx
│   │   │   ├── layout
│   │   │   │   ├── nav.tsx
│   │   │   │   ├── theme-toggle.tsx
│   │   │   │   ├── toc-thumb.tsx
│   │   │   │   └── toc.tsx
│   │   │   ├── page.client.tsx
│   │   │   ├── page.tsx
│   │   │   ├── shared.tsx
│   │   │   └── ui
│   │   │       ├── button.tsx
│   │   │       ├── collapsible.tsx
│   │   │       ├── popover.tsx
│   │   │       └── scroll-area.tsx
│   │   ├── endpoint.tsx
│   │   ├── features.tsx
│   │   ├── floating-ai-search.tsx
│   │   ├── fork-button.tsx
│   │   ├── generate-apple-jwt.tsx
│   │   ├── generate-secret.tsx
│   │   ├── github-stat.tsx
│   │   ├── icons.tsx
│   │   ├── landing
│   │   │   ├── gradient-bg.tsx
│   │   │   ├── grid-pattern.tsx
│   │   │   ├── hero.tsx
│   │   │   ├── section-svg.tsx
│   │   │   ├── section.tsx
│   │   │   ├── spotlight.tsx
│   │   │   └── testimonials.tsx
│   │   ├── logo-context-menu.tsx
│   │   ├── logo.tsx
│   │   ├── markdown-renderer.tsx
│   │   ├── markdown.tsx
│   │   ├── mdx
│   │   │   ├── add-to-cursor.tsx
│   │   │   └── database-tables.tsx
│   │   ├── message-feedback.tsx
│   │   ├── mobile-search-icon.tsx
│   │   ├── nav-bar.tsx
│   │   ├── nav-link.tsx
│   │   ├── nav-mobile.tsx
│   │   ├── promo-card.tsx
│   │   ├── resource-card.tsx
│   │   ├── resource-grid.tsx
│   │   ├── resource-section.tsx
│   │   ├── ripple.tsx
│   │   ├── search-dialog.tsx
│   │   ├── side-bar.tsx
│   │   ├── sidebar-content.tsx
│   │   ├── techstack-icons.tsx
│   │   ├── theme-provider.tsx
│   │   ├── theme-toggler.tsx
│   │   └── ui
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── aside-link.tsx
│   │       ├── aspect-ratio.tsx
│   │       ├── avatar.tsx
│   │       ├── background-beams.tsx
│   │       ├── background-boxes.tsx
│   │       ├── badge.tsx
│   │       ├── breadcrumb.tsx
│   │       ├── button.tsx
│   │       ├── calendar.tsx
│   │       ├── callout.tsx
│   │       ├── card.tsx
│   │       ├── carousel.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── code-block.tsx
│   │       ├── collapsible.tsx
│   │       ├── command.tsx
│   │       ├── context-menu.tsx
│   │       ├── dialog.tsx
│   │       ├── drawer.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── dynamic-code-block.tsx
│   │       ├── fade-in.tsx
│   │       ├── form.tsx
│   │       ├── hover-card.tsx
│   │       ├── input-otp.tsx
│   │       ├── input.tsx
│   │       ├── label.tsx
│   │       ├── menubar.tsx
│   │       ├── navigation-menu.tsx
│   │       ├── pagination.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── radio-group.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── sparkles.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── toggle-group.tsx
│   │       ├── toggle.tsx
│   │       ├── tooltip-docs.tsx
│   │       ├── tooltip.tsx
│   │       └── use-copy-button.tsx
│   ├── components.json
│   ├── content
│   │   ├── blogs
│   │   │   ├── 0-supabase-auth-to-planetscale-migration.mdx
│   │   │   ├── 1-3.mdx
│   │   │   ├── authjs-joins-better-auth.mdx
│   │   │   └── seed-round.mdx
│   │   ├── changelogs
│   │   │   ├── 1-2.mdx
│   │   │   └── 1.0.mdx
│   │   └── docs
│   │       ├── adapters
│   │       │   ├── community-adapters.mdx
│   │       │   ├── drizzle.mdx
│   │       │   ├── mongo.mdx
│   │       │   ├── mssql.mdx
│   │       │   ├── mysql.mdx
│   │       │   ├── other-relational-databases.mdx
│   │       │   ├── postgresql.mdx
│   │       │   ├── prisma.mdx
│   │       │   └── sqlite.mdx
│   │       ├── authentication
│   │       │   ├── apple.mdx
│   │       │   ├── atlassian.mdx
│   │       │   ├── cognito.mdx
│   │       │   ├── discord.mdx
│   │       │   ├── dropbox.mdx
│   │       │   ├── email-password.mdx
│   │       │   ├── facebook.mdx
│   │       │   ├── figma.mdx
│   │       │   ├── github.mdx
│   │       │   ├── gitlab.mdx
│   │       │   ├── google.mdx
│   │       │   ├── huggingface.mdx
│   │       │   ├── kakao.mdx
│   │       │   ├── kick.mdx
│   │       │   ├── line.mdx
│   │       │   ├── linear.mdx
│   │       │   ├── linkedin.mdx
│   │       │   ├── microsoft.mdx
│   │       │   ├── naver.mdx
│   │       │   ├── notion.mdx
│   │       │   ├── other-social-providers.mdx
│   │       │   ├── paypal.mdx
│   │       │   ├── reddit.mdx
│   │       │   ├── roblox.mdx
│   │       │   ├── salesforce.mdx
│   │       │   ├── slack.mdx
│   │       │   ├── spotify.mdx
│   │       │   ├── tiktok.mdx
│   │       │   ├── twitch.mdx
│   │       │   ├── twitter.mdx
│   │       │   ├── vk.mdx
│   │       │   └── zoom.mdx
│   │       ├── basic-usage.mdx
│   │       ├── comparison.mdx
│   │       ├── concepts
│   │       │   ├── api.mdx
│   │       │   ├── cli.mdx
│   │       │   ├── client.mdx
│   │       │   ├── cookies.mdx
│   │       │   ├── database.mdx
│   │       │   ├── email.mdx
│   │       │   ├── hooks.mdx
│   │       │   ├── oauth.mdx
│   │       │   ├── plugins.mdx
│   │       │   ├── rate-limit.mdx
│   │       │   ├── session-management.mdx
│   │       │   ├── typescript.mdx
│   │       │   └── users-accounts.mdx
│   │       ├── examples
│   │       │   ├── astro.mdx
│   │       │   ├── next-js.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   └── svelte-kit.mdx
│   │       ├── guides
│   │       │   ├── auth0-migration-guide.mdx
│   │       │   ├── browser-extension-guide.mdx
│   │       │   ├── clerk-migration-guide.mdx
│   │       │   ├── create-a-db-adapter.mdx
│   │       │   ├── next-auth-migration-guide.mdx
│   │       │   ├── optimizing-for-performance.mdx
│   │       │   ├── saml-sso-with-okta.mdx
│   │       │   ├── supabase-migration-guide.mdx
│   │       │   └── your-first-plugin.mdx
│   │       ├── installation.mdx
│   │       ├── integrations
│   │       │   ├── astro.mdx
│   │       │   ├── convex.mdx
│   │       │   ├── elysia.mdx
│   │       │   ├── expo.mdx
│   │       │   ├── express.mdx
│   │       │   ├── fastify.mdx
│   │       │   ├── hono.mdx
│   │       │   ├── lynx.mdx
│   │       │   ├── nestjs.mdx
│   │       │   ├── next.mdx
│   │       │   ├── nitro.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   ├── solid-start.mdx
│   │       │   ├── svelte-kit.mdx
│   │       │   ├── tanstack.mdx
│   │       │   └── waku.mdx
│   │       ├── introduction.mdx
│   │       ├── meta.json
│   │       ├── plugins
│   │       │   ├── 2fa.mdx
│   │       │   ├── admin.mdx
│   │       │   ├── anonymous.mdx
│   │       │   ├── api-key.mdx
│   │       │   ├── autumn.mdx
│   │       │   ├── bearer.mdx
│   │       │   ├── captcha.mdx
│   │       │   ├── community-plugins.mdx
│   │       │   ├── device-authorization.mdx
│   │       │   ├── dodopayments.mdx
│   │       │   ├── dub.mdx
│   │       │   ├── email-otp.mdx
│   │       │   ├── generic-oauth.mdx
│   │       │   ├── have-i-been-pwned.mdx
│   │       │   ├── jwt.mdx
│   │       │   ├── last-login-method.mdx
│   │       │   ├── magic-link.mdx
│   │       │   ├── mcp.mdx
│   │       │   ├── multi-session.mdx
│   │       │   ├── oauth-proxy.mdx
│   │       │   ├── oidc-provider.mdx
│   │       │   ├── one-tap.mdx
│   │       │   ├── one-time-token.mdx
│   │       │   ├── open-api.mdx
│   │       │   ├── organization.mdx
│   │       │   ├── passkey.mdx
│   │       │   ├── phone-number.mdx
│   │       │   ├── polar.mdx
│   │       │   ├── siwe.mdx
│   │       │   ├── sso.mdx
│   │       │   ├── stripe.mdx
│   │       │   └── username.mdx
│   │       └── reference
│   │           ├── contributing.mdx
│   │           ├── faq.mdx
│   │           ├── options.mdx
│   │           ├── resources.mdx
│   │           ├── security.mdx
│   │           └── telemetry.mdx
│   ├── hooks
│   │   └── use-mobile.ts
│   ├── ignore-build.sh
│   ├── lib
│   │   ├── blog.ts
│   │   ├── chat
│   │   │   └── inkeep-qa-schema.ts
│   │   ├── constants.ts
│   │   ├── export-search-indexes.ts
│   │   ├── inkeep-analytics.ts
│   │   ├── is-active.ts
│   │   ├── metadata.ts
│   │   ├── source.ts
│   │   └── utils.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── proxy.ts
│   ├── public
│   │   ├── avatars
│   │   │   └── beka.jpg
│   │   ├── blogs
│   │   │   ├── authjs-joins.png
│   │   │   ├── seed-round.png
│   │   │   └── supabase-ps.png
│   │   ├── branding
│   │   │   ├── better-auth-brand-assets.zip
│   │   │   ├── better-auth-logo-dark.png
│   │   │   ├── better-auth-logo-dark.svg
│   │   │   ├── better-auth-logo-light.png
│   │   │   ├── better-auth-logo-light.svg
│   │   │   ├── better-auth-logo-wordmark-dark.png
│   │   │   ├── better-auth-logo-wordmark-dark.svg
│   │   │   ├── better-auth-logo-wordmark-light.png
│   │   │   └── better-auth-logo-wordmark-light.svg
│   │   ├── extension-id.png
│   │   ├── favicon
│   │   │   ├── android-chrome-192x192.png
│   │   │   ├── android-chrome-512x512.png
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-16x16.png
│   │   │   ├── favicon-32x32.png
│   │   │   ├── favicon.ico
│   │   │   ├── light
│   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   ├── apple-touch-icon.png
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   └── site.webmanifest
│   │   │   └── site.webmanifest
│   │   ├── images
│   │   │   └── blogs
│   │   │       └── better auth (1).png
│   │   ├── logo.png
│   │   ├── logo.svg
│   │   ├── LogoDark.webp
│   │   ├── LogoLight.webp
│   │   ├── og.png
│   │   ├── open-api-reference.png
│   │   ├── people-say
│   │   │   ├── code-with-antonio.jpg
│   │   │   ├── dagmawi-babi.png
│   │   │   ├── dax.png
│   │   │   ├── dev-ed.png
│   │   │   ├── egoist.png
│   │   │   ├── guillermo-rauch.png
│   │   │   ├── jonathan-wilke.png
│   │   │   ├── josh-tried-coding.jpg
│   │   │   ├── kitze.jpg
│   │   │   ├── lazar-nikolov.png
│   │   │   ├── nizzy.png
│   │   │   ├── omar-mcadam.png
│   │   │   ├── ryan-vogel.jpg
│   │   │   ├── saltyatom.jpg
│   │   │   ├── sebastien-chopin.png
│   │   │   ├── shreyas-mididoddi.png
│   │   │   ├── tech-nerd.png
│   │   │   ├── theo.png
│   │   │   ├── vybhav-bhargav.png
│   │   │   └── xavier-pladevall.jpg
│   │   ├── plus.svg
│   │   ├── release-og
│   │   │   ├── 1-2.png
│   │   │   ├── 1-3.png
│   │   │   └── changelog-og.png
│   │   └── v1-og.png
│   ├── README.md
│   ├── scripts
│   │   ├── endpoint-to-doc
│   │   │   ├── index.ts
│   │   │   ├── input.ts
│   │   │   ├── output.mdx
│   │   │   └── readme.md
│   │   └── sync-orama.ts
│   ├── source.config.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── turbo.json
├── e2e
│   ├── integration
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── solid-vinxi
│   │   │   ├── .gitignore
│   │   │   ├── app.config.ts
│   │   │   ├── e2e
│   │   │   │   ├── test.spec.ts
│   │   │   │   └── utils.ts
│   │   │   ├── package.json
│   │   │   ├── public
│   │   │   │   └── favicon.ico
│   │   │   ├── src
│   │   │   │   ├── app.tsx
│   │   │   │   ├── entry-client.tsx
│   │   │   │   ├── entry-server.tsx
│   │   │   │   ├── global.d.ts
│   │   │   │   ├── lib
│   │   │   │   │   ├── auth-client.ts
│   │   │   │   │   └── auth.ts
│   │   │   │   └── routes
│   │   │   │       ├── [...404].tsx
│   │   │   │       ├── api
│   │   │   │       │   └── auth
│   │   │   │       │       └── [...all].ts
│   │   │   │       └── index.tsx
│   │   │   └── tsconfig.json
│   │   ├── test-utils
│   │   │   ├── package.json
│   │   │   └── src
│   │   │       └── playwright.ts
│   │   └── vanilla-node
│   │       ├── e2e
│   │       │   ├── app.ts
│   │       │   ├── domain.spec.ts
│   │       │   ├── postgres-js.spec.ts
│   │       │   ├── test.spec.ts
│   │       │   └── utils.ts
│   │       ├── index.html
│   │       ├── package.json
│   │       ├── src
│   │       │   ├── main.ts
│   │       │   └── vite-env.d.ts
│   │       ├── tsconfig.json
│   │       └── vite.config.ts
│   └── smoke
│       ├── package.json
│       ├── test
│       │   ├── bun.spec.ts
│       │   ├── cloudflare.spec.ts
│       │   ├── deno.spec.ts
│       │   ├── fixtures
│       │   │   ├── bun-simple.ts
│       │   │   ├── cloudflare
│       │   │   │   ├── .gitignore
│       │   │   │   ├── drizzle
│       │   │   │   │   ├── 0000_clean_vector.sql
│       │   │   │   │   └── meta
│       │   │   │   │       ├── _journal.json
│       │   │   │   │       └── 0000_snapshot.json
│       │   │   │   ├── drizzle.config.ts
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── auth-schema.ts
│       │   │   │   │   ├── db.ts
│       │   │   │   │   └── index.ts
│       │   │   │   ├── test
│       │   │   │   │   ├── apply-migrations.ts
│       │   │   │   │   ├── env.d.ts
│       │   │   │   │   └── index.test.ts
│       │   │   │   ├── tsconfig.json
│       │   │   │   ├── vitest.config.ts
│       │   │   │   ├── worker-configuration.d.ts
│       │   │   │   └── wrangler.json
│       │   │   ├── deno-simple.ts
│       │   │   ├── tsconfig-decelration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── demo.ts
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-exact-optional-property-types
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── index.ts
│       │   │   │   │   ├── organization.ts
│       │   │   │   │   ├── user-additional-fields.ts
│       │   │   │   │   └── username.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-isolated-module-bundler
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-verbatim-module-syntax-node10
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   └── vite
│       │   │       ├── package.json
│       │   │       ├── src
│       │   │       │   ├── client.ts
│       │   │       │   └── server.ts
│       │   │       ├── tsconfig.json
│       │   │       └── vite.config.ts
│       │   ├── ssr.ts
│       │   ├── typecheck.spec.ts
│       │   └── vite.spec.ts
│       └── tsconfig.json
├── LICENSE.md
├── package.json
├── packages
│   ├── better-auth
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── __snapshots__
│   │   │   │   └── init.test.ts.snap
│   │   │   ├── adapters
│   │   │   │   ├── adapter-factory
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── __snapshots__
│   │   │   │   │   │   │   └── adapter-factory.test.ts.snap
│   │   │   │   │   │   └── adapter-factory.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── create-test-suite.ts
│   │   │   │   ├── drizzle-adapter
│   │   │   │   │   ├── drizzle-adapter.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── adapter.drizzle.mysql.test.ts
│   │   │   │   │       ├── adapter.drizzle.pg.test.ts
│   │   │   │   │       ├── adapter.drizzle.sqlite.test.ts
│   │   │   │   │       └── generate-schema.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely-adapter
│   │   │   │   │   ├── bun-sqlite-dialect.ts
│   │   │   │   │   ├── dialect.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── kysely-adapter.ts
│   │   │   │   │   ├── node-sqlite-dialect.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── adapter.kysely.mssql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.mysql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.pg.test.ts
│   │   │   │   │   │   ├── adapter.kysely.sqlite.test.ts
│   │   │   │   │   │   └── node-sqlite-dialect.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── memory-adapter
│   │   │   │   │   ├── adapter.memory.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── memory-adapter.ts
│   │   │   │   ├── mongodb-adapter
│   │   │   │   │   ├── adapter.mongo-db.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mongodb-adapter.ts
│   │   │   │   ├── prisma-adapter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── prisma-adapter.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── base.prisma
│   │   │   │   │       ├── generate-auth-config.ts
│   │   │   │   │       ├── generate-prisma-schema.ts
│   │   │   │   │       ├── get-prisma-client.ts
│   │   │   │   │       ├── prisma.mysql.test.ts
│   │   │   │   │       ├── prisma.pg.test.ts
│   │   │   │   │       ├── prisma.sqlite.test.ts
│   │   │   │   │       └── push-prisma-schema.ts
│   │   │   │   ├── test-adapter.ts
│   │   │   │   ├── test.ts
│   │   │   │   ├── tests
│   │   │   │   │   ├── auth-flow.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── normal.ts
│   │   │   │   │   ├── number-id.ts
│   │   │   │   │   ├── performance.ts
│   │   │   │   │   └── transactions.ts
│   │   │   │   └── utils.ts
│   │   │   ├── api
│   │   │   │   ├── check-endpoint-conflicts.test.ts
│   │   │   │   ├── index.test.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── middlewares
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── origin-check.test.ts
│   │   │   │   │   └── origin-check.ts
│   │   │   │   ├── rate-limiter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── rate-limiter.test.ts
│   │   │   │   ├── routes
│   │   │   │   │   ├── account.test.ts
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── callback.ts
│   │   │   │   │   ├── email-verification.test.ts
│   │   │   │   │   ├── email-verification.ts
│   │   │   │   │   ├── error.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── ok.ts
│   │   │   │   │   ├── reset-password.test.ts
│   │   │   │   │   ├── reset-password.ts
│   │   │   │   │   ├── session-api.test.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── sign-in.test.ts
│   │   │   │   │   ├── sign-in.ts
│   │   │   │   │   ├── sign-out.test.ts
│   │   │   │   │   ├── sign-out.ts
│   │   │   │   │   ├── sign-up.test.ts
│   │   │   │   │   ├── sign-up.ts
│   │   │   │   │   ├── update-user.test.ts
│   │   │   │   │   └── update-user.ts
│   │   │   │   ├── to-auth-endpoints.test.ts
│   │   │   │   └── to-auth-endpoints.ts
│   │   │   ├── auth.test.ts
│   │   │   ├── auth.ts
│   │   │   ├── call.test.ts
│   │   │   ├── client
│   │   │   │   ├── client-ssr.test.ts
│   │   │   │   ├── client.test.ts
│   │   │   │   ├── config.ts
│   │   │   │   ├── fetch-plugins.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── lynx
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── lynx-store.ts
│   │   │   │   ├── parser.ts
│   │   │   │   ├── path-to-object.ts
│   │   │   │   ├── plugins
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── infer-plugin.ts
│   │   │   │   ├── proxy.ts
│   │   │   │   ├── query.ts
│   │   │   │   ├── react
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── react-store.ts
│   │   │   │   ├── session-atom.ts
│   │   │   │   ├── solid
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── solid-store.ts
│   │   │   │   ├── svelte
│   │   │   │   │   └── index.ts
│   │   │   │   ├── test-plugin.ts
│   │   │   │   ├── types.ts
│   │   │   │   ├── url.test.ts
│   │   │   │   ├── vanilla.ts
│   │   │   │   └── vue
│   │   │   │       ├── index.ts
│   │   │   │       └── vue-store.ts
│   │   │   ├── cookies
│   │   │   │   ├── check-cookies.ts
│   │   │   │   ├── cookie-utils.ts
│   │   │   │   ├── cookies.test.ts
│   │   │   │   └── index.ts
│   │   │   ├── crypto
│   │   │   │   ├── buffer.ts
│   │   │   │   ├── hash.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt.ts
│   │   │   │   ├── password.test.ts
│   │   │   │   ├── password.ts
│   │   │   │   └── random.ts
│   │   │   ├── db
│   │   │   │   ├── db.test.ts
│   │   │   │   ├── field.ts
│   │   │   │   ├── get-migration.ts
│   │   │   │   ├── get-schema.ts
│   │   │   │   ├── get-tables.test.ts
│   │   │   │   ├── get-tables.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── internal-adapter.test.ts
│   │   │   │   ├── internal-adapter.ts
│   │   │   │   ├── schema.ts
│   │   │   │   ├── secondary-storage.test.ts
│   │   │   │   ├── to-zod.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── with-hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── init.test.ts
│   │   │   ├── init.ts
│   │   │   ├── integrations
│   │   │   │   ├── next-js.ts
│   │   │   │   ├── node.ts
│   │   │   │   ├── react-start.ts
│   │   │   │   ├── solid-start.ts
│   │   │   │   └── svelte-kit.ts
│   │   │   ├── oauth2
│   │   │   │   ├── index.ts
│   │   │   │   ├── link-account.test.ts
│   │   │   │   ├── link-account.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── utils.ts
│   │   │   ├── plugins
│   │   │   │   ├── access
│   │   │   │   │   ├── access.test.ts
│   │   │   │   │   ├── access.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── additional-fields
│   │   │   │   │   ├── additional-fields.test.ts
│   │   │   │   │   └── client.ts
│   │   │   │   ├── admin
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── admin.test.ts
│   │   │   │   │   ├── admin.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── anonymous
│   │   │   │   │   ├── anon.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── api-key
│   │   │   │   │   ├── api-key.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── create-api-key.ts
│   │   │   │   │   │   ├── delete-all-expired-api-keys.ts
│   │   │   │   │   │   ├── delete-api-key.ts
│   │   │   │   │   │   ├── get-api-key.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── list-api-keys.ts
│   │   │   │   │   │   ├── update-api-key.ts
│   │   │   │   │   │   └── verify-api-key.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── bearer
│   │   │   │   │   ├── bearer.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── captcha
│   │   │   │   │   ├── captcha.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-handlers
│   │   │   │   │       ├── captchafox.ts
│   │   │   │   │       ├── cloudflare-turnstile.ts
│   │   │   │   │       ├── google-recaptcha.ts
│   │   │   │   │       ├── h-captcha.ts
│   │   │   │   │       └── index.ts
│   │   │   │   ├── custom-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-session.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── device-authorization
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── device-authorization.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── schema.ts
│   │   │   │   ├── email-otp
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── email-otp.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── generic-oauth
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── generic-oauth.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── haveibeenpwned
│   │   │   │   │   ├── haveibeenpwned.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── jwt.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── sign.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── last-login-method
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-prefix.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── last-login-method.test.ts
│   │   │   │   ├── magic-link
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── magic-link.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── mcp
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mcp.test.ts
│   │   │   │   ├── multi-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── multi-session.test.ts
│   │   │   │   ├── oauth-proxy
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── oauth-proxy.test.ts
│   │   │   │   ├── oidc-provider
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── oidc.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── ui.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── one-tap
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── one-time-token
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── one-time-token.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── open-api
│   │   │   │   │   ├── generator.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── logo.ts
│   │   │   │   │   └── open-api.test.ts
│   │   │   │   ├── organization
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── call.ts
│   │   │   │   │   ├── client.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── organization-hook.test.ts
│   │   │   │   │   ├── organization.test.ts
│   │   │   │   │   ├── organization.ts
│   │   │   │   │   ├── permission.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── crud-access-control.test.ts
│   │   │   │   │   │   ├── crud-access-control.ts
│   │   │   │   │   │   ├── crud-invites.ts
│   │   │   │   │   │   ├── crud-members.test.ts
│   │   │   │   │   │   ├── crud-members.ts
│   │   │   │   │   │   ├── crud-org.test.ts
│   │   │   │   │   │   ├── crud-org.ts
│   │   │   │   │   │   └── crud-team.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── team.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── passkey
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── passkey.test.ts
│   │   │   │   ├── phone-number
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── phone-number-error.ts
│   │   │   │   │   └── phone-number.test.ts
│   │   │   │   ├── siwe
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── siwe.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── two-factor
│   │   │   │   │   ├── backup-codes
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── constant.ts
│   │   │   │   │   ├── error-code.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── otp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── totp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── two-factor.test.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-two-factor.ts
│   │   │   │   └── username
│   │   │   │       ├── client.ts
│   │   │   │       ├── error-codes.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       └── username.test.ts
│   │   │   ├── social-providers
│   │   │   │   └── index.ts
│   │   │   ├── social.test.ts
│   │   │   ├── test-utils
│   │   │   │   ├── headers.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── test-instance.ts
│   │   │   ├── types
│   │   │   │   ├── adapter.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── models.ts
│   │   │   │   ├── plugins.ts
│   │   │   │   └── types.test.ts
│   │   │   └── utils
│   │   │       ├── await-object.ts
│   │   │       ├── boolean.ts
│   │   │       ├── clone.ts
│   │   │       ├── constants.ts
│   │   │       ├── date.ts
│   │   │       ├── ensure-utc.ts
│   │   │       ├── get-request-ip.ts
│   │   │       ├── hashing.ts
│   │   │       ├── hide-metadata.ts
│   │   │       ├── id.ts
│   │   │       ├── import-util.ts
│   │   │       ├── index.ts
│   │   │       ├── is-atom.ts
│   │   │       ├── is-promise.ts
│   │   │       ├── json.ts
│   │   │       ├── merger.ts
│   │   │       ├── middleware-response.ts
│   │   │       ├── misc.ts
│   │   │       ├── password.ts
│   │   │       ├── plugin-helper.ts
│   │   │       ├── shim.ts
│   │   │       ├── time.ts
│   │   │       ├── url.ts
│   │   │       └── wildcard.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   ├── vitest.config.ts
│   │   └── vitest.setup.ts
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── generate.ts
│   │   │   │   ├── info.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── login.ts
│   │   │   │   ├── mcp.ts
│   │   │   │   ├── migrate.ts
│   │   │   │   └── secret.ts
│   │   │   ├── generators
│   │   │   │   ├── auth-config.ts
│   │   │   │   ├── drizzle.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely.ts
│   │   │   │   ├── prisma.ts
│   │   │   │   └── types.ts
│   │   │   ├── index.ts
│   │   │   └── utils
│   │   │       ├── add-svelte-kit-env-modules.ts
│   │   │       ├── check-package-managers.ts
│   │   │       ├── format-ms.ts
│   │   │       ├── get-config.ts
│   │   │       ├── get-package-info.ts
│   │   │       ├── get-tsconfig-info.ts
│   │   │       └── install-dependencies.ts
│   │   ├── test
│   │   │   ├── __snapshots__
│   │   │   │   ├── auth-schema-mysql-enum.txt
│   │   │   │   ├── auth-schema-mysql-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey.txt
│   │   │   │   ├── auth-schema-mysql.txt
│   │   │   │   ├── auth-schema-number-id.txt
│   │   │   │   ├── auth-schema-pg-enum.txt
│   │   │   │   ├── auth-schema-pg-passkey.txt
│   │   │   │   ├── auth-schema-sqlite-enum.txt
│   │   │   │   ├── auth-schema-sqlite-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey.txt
│   │   │   │   ├── auth-schema-sqlite.txt
│   │   │   │   ├── auth-schema.txt
│   │   │   │   ├── migrations.sql
│   │   │   │   ├── schema-mongodb.prisma
│   │   │   │   ├── schema-mysql-custom.prisma
│   │   │   │   ├── schema-mysql.prisma
│   │   │   │   ├── schema-numberid.prisma
│   │   │   │   └── schema.prisma
│   │   │   ├── generate-all-db.test.ts
│   │   │   ├── generate.test.ts
│   │   │   ├── get-config.test.ts
│   │   │   ├── info.test.ts
│   │   │   └── migrate.test.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   └── tsdown.config.ts
│   ├── core
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── api
│   │   │   │   └── index.ts
│   │   │   ├── async_hooks
│   │   │   │   └── index.ts
│   │   │   ├── context
│   │   │   │   ├── endpoint-context.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── transaction.ts
│   │   │   ├── db
│   │   │   │   ├── adapter
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── plugin.ts
│   │   │   │   ├── schema
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── shared.ts
│   │   │   │   │   ├── user.ts
│   │   │   │   │   └── verification.ts
│   │   │   │   └── type.ts
│   │   │   ├── env
│   │   │   │   ├── color-depth.ts
│   │   │   │   ├── env-impl.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── logger.test.ts
│   │   │   │   └── logger.ts
│   │   │   ├── error
│   │   │   │   ├── codes.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── oauth2
│   │   │   │   ├── client-credentials-token.ts
│   │   │   │   ├── create-authorization-url.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── oauth-provider.ts
│   │   │   │   ├── refresh-access-token.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── validate-authorization-code.ts
│   │   │   ├── social-providers
│   │   │   │   ├── apple.ts
│   │   │   │   ├── atlassian.ts
│   │   │   │   ├── cognito.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── dropbox.ts
│   │   │   │   ├── facebook.ts
│   │   │   │   ├── figma.ts
│   │   │   │   ├── github.ts
│   │   │   │   ├── gitlab.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── huggingface.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kakao.ts
│   │   │   │   ├── kick.ts
│   │   │   │   ├── line.ts
│   │   │   │   ├── linear.ts
│   │   │   │   ├── linkedin.ts
│   │   │   │   ├── microsoft-entra-id.ts
│   │   │   │   ├── naver.ts
│   │   │   │   ├── notion.ts
│   │   │   │   ├── paypal.ts
│   │   │   │   ├── reddit.ts
│   │   │   │   ├── roblox.ts
│   │   │   │   ├── salesforce.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── spotify.ts
│   │   │   │   ├── tiktok.ts
│   │   │   │   ├── twitch.ts
│   │   │   │   ├── twitter.ts
│   │   │   │   ├── vk.ts
│   │   │   │   └── zoom.ts
│   │   │   ├── types
│   │   │   │   ├── context.ts
│   │   │   │   ├── cookie.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── init-options.ts
│   │   │   │   ├── plugin-client.ts
│   │   │   │   └── plugin.ts
│   │   │   └── utils
│   │   │       ├── error-codes.ts
│   │   │       └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── expo
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── expo.test.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── sso
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── index.ts
│   │   │   ├── oidc.test.ts
│   │   │   └── saml.test.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── stripe
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   ├── stripe.test.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   └── telemetry
│       ├── package.json
│       ├── src
│       │   ├── detectors
│       │   │   ├── detect-auth-config.ts
│       │   │   ├── detect-database.ts
│       │   │   ├── detect-framework.ts
│       │   │   ├── detect-project-info.ts
│       │   │   ├── detect-runtime.ts
│       │   │   └── detect-system-info.ts
│       │   ├── index.ts
│       │   ├── project-id.ts
│       │   ├── telemetry.test.ts
│       │   ├── types.ts
│       │   └── utils
│       │       ├── hash.ts
│       │       ├── id.ts
│       │       ├── import-util.ts
│       │       └── package-json.ts
│       ├── tsconfig.json
│       └── tsdown.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── SECURITY.md
├── tsconfig.json
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/packages/better-auth/src/client/lynx/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getClientConfig } from "../config";
  2 | import type {
  3 | 	InferActions,
  4 | 	InferClientAPI,
  5 | 	InferErrorCodes,
  6 | 	IsSignal,
  7 | } from "../types";
  8 | import type {
  9 | 	BetterAuthClientPlugin,
 10 | 	BetterAuthClientOptions,
 11 | } from "@better-auth/core";
 12 | import { createDynamicPathProxy } from "../proxy";
 13 | import type { PrettifyDeep, UnionToIntersection } from "../../types/helper";
 14 | import type {
 15 | 	BetterFetchError,
 16 | 	BetterFetchResponse,
 17 | } from "@better-fetch/fetch";
 18 | import { useStore } from "./lynx-store";
 19 | import type { BASE_ERROR_CODES } from "@better-auth/core/error";
 20 | import type { SessionQueryParams } from "../types";
 21 | 
 22 | function getAtomKey(str: string) {
 23 | 	return `use${capitalizeFirstLetter(str)}`;
 24 | }
 25 | 
 26 | export function capitalizeFirstLetter(str: string) {
 27 | 	return str.charAt(0).toUpperCase() + str.slice(1);
 28 | }
 29 | 
 30 | type InferResolvedHooks<O extends BetterAuthClientOptions> = O extends {
 31 | 	plugins: Array<infer Plugin>;
 32 | }
 33 | 	? UnionToIntersection<
 34 | 			Plugin extends BetterAuthClientPlugin
 35 | 				? Plugin["getAtoms"] extends (fetch: any) => infer Atoms
 36 | 					? Atoms extends Record<string, any>
 37 | 						? {
 38 | 								[key in keyof Atoms as IsSignal<key> extends true
 39 | 									? never
 40 | 									: key extends string
 41 | 										? `use${Capitalize<key>}`
 42 | 										: never]: () => ReturnType<Atoms[key]["get"]>;
 43 | 							}
 44 | 						: {}
 45 | 					: {}
 46 | 				: {}
 47 | 		>
 48 | 	: {};
 49 | 
 50 | export function createAuthClient<Option extends BetterAuthClientOptions>(
 51 | 	options?: Option,
 52 | ) {
 53 | 	const {
 54 | 		pluginPathMethods,
 55 | 		pluginsActions,
 56 | 		pluginsAtoms,
 57 | 		$fetch,
 58 | 		$store,
 59 | 		atomListeners,
 60 | 	} = getClientConfig(options);
 61 | 	let resolvedHooks: Record<string, any> = {};
 62 | 	for (const [key, value] of Object.entries(pluginsAtoms)) {
 63 | 		resolvedHooks[getAtomKey(key)] = () => useStore(value);
 64 | 	}
 65 | 
 66 | 	const routes = {
 67 | 		...pluginsActions,
 68 | 		...resolvedHooks,
 69 | 		$fetch,
 70 | 		$store,
 71 | 	};
 72 | 	const proxy = createDynamicPathProxy(
 73 | 		routes,
 74 | 		$fetch,
 75 | 		pluginPathMethods,
 76 | 		pluginsAtoms,
 77 | 		atomListeners,
 78 | 	);
 79 | 
 80 | 	type ClientAPI = InferClientAPI<Option>;
 81 | 	type Session = ClientAPI extends {
 82 | 		getSession: () => Promise<infer Res>;
 83 | 	}
 84 | 		? Res extends BetterFetchResponse<infer S>
 85 | 			? S
 86 | 			: Res
 87 | 		: never;
 88 | 	return proxy as UnionToIntersection<InferResolvedHooks<Option>> &
 89 | 		ClientAPI &
 90 | 		InferActions<Option> & {
 91 | 			useSession: () => {
 92 | 				data: Session;
 93 | 				isPending: boolean;
 94 | 				error: BetterFetchError | null;
 95 | 				refetch: (queryParams?: { query?: SessionQueryParams }) => void;
 96 | 			};
 97 | 			$Infer: {
 98 | 				Session: NonNullable<Session>;
 99 | 			};
100 | 			$fetch: typeof $fetch;
101 | 			$store: typeof $store;
102 | 			$ERROR_CODES: PrettifyDeep<
103 | 				InferErrorCodes<Option> & typeof BASE_ERROR_CODES
104 | 			>;
105 | 		};
106 | }
107 | 
108 | export { useStore };
109 | export type * from "@better-fetch/fetch";
110 | export type * from "nanostores";
111 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/authentication/microsoft.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Microsoft
 3 | description: Microsoft provider setup and usage.
 4 | ---
 5 | 
 6 | Enabling OAuth with Microsoft Azure Entra ID (formerly Active Directory) allows your users to sign in and sign up to your application with their Microsoft account.
 7 | 
 8 | <Steps>
 9 |     <Step> 
10 |         ### Get your Microsoft credentials
11 |         To use Microsoft as a social provider, you need to get your Microsoft credentials. Which involves generating your own Client ID and Client Secret using your Microsoft Entra ID dashboard account.
12 | 
13 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/microsoft` for local development. For production, you should change it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
14 | 
15 |         see the [Microsoft Entra ID documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) for more information.
16 |     </Step>
17 | 
18 |     <Step>
19 |     ### Configure the provider
20 |     To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.microsoft` in your auth configuration.
21 | 
22 |     ```ts title="auth.ts"
23 |     import { betterAuth } from "better-auth"
24 | 
25 |     export const auth = betterAuth({
26 |         socialProviders: {
27 |             microsoft: { // [!code highlight]
28 |                 clientId: process.env.MICROSOFT_CLIENT_ID as string, // [!code highlight]
29 |                 clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string, // [!code highlight]
30 |                 // Optional
31 |                 tenantId: 'common', // [!code highlight]                
32 |                 authority: "https://login.microsoftonline.com", // Authentication authority URL // [!code highlight]
33 |                 prompt: "select_account", // Forces account selection // [!code highlight]
34 |             }, // [!code highlight]
35 |         },
36 |     })
37 |     ```
38 |     
39 |     **Authority URL**: Use the default `https://login.microsoftonline.com` for standard Entra ID scenarios or `https://<tenant-id>.ciamlogin.com` for CIAM (Customer Identity and Access Management) scenarios.
40 |     
41 |     </Step>
42 | 
43 | </Steps>
44 | 
45 | ## Sign In with Microsoft
46 | 
47 | To sign in with Microsoft, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
48 | 
49 | - `provider`: The provider to use. It should be set to `microsoft`.
50 | 
51 | ```ts title="auth-client.ts"
52 | import { createAuthClient } from "better-auth/client";
53 | 
54 | const authClient = createAuthClient();
55 | 
56 | const signIn = async () => {
57 |   const data = await authClient.signIn.social({
58 |     provider: "microsoft",
59 |     callbackURL: "/dashboard", // The URL to redirect to after the sign in
60 |   });
61 | };
62 | ```
63 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/linear.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	refreshAccessToken,
  6 | 	validateAuthorizationCode,
  7 | } from "../oauth2";
  8 | 
  9 | interface LinearUser {
 10 | 	id: string;
 11 | 	name: string;
 12 | 	email: string;
 13 | 	avatarUrl?: string;
 14 | 	active: boolean;
 15 | 	createdAt: string;
 16 | 	updatedAt: string;
 17 | }
 18 | 
 19 | export interface LinearProfile {
 20 | 	data: {
 21 | 		viewer: LinearUser;
 22 | 	};
 23 | }
 24 | 
 25 | export interface LinearOptions extends ProviderOptions<LinearUser> {
 26 | 	clientId: string;
 27 | }
 28 | 
 29 | export const linear = (options: LinearOptions) => {
 30 | 	const tokenEndpoint = "https://api.linear.app/oauth/token";
 31 | 	return {
 32 | 		id: "linear",
 33 | 		name: "Linear",
 34 | 		createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
 35 | 			const _scopes = options.disableDefaultScope ? [] : ["read"];
 36 | 			options.scope && _scopes.push(...options.scope);
 37 | 			scopes && _scopes.push(...scopes);
 38 | 			return createAuthorizationURL({
 39 | 				id: "linear",
 40 | 				options,
 41 | 				authorizationEndpoint: "https://linear.app/oauth/authorize",
 42 | 				scopes: _scopes,
 43 | 				state,
 44 | 				redirectURI,
 45 | 				loginHint,
 46 | 			});
 47 | 		},
 48 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 49 | 			return validateAuthorizationCode({
 50 | 				code,
 51 | 				redirectURI,
 52 | 				options,
 53 | 				tokenEndpoint,
 54 | 			});
 55 | 		},
 56 | 		refreshAccessToken: options.refreshAccessToken
 57 | 			? options.refreshAccessToken
 58 | 			: async (refreshToken) => {
 59 | 					return refreshAccessToken({
 60 | 						refreshToken,
 61 | 						options: {
 62 | 							clientId: options.clientId,
 63 | 							clientKey: options.clientKey,
 64 | 							clientSecret: options.clientSecret,
 65 | 						},
 66 | 						tokenEndpoint,
 67 | 					});
 68 | 				},
 69 | 		async getUserInfo(token) {
 70 | 			if (options.getUserInfo) {
 71 | 				return options.getUserInfo(token);
 72 | 			}
 73 | 
 74 | 			const { data: profile, error } = await betterFetch<LinearProfile>(
 75 | 				"https://api.linear.app/graphql",
 76 | 				{
 77 | 					method: "POST",
 78 | 					headers: {
 79 | 						"Content-Type": "application/json",
 80 | 						Authorization: `Bearer ${token.accessToken}`,
 81 | 					},
 82 | 					body: JSON.stringify({
 83 | 						query: `
 84 | 							query {
 85 | 								viewer {
 86 | 									id
 87 | 									name
 88 | 									email
 89 | 									avatarUrl
 90 | 									active
 91 | 									createdAt
 92 | 									updatedAt
 93 | 								}
 94 | 							}
 95 | 						`,
 96 | 					}),
 97 | 				},
 98 | 			);
 99 | 			if (error || !profile?.data?.viewer) {
100 | 				return null;
101 | 			}
102 | 
103 | 			const userData = profile.data.viewer;
104 | 			const userMap = await options.mapProfileToUser?.(userData);
105 | 
106 | 			return {
107 | 				user: {
108 | 					id: profile.data.viewer.id,
109 | 					name: profile.data.viewer.name,
110 | 					email: profile.data.viewer.email,
111 | 					image: profile.data.viewer.avatarUrl,
112 | 					emailVerified: true,
113 | 					...userMap,
114 | 				},
115 | 				data: userData,
116 | 			};
117 | 		},
118 | 		options,
119 | 	} satisfies OAuthProvider<LinearUser>;
120 | };
121 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/pagination.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import * as React from "react";
  2 | import {
  3 | 	ChevronLeftIcon,
  4 | 	ChevronRightIcon,
  5 | 	DotsHorizontalIcon,
  6 | } from "@radix-ui/react-icons";
  7 | 
  8 | import { cn } from "@/lib/utils";
  9 | import { ButtonProps, buttonVariants } from "@/components/ui/button";
 10 | 
 11 | const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
 12 | 	<nav
 13 | 		role="navigation"
 14 | 		aria-label="pagination"
 15 | 		className={cn("mx-auto flex w-full justify-center", className)}
 16 | 		{...props}
 17 | 	/>
 18 | );
 19 | Pagination.displayName = "Pagination";
 20 | 
 21 | const PaginationContent = ({
 22 | 	ref,
 23 | 	className,
 24 | 	...props
 25 | }: React.ComponentProps<"ul"> & {
 26 | 	ref: React.RefObject<HTMLUListElement>;
 27 | }) => (
 28 | 	<ul
 29 | 		ref={ref}
 30 | 		className={cn("flex flex-row items-center gap-1", className)}
 31 | 		{...props}
 32 | 	/>
 33 | );
 34 | PaginationContent.displayName = "PaginationContent";
 35 | 
 36 | const PaginationItem = ({
 37 | 	ref,
 38 | 	className,
 39 | 	...props
 40 | }: React.ComponentProps<"li"> & {
 41 | 	ref: React.RefObject<HTMLLIElement>;
 42 | }) => <li ref={ref} className={cn("", className)} {...props} />;
 43 | PaginationItem.displayName = "PaginationItem";
 44 | 
 45 | type PaginationLinkProps = {
 46 | 	isActive?: boolean;
 47 | } & Pick<ButtonProps, "size"> &
 48 | 	React.ComponentProps<"a">;
 49 | 
 50 | const PaginationLink = ({
 51 | 	className,
 52 | 	isActive,
 53 | 	size = "icon",
 54 | 	...props
 55 | }: PaginationLinkProps) => (
 56 | 	<a
 57 | 		aria-current={isActive ? "page" : undefined}
 58 | 		className={cn(
 59 | 			buttonVariants({
 60 | 				variant: isActive ? "outline" : "ghost",
 61 | 				size,
 62 | 			}),
 63 | 			className,
 64 | 		)}
 65 | 		{...props}
 66 | 	/>
 67 | );
 68 | PaginationLink.displayName = "PaginationLink";
 69 | 
 70 | const PaginationPrevious = ({
 71 | 	className,
 72 | 	...props
 73 | }: React.ComponentProps<typeof PaginationLink>) => (
 74 | 	<PaginationLink
 75 | 		aria-label="Go to previous page"
 76 | 		size="default"
 77 | 		className={cn("gap-1 pl-2.5", className)}
 78 | 		{...props}
 79 | 	>
 80 | 		<ChevronLeftIcon className="h-4 w-4" />
 81 | 		<span>Previous</span>
 82 | 	</PaginationLink>
 83 | );
 84 | PaginationPrevious.displayName = "PaginationPrevious";
 85 | 
 86 | const PaginationNext = ({
 87 | 	className,
 88 | 	...props
 89 | }: React.ComponentProps<typeof PaginationLink>) => (
 90 | 	<PaginationLink
 91 | 		aria-label="Go to next page"
 92 | 		size="default"
 93 | 		className={cn("gap-1 pr-2.5", className)}
 94 | 		{...props}
 95 | 	>
 96 | 		<span>Next</span>
 97 | 		<ChevronRightIcon className="h-4 w-4" />
 98 | 	</PaginationLink>
 99 | );
100 | PaginationNext.displayName = "PaginationNext";
101 | 
102 | const PaginationEllipsis = ({
103 | 	className,
104 | 	...props
105 | }: React.ComponentProps<"span">) => (
106 | 	<span
107 | 		aria-hidden
108 | 		className={cn("flex h-9 w-9 items-center justify-center", className)}
109 | 		{...props}
110 | 	>
111 | 		<DotsHorizontalIcon className="h-4 w-4" />
112 | 		<span className="sr-only">More pages</span>
113 | 	</span>
114 | );
115 | PaginationEllipsis.displayName = "PaginationEllipsis";
116 | 
117 | export {
118 | 	Pagination,
119 | 	PaginationContent,
120 | 	PaginationLink,
121 | 	PaginationItem,
122 | 	PaginationPrevious,
123 | 	PaginationNext,
124 | 	PaginationEllipsis,
125 | };
126 | 
```

--------------------------------------------------------------------------------
/docs/scripts/endpoint-to-doc/input.ts:
--------------------------------------------------------------------------------

```typescript
  1 | //@ts-nocheck
  2 | import {
  3 | 	createAuthEndpoint,
  4 | 	sessionMiddleware,
  5 | 	referenceMiddleware,
  6 | } from "./index";
  7 | import { z } from "zod";
  8 | 
  9 | export const restoreSubscription = createAuthEndpoint(
 10 | 	"/subscription/restore",
 11 | 	{
 12 | 		method: "POST",
 13 | 		body: z.object({
 14 | 			referenceId: z
 15 | 				.string({
 16 | 					description: "Reference id of the subscription to restore. Eg: '123'",
 17 | 				})
 18 | 				.optional(),
 19 | 			subscriptionId: z.string({
 20 | 				description: "The id of the subscription to restore. Eg: 'sub_123'",
 21 | 			}),
 22 | 		}),
 23 | 		use: [sessionMiddleware, referenceMiddleware("restore-subscription")],
 24 | 	},
 25 | 	async (ctx) => {
 26 | 		const referenceId = ctx.body?.referenceId || ctx.context.session.user.id;
 27 | 
 28 | 		const subscription = ctx.body.subscriptionId
 29 | 			? await ctx.context.adapter.findOne<Subscription>({
 30 | 					model: "subscription",
 31 | 					where: [
 32 | 						{
 33 | 							field: "id",
 34 | 							value: ctx.body.subscriptionId,
 35 | 						},
 36 | 					],
 37 | 				})
 38 | 			: await ctx.context.adapter
 39 | 					.findMany<Subscription>({
 40 | 						model: "subscription",
 41 | 						where: [
 42 | 							{
 43 | 								field: "referenceId",
 44 | 								value: referenceId,
 45 | 							},
 46 | 						],
 47 | 					})
 48 | 					.then((subs) =>
 49 | 						subs.find(
 50 | 							(sub) => sub.status === "active" || sub.status === "trialing",
 51 | 						),
 52 | 					);
 53 | 		if (!subscription || !subscription.stripeCustomerId) {
 54 | 			throw ctx.error("BAD_REQUEST", {
 55 | 				message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
 56 | 			});
 57 | 		}
 58 | 		if (subscription.status != "active" && subscription.status != "trialing") {
 59 | 			throw ctx.error("BAD_REQUEST", {
 60 | 				message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE,
 61 | 			});
 62 | 		}
 63 | 		if (!subscription.cancelAtPeriodEnd) {
 64 | 			throw ctx.error("BAD_REQUEST", {
 65 | 				message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION,
 66 | 			});
 67 | 		}
 68 | 
 69 | 		const activeSubscription = await client.subscriptions
 70 | 			.list({
 71 | 				customer: subscription.stripeCustomerId,
 72 | 			})
 73 | 			.then(
 74 | 				(res) =>
 75 | 					res.data.filter(
 76 | 						(sub) => sub.status === "active" || sub.status === "trialing",
 77 | 					)[0],
 78 | 			);
 79 | 		if (!activeSubscription) {
 80 | 			throw ctx.error("BAD_REQUEST", {
 81 | 				message: STRIPE_ERROR_CODES.SUBSCRIPTION_NOT_FOUND,
 82 | 			});
 83 | 		}
 84 | 
 85 | 		try {
 86 | 			const newSub = await client.subscriptions.update(activeSubscription.id, {
 87 | 				cancel_at_period_end: false,
 88 | 			});
 89 | 
 90 | 			await ctx.context.adapter.update({
 91 | 				model: "subscription",
 92 | 				update: {
 93 | 					cancelAtPeriodEnd: false,
 94 | 					updatedAt: new Date(),
 95 | 				},
 96 | 				where: [
 97 | 					{
 98 | 						field: "id",
 99 | 						value: subscription.id,
100 | 					},
101 | 				],
102 | 			});
103 | 
104 | 			return ctx.json(newSub);
105 | 		} catch (error) {
106 | 			ctx.context.logger.error("Error restoring subscription", error);
107 | 			throw new APIError("BAD_REQUEST", {
108 | 				message: STRIPE_ERROR_CODES.UNABLE_TO_CREATE_CUSTOMER,
109 | 			});
110 | 		}
111 | 	},
112 | );
113 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-pg-passkey.txt:
--------------------------------------------------------------------------------

```
 1 | import {
 2 |   pgTable,
 3 |   text,
 4 |   timestamp,
 5 |   boolean,
 6 |   integer,
 7 | } from "drizzle-orm/pg-core";
 8 | 
 9 | export const custom_user = pgTable("custom_user", {
10 |   id: text("id").primaryKey(),
11 |   name: text("name").notNull(),
12 |   email: text("email").notNull().unique(),
13 |   emailVerified: boolean("email_verified").default(false).notNull(),
14 |   image: text("image"),
15 |   createdAt: timestamp("created_at").defaultNow().notNull(),
16 |   updatedAt: timestamp("updated_at")
17 |     .defaultNow()
18 |     .$onUpdate(() => /* @__PURE__ */ new Date())
19 |     .notNull(),
20 | });
21 | 
22 | export const custom_session = pgTable("custom_session", {
23 |   id: text("id").primaryKey(),
24 |   expiresAt: timestamp("expires_at").notNull(),
25 |   token: text("token").notNull().unique(),
26 |   createdAt: timestamp("created_at").defaultNow().notNull(),
27 |   updatedAt: timestamp("updated_at")
28 |     .$onUpdate(() => /* @__PURE__ */ new Date())
29 |     .notNull(),
30 |   ipAddress: text("ip_address"),
31 |   userAgent: text("user_agent"),
32 |   userId: text("user_id")
33 |     .notNull()
34 |     .references(() => custom_user.id, { onDelete: "cascade" }),
35 | });
36 | 
37 | export const custom_account = pgTable("custom_account", {
38 |   id: text("id").primaryKey(),
39 |   accountId: text("account_id").notNull(),
40 |   providerId: text("provider_id").notNull(),
41 |   userId: text("user_id")
42 |     .notNull()
43 |     .references(() => custom_user.id, { onDelete: "cascade" }),
44 |   accessToken: text("access_token"),
45 |   refreshToken: text("refresh_token"),
46 |   idToken: text("id_token"),
47 |   accessTokenExpiresAt: timestamp("access_token_expires_at"),
48 |   refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
49 |   scope: text("scope"),
50 |   password: text("password"),
51 |   createdAt: timestamp("created_at").defaultNow().notNull(),
52 |   updatedAt: timestamp("updated_at")
53 |     .$onUpdate(() => /* @__PURE__ */ new Date())
54 |     .notNull(),
55 | });
56 | 
57 | export const custom_verification = pgTable("custom_verification", {
58 |   id: text("id").primaryKey(),
59 |   identifier: text("identifier").notNull(),
60 |   value: text("value").notNull(),
61 |   expiresAt: timestamp("expires_at").notNull(),
62 |   createdAt: timestamp("created_at").defaultNow().notNull(),
63 |   updatedAt: timestamp("updated_at")
64 |     .defaultNow()
65 |     .$onUpdate(() => /* @__PURE__ */ new Date())
66 |     .notNull(),
67 | });
68 | 
69 | export const passkey = pgTable("passkey", {
70 |   id: text("id").primaryKey(),
71 |   name: text("name"),
72 |   publicKey: text("public_key").notNull(),
73 |   userId: text("user_id")
74 |     .notNull()
75 |     .references(() => custom_user.id, { onDelete: "cascade" }),
76 |   credentialID: text("credential_id").notNull(),
77 |   counter: integer("counter").notNull(),
78 |   deviceType: text("device_type").notNull(),
79 |   backedUp: boolean("backed_up").notNull(),
80 |   transports: text("transports"),
81 |   createdAt: timestamp("created_at"),
82 |   aaguid: text("aaguid"),
83 | });
84 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/react/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getClientConfig } from "../config";
  2 | import type {
  3 | 	InferActions,
  4 | 	InferClientAPI,
  5 | 	InferErrorCodes,
  6 | 	IsSignal,
  7 | } from "../types";
  8 | import type {
  9 | 	BetterAuthClientPlugin,
 10 | 	BetterAuthClientOptions,
 11 | } from "@better-auth/core";
 12 | import { createDynamicPathProxy } from "../proxy";
 13 | import type { PrettifyDeep, UnionToIntersection } from "../../types/helper";
 14 | import type {
 15 | 	BetterFetchError,
 16 | 	BetterFetchResponse,
 17 | } from "@better-fetch/fetch";
 18 | import { useStore } from "./react-store";
 19 | import type { BASE_ERROR_CODES } from "@better-auth/core/error";
 20 | import type { SessionQueryParams } from "../types";
 21 | 
 22 | function getAtomKey(str: string) {
 23 | 	return `use${capitalizeFirstLetter(str)}`;
 24 | }
 25 | 
 26 | export function capitalizeFirstLetter(str: string) {
 27 | 	return str.charAt(0).toUpperCase() + str.slice(1);
 28 | }
 29 | 
 30 | type InferResolvedHooks<O extends BetterAuthClientOptions> = O extends {
 31 | 	plugins: Array<infer Plugin>;
 32 | }
 33 | 	? UnionToIntersection<
 34 | 			Plugin extends BetterAuthClientPlugin
 35 | 				? Plugin["getAtoms"] extends (fetch: any) => infer Atoms
 36 | 					? Atoms extends Record<string, any>
 37 | 						? {
 38 | 								[key in keyof Atoms as IsSignal<key> extends true
 39 | 									? never
 40 | 									: key extends string
 41 | 										? `use${Capitalize<key>}`
 42 | 										: never]: () => ReturnType<Atoms[key]["get"]>;
 43 | 							}
 44 | 						: {}
 45 | 					: {}
 46 | 				: {}
 47 | 		>
 48 | 	: {};
 49 | 
 50 | export function createAuthClient<Option extends BetterAuthClientOptions>(
 51 | 	options?: Option,
 52 | ) {
 53 | 	const {
 54 | 		pluginPathMethods,
 55 | 		pluginsActions,
 56 | 		pluginsAtoms,
 57 | 		$fetch,
 58 | 		$store,
 59 | 		atomListeners,
 60 | 	} = getClientConfig(options);
 61 | 	let resolvedHooks: Record<string, any> = {};
 62 | 	for (const [key, value] of Object.entries(pluginsAtoms)) {
 63 | 		resolvedHooks[getAtomKey(key)] = () => useStore(value);
 64 | 	}
 65 | 
 66 | 	const routes = {
 67 | 		...pluginsActions,
 68 | 		...resolvedHooks,
 69 | 		$fetch,
 70 | 		$store,
 71 | 	};
 72 | 	const proxy = createDynamicPathProxy(
 73 | 		routes,
 74 | 		$fetch,
 75 | 		pluginPathMethods,
 76 | 		pluginsAtoms,
 77 | 		atomListeners,
 78 | 	);
 79 | 
 80 | 	type ClientAPI = InferClientAPI<Option>;
 81 | 	type Session = ClientAPI extends {
 82 | 		getSession: () => Promise<infer Res>;
 83 | 	}
 84 | 		? Res extends BetterFetchResponse<infer S>
 85 | 			? S
 86 | 			: Res
 87 | 		: never;
 88 | 	return proxy as UnionToIntersection<InferResolvedHooks<Option>> &
 89 | 		ClientAPI &
 90 | 		InferActions<Option> & {
 91 | 			useSession: () => {
 92 | 				data: Session;
 93 | 				isPending: boolean;
 94 | 				isRefetching: boolean;
 95 | 				error: BetterFetchError | null;
 96 | 				refetch: (queryParams?: { query?: SessionQueryParams }) => void;
 97 | 			};
 98 | 			$Infer: {
 99 | 				Session: NonNullable<Session>;
100 | 			};
101 | 			$fetch: typeof $fetch;
102 | 			$store: typeof $store;
103 | 			$ERROR_CODES: PrettifyDeep<
104 | 				InferErrorCodes<Option> & typeof BASE_ERROR_CODES
105 | 			>;
106 | 		};
107 | }
108 | 
109 | export { useStore };
110 | export type * from "@better-fetch/fetch";
111 | export type * from "nanostores";
112 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/integrations/nestjs.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: NestJS Integration
 3 | description: Integrate Better Auth with NestJS.
 4 | ---
 5 | 
 6 | This guide will show you how to integrate Better Auth with [NestJS](https://nestjs.com/).
 7 | 
 8 | Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
 9 | 
10 | <Callout type="info">
11 | The NestJS integration is **community maintained**. If you encounter any issues, please open them at [nestjs-better-auth](https://github.com/ThallesP/nestjs-better-auth).
12 | </Callout>
13 | 
14 | ## Installation
15 | 
16 | Install the NestJS integration library:
17 | 
18 | ```package-install
19 | @thallesp/nestjs-better-auth
20 | ```
21 | 
22 | ## Basic Setup
23 | 
24 | <Callout type="warn">
25 | Currently the library has beta support for Fastify, if you experience any issues with it, please open an issue at [nestjs-better-auth](https://github.com/ThallesP/nestjs-better-auth).
26 | </Callout>
27 | 
28 | ### 1. Disable Body Parser
29 | 
30 | Disable NestJS's built-in body parser to allow Better Auth to handle the raw request body:
31 | 
32 | ```ts title="main.ts"
33 | import { NestFactory } from "@nestjs/core";
34 | import { AppModule } from "./app.module";
35 | 
36 | async function bootstrap() {
37 |   const app = await NestFactory.create(AppModule, {
38 |     bodyParser: false, // Required for Better Auth
39 |   });
40 |   await app.listen(process.env.PORT ?? 3000);
41 | }
42 | bootstrap();
43 | ```
44 | 
45 | ### 2. Import AuthModule
46 | 
47 | Import the `AuthModule` in your root module:
48 | 
49 | ```ts title="app.module.ts"
50 | import { Module } from '@nestjs/common';
51 | import { AuthModule } from '@thallesp/nestjs-better-auth';
52 | import { auth } from "./auth"; // Your Better Auth instance
53 | 
54 | @Module({
55 |   imports: [
56 |     AuthModule.forRoot({ auth }),
57 |   ],
58 | })
59 | export class AppModule {}
60 | ```
61 | 
62 | ### 3. Route Protection
63 | 
64 | **Global by default**: An `AuthGuard` is registered globally by this module. All routes are protected unless you explicitly allow access.
65 | 
66 | Use the `Session` decorator to access the user session:
67 | 
68 | ```ts title="user.controller.ts"
69 | import { Controller, Get } from '@nestjs/common';
70 | import { Session, UserSession, AllowAnonymous, OptionalAuth } from '@thallesp/nestjs-better-auth';
71 | 
72 | @Controller('users')
73 | export class UserController {
74 |   @Get('me')
75 |   async getProfile(@Session() session: UserSession) {
76 |     return { user: session.user };
77 |   }
78 | 
79 |   @Get('public')
80 |   @AllowAnonymous() // Allow anonymous access
81 |   async getPublic() {
82 |     return { message: 'Public route' };
83 |   }
84 | 
85 |   @Get('optional')
86 |   @OptionalAuth() // Authentication is optional
87 |   async getOptional(@Session() session: UserSession) {
88 |     return { authenticated: !!session };
89 |   }
90 | }
91 | ```
92 | 
93 | ## Full Documentation
94 | 
95 | For comprehensive documentation including decorators, hooks, global guards, and advanced configuration, visit the [NestJS Better Auth repository](https://github.com/thallesp/nestjs-better-auth).
96 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/open-api/open-api.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect } from "vitest";
 2 | import { getTestInstance } from "../../test-utils/test-instance";
 3 | import { openAPI } from ".";
 4 | 
 5 | describe("open-api", async (it) => {
 6 | 	const { auth } = await getTestInstance({
 7 | 		plugins: [openAPI()],
 8 | 		user: {
 9 | 			additionalFields: {
10 | 				role: {
11 | 					type: "string",
12 | 					required: true,
13 | 					defaultValue: "user",
14 | 				},
15 | 				preferences: {
16 | 					type: "string",
17 | 					required: false,
18 | 				},
19 | 			},
20 | 		},
21 | 	});
22 | 
23 | 	it("should generate OpenAPI schema", async () => {
24 | 		const schema = await auth.api.generateOpenAPISchema();
25 | 		expect(schema).toBeDefined();
26 | 	});
27 | 
28 | 	it("should have an id field in the User schema", async () => {
29 | 		const schema = await auth.api.generateOpenAPISchema();
30 | 		const schemas = schema.components.schemas as Record<
31 | 			string,
32 | 			Record<string, any>
33 | 		>;
34 | 		expect(schemas["User"]!.properties.id).toEqual({
35 | 			type: "string",
36 | 		});
37 | 	});
38 | 
39 | 	it("should include additionalFields in the User schema", async () => {
40 | 		const schema = await auth.api.generateOpenAPISchema();
41 | 		const schemas = schema.components.schemas as Record<
42 | 			string,
43 | 			Record<string, any>
44 | 		>;
45 | 
46 | 		expect(schemas["User"]!.properties.role).toEqual({
47 | 			type: "string",
48 | 			default: "user",
49 | 		});
50 | 
51 | 		expect(schemas["User"]!.properties.preferences).toEqual({
52 | 			type: "string",
53 | 		});
54 | 		expect(schemas["User"]!.required).toContain("role");
55 | 		expect(schemas["User"]!.required).not.toContain("preferences");
56 | 	});
57 | 
58 | 	it("should properly handle nested objects in request body schema", async () => {
59 | 		const schema = await auth.api.generateOpenAPISchema();
60 | 		const paths = schema.paths as Record<string, any>;
61 | 
62 | 		const signInSocialPath = paths["/sign-in/social"];
63 | 		expect(signInSocialPath).toBeDefined();
64 | 
65 | 		const requestBody = signInSocialPath.post.requestBody;
66 | 		expect(requestBody).toBeDefined();
67 | 
68 | 		const schema_properties =
69 | 			requestBody.content["application/json"].schema.properties;
70 | 		expect(schema_properties.idToken).toBeDefined();
71 | 		expect(schema_properties.idToken.type).toBe("object");
72 | 		expect(schema_properties.idToken.properties).toBeDefined();
73 | 		expect(schema_properties.idToken.properties.token).toBeDefined();
74 | 		expect(schema_properties.idToken.properties.token.type).toBe("string");
75 | 		expect(schema_properties.idToken.properties.accessToken).toBeDefined();
76 | 		expect(schema_properties.idToken.properties.accessToken.type).toBe(
77 | 			"string",
78 | 		);
79 | 		expect(schema_properties.idToken.properties.refreshToken).toBeDefined();
80 | 		expect(schema_properties.idToken.properties.refreshToken.type).toBe(
81 | 			"string",
82 | 		);
83 | 
84 | 		expect(schema_properties.idToken.required).toContain("token");
85 | 		expect(schema_properties.idToken.required).not.toContain("accessToken");
86 | 		expect(schema_properties.idToken.required).not.toContain("refreshToken");
87 | 	});
88 | });
89 | 
```

--------------------------------------------------------------------------------
/docs/components/mdx/database-tables.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 | 	Table,
  3 | 	TableBody,
  4 | 	TableCell,
  5 | 	TableHead,
  6 | 	TableHeader,
  7 | 	TableRow,
  8 | } from "@/components/ui/table";
  9 | import { Badge } from "@/components/ui/badge";
 10 | import { Key, Link } from "lucide-react";
 11 | import {
 12 | 	Tooltip,
 13 | 	TooltipContent,
 14 | 	TooltipProvider,
 15 | 	TooltipTrigger,
 16 | } from "../ui/tooltip-docs";
 17 | 
 18 | interface Field {
 19 | 	name: string;
 20 | 	type: string;
 21 | 	description: string;
 22 | 	isPrimaryKey?: boolean;
 23 | 	isForeignKey?: boolean;
 24 | 	isOptional?: boolean;
 25 | }
 26 | 
 27 | interface DatabaseTableProps {
 28 | 	fields: Field[];
 29 | }
 30 | 
 31 | export default function DatabaseTable({ fields }: DatabaseTableProps) {
 32 | 	return (
 33 | 		<Table className="my-0">
 34 | 			<TableHeader>
 35 | 				<TableRow className="bg-primary/10 dark:bg-primary/20">
 36 | 					<TableHead className="w-1/6">Field Name</TableHead>
 37 | 					<TableHead className="w-1/6">Type</TableHead>
 38 | 					<TableHead className="w-1/12">Key</TableHead>
 39 | 					<TableHead className="w-1/2">Description</TableHead>
 40 | 				</TableRow>
 41 | 			</TableHeader>
 42 | 			<TableBody>
 43 | 				{fields.map((field, index) => (
 44 | 					<TableRow
 45 | 						key={index}
 46 | 						className={index % 2 === 0 ? "bga-muted/50" : ""}
 47 | 					>
 48 | 						<TableCell className="font-medium">{field.name}</TableCell>
 49 | 						<TableCell className="font-mono text-sm">
 50 | 							<Badge variant="outline">{field.type}</Badge>
 51 | 						</TableCell>
 52 | 						<TableCell>
 53 | 							{field.isPrimaryKey && (
 54 | 								<TooltipProvider delayDuration={0}>
 55 | 									<Tooltip>
 56 | 										<TooltipTrigger>
 57 | 											<Badge
 58 | 												variant="secondary"
 59 | 												className="mr-1 rounded-sm bg-amber-500"
 60 | 											>
 61 | 												<Key className="w-3 h-3 mr-1" size={14} />
 62 | 												PK
 63 | 											</Badge>
 64 | 										</TooltipTrigger>
 65 | 										<TooltipContent>Primary Key</TooltipContent>
 66 | 									</Tooltip>
 67 | 								</TooltipProvider>
 68 | 							)}
 69 | 							{field.isForeignKey && (
 70 | 								<TooltipProvider delayDuration={0}>
 71 | 									<Tooltip>
 72 | 										<TooltipTrigger>
 73 | 											<Badge
 74 | 												variant="secondary"
 75 | 												className="mr-1 rounded-sm bg-blue-500"
 76 | 											>
 77 | 												<Link className="w-3 h-3 mr-1" size={14} />
 78 | 												FK
 79 | 											</Badge>
 80 | 										</TooltipTrigger>
 81 | 										<TooltipContent>Foreign Key</TooltipContent>
 82 | 									</Tooltip>
 83 | 								</TooltipProvider>
 84 | 							)}
 85 | 							{!field.isPrimaryKey &&
 86 | 								!field.isForeignKey &&
 87 | 								!field.isOptional && (
 88 | 									<span className="text-muted text-center">-</span>
 89 | 								)}
 90 | 							{field.isOptional && (
 91 | 								<TooltipProvider delayDuration={0}>
 92 | 									<Tooltip>
 93 | 										<TooltipTrigger>
 94 | 											<Badge variant="outline">?</Badge>
 95 | 										</TooltipTrigger>
 96 | 										<TooltipContent>Optional</TooltipContent>
 97 | 									</Tooltip>
 98 | 								</TooltipProvider>
 99 | 							)}
100 | 						</TableCell>
101 | 						<TableCell>{field.description}</TableCell>
102 | 					</TableRow>
103 | 				))}
104 | 			</TableBody>
105 | 		</Table>
106 | 	);
107 | }
108 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from "zod";
  2 | import { apple } from "./apple";
  3 | import { atlassian } from "./atlassian";
  4 | import { cognito } from "./cognito";
  5 | import { discord } from "./discord";
  6 | import { facebook } from "./facebook";
  7 | import { figma } from "./figma";
  8 | import { github } from "./github";
  9 | import { google } from "./google";
 10 | import { kick } from "./kick";
 11 | import { huggingface } from "./huggingface";
 12 | import { microsoft } from "./microsoft-entra-id";
 13 | import { slack } from "./slack";
 14 | import { notion } from "./notion";
 15 | import { spotify } from "./spotify";
 16 | import { twitch } from "./twitch";
 17 | import { twitter } from "./twitter";
 18 | import { dropbox } from "./dropbox";
 19 | import { linear } from "./linear";
 20 | import { linkedin } from "./linkedin";
 21 | import { gitlab } from "./gitlab";
 22 | import { tiktok } from "./tiktok";
 23 | import { reddit } from "./reddit";
 24 | import { roblox } from "./roblox";
 25 | import { salesforce } from "./salesforce";
 26 | import { vk } from "./vk";
 27 | import { zoom } from "./zoom";
 28 | import { kakao } from "./kakao";
 29 | import { naver } from "./naver";
 30 | import { line } from "./line";
 31 | import { paypal } from "./paypal";
 32 | 
 33 | export const socialProviders = {
 34 | 	apple,
 35 | 	atlassian,
 36 | 	cognito,
 37 | 	discord,
 38 | 	facebook,
 39 | 	figma,
 40 | 	github,
 41 | 	microsoft,
 42 | 	google,
 43 | 	huggingface,
 44 | 	slack,
 45 | 	spotify,
 46 | 	twitch,
 47 | 	twitter,
 48 | 	dropbox,
 49 | 	kick,
 50 | 	linear,
 51 | 	linkedin,
 52 | 	gitlab,
 53 | 	tiktok,
 54 | 	reddit,
 55 | 	roblox,
 56 | 	salesforce,
 57 | 	vk,
 58 | 	zoom,
 59 | 	notion,
 60 | 	kakao,
 61 | 	naver,
 62 | 	line,
 63 | 	paypal,
 64 | };
 65 | 
 66 | export const socialProviderList = Object.keys(socialProviders) as [
 67 | 	"github",
 68 | 	...(keyof typeof socialProviders)[],
 69 | ];
 70 | 
 71 | export const SocialProviderListEnum = z
 72 | 	.enum(socialProviderList)
 73 | 	.or(z.string()) as z.ZodType<SocialProviderList[number] | (string & {})>;
 74 | 
 75 | export type SocialProvider = z.infer<typeof SocialProviderListEnum>;
 76 | 
 77 | export type SocialProviders = {
 78 | 	[K in SocialProviderList[number]]?: Parameters<
 79 | 		(typeof socialProviders)[K]
 80 | 	>[0] & {
 81 | 		enabled?: boolean;
 82 | 	};
 83 | };
 84 | 
 85 | export * from "./apple";
 86 | export * from "./atlassian";
 87 | export * from "./cognito";
 88 | export * from "./discord";
 89 | export * from "./dropbox";
 90 | export * from "./facebook";
 91 | export * from "./figma";
 92 | export * from "./github";
 93 | export * from "./linear";
 94 | export * from "./linkedin";
 95 | export * from "./gitlab";
 96 | export * from "./google";
 97 | export * from "./kick";
 98 | export * from "./linkedin";
 99 | export * from "./microsoft-entra-id";
100 | export * from "./notion";
101 | export * from "./reddit";
102 | export * from "./roblox";
103 | export * from "./salesforce";
104 | export * from "./spotify";
105 | export * from "./tiktok";
106 | export * from "./twitch";
107 | export * from "./twitter";
108 | export * from "./vk";
109 | export * from "./zoom";
110 | export * from "./kick";
111 | export * from "./huggingface";
112 | export * from "./slack";
113 | export * from "./kakao";
114 | export * from "./naver";
115 | export * from "./line";
116 | export * from "./paypal";
117 | 
118 | export type SocialProviderList = typeof socialProviderList;
119 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/naver.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	validateAuthorizationCode,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | 
  9 | export interface NaverProfile {
 10 | 	/** API response result code */
 11 | 	resultcode: string;
 12 | 	/** API response message */
 13 | 	message: string;
 14 | 	response: {
 15 | 		/** Unique Naver user identifier */
 16 | 		id: string;
 17 | 		/** User nickname */
 18 | 		nickname: string;
 19 | 		/** User real name */
 20 | 		name: string;
 21 | 		/** User email address */
 22 | 		email: string;
 23 | 		/** Gender (F: female, M: male, U: unknown) */
 24 | 		gender: string;
 25 | 		/** Age range */
 26 | 		age: string;
 27 | 		/** Birthday (MM-DD format) */
 28 | 		birthday: string;
 29 | 		/** Birth year */
 30 | 		birthyear: string;
 31 | 		/** Profile image URL */
 32 | 		profile_image: string;
 33 | 		/** Mobile phone number */
 34 | 		mobile: string;
 35 | 	};
 36 | }
 37 | 
 38 | export interface NaverOptions extends ProviderOptions<NaverProfile> {
 39 | 	clientId: string;
 40 | }
 41 | 
 42 | export const naver = (options: NaverOptions) => {
 43 | 	return {
 44 | 		id: "naver",
 45 | 		name: "Naver",
 46 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
 47 | 			const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
 48 | 			options.scope && _scopes.push(...options.scope);
 49 | 			scopes && _scopes.push(...scopes);
 50 | 			return createAuthorizationURL({
 51 | 				id: "naver",
 52 | 				options,
 53 | 				authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
 54 | 				scopes: _scopes,
 55 | 				state,
 56 | 				redirectURI,
 57 | 			});
 58 | 		},
 59 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 60 | 			return validateAuthorizationCode({
 61 | 				code,
 62 | 				redirectURI,
 63 | 				options,
 64 | 				tokenEndpoint: "https://nid.naver.com/oauth2.0/token",
 65 | 			});
 66 | 		},
 67 | 		refreshAccessToken: options.refreshAccessToken
 68 | 			? options.refreshAccessToken
 69 | 			: async (refreshToken) => {
 70 | 					return refreshAccessToken({
 71 | 						refreshToken,
 72 | 						options: {
 73 | 							clientId: options.clientId,
 74 | 							clientKey: options.clientKey,
 75 | 							clientSecret: options.clientSecret,
 76 | 						},
 77 | 						tokenEndpoint: "https://nid.naver.com/oauth2.0/token",
 78 | 					});
 79 | 				},
 80 | 		async getUserInfo(token) {
 81 | 			if (options.getUserInfo) {
 82 | 				return options.getUserInfo(token);
 83 | 			}
 84 | 			const { data: profile, error } = await betterFetch<NaverProfile>(
 85 | 				"https://openapi.naver.com/v1/nid/me",
 86 | 				{
 87 | 					headers: {
 88 | 						Authorization: `Bearer ${token.accessToken}`,
 89 | 					},
 90 | 				},
 91 | 			);
 92 | 			if (error || !profile || profile.resultcode !== "00") {
 93 | 				return null;
 94 | 			}
 95 | 			const userMap = await options.mapProfileToUser?.(profile);
 96 | 			const res = profile.response || {};
 97 | 			const user = {
 98 | 				id: res.id,
 99 | 				name: res.name || res.nickname,
100 | 				email: res.email,
101 | 				image: res.profile_image,
102 | 				emailVerified: false,
103 | 				...userMap,
104 | 			};
105 | 			return {
106 | 				user,
107 | 				data: profile,
108 | 			};
109 | 		},
110 | 		options,
111 | 	} satisfies OAuthProvider<NaverProfile>;
112 | };
113 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/dropbox.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	refreshAccessToken,
  6 | 	validateAuthorizationCode,
  7 | } from "../oauth2";
  8 | 
  9 | export interface DropboxProfile {
 10 | 	account_id: string;
 11 | 	name: {
 12 | 		given_name: string;
 13 | 		surname: string;
 14 | 		familiar_name: string;
 15 | 		display_name: string;
 16 | 		abbreviated_name: string;
 17 | 	};
 18 | 	email: string;
 19 | 	email_verified: boolean;
 20 | 	profile_photo_url: string;
 21 | }
 22 | 
 23 | export interface DropboxOptions extends ProviderOptions<DropboxProfile> {
 24 | 	clientId: string;
 25 | 	accessType?: "offline" | "online" | "legacy";
 26 | }
 27 | 
 28 | export const dropbox = (options: DropboxOptions) => {
 29 | 	const tokenEndpoint = "https://api.dropboxapi.com/oauth2/token";
 30 | 
 31 | 	return {
 32 | 		id: "dropbox",
 33 | 		name: "Dropbox",
 34 | 		createAuthorizationURL: async ({
 35 | 			state,
 36 | 			scopes,
 37 | 			codeVerifier,
 38 | 			redirectURI,
 39 | 		}) => {
 40 | 			const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
 41 | 			options.scope && _scopes.push(...options.scope);
 42 | 			scopes && _scopes.push(...scopes);
 43 | 			const additionalParams: Record<string, string> = {};
 44 | 			if (options.accessType) {
 45 | 				additionalParams.token_access_type = options.accessType;
 46 | 			}
 47 | 			return await createAuthorizationURL({
 48 | 				id: "dropbox",
 49 | 				options,
 50 | 				authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
 51 | 				scopes: _scopes,
 52 | 				state,
 53 | 				redirectURI,
 54 | 				codeVerifier,
 55 | 				additionalParams,
 56 | 			});
 57 | 		},
 58 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 59 | 			return await validateAuthorizationCode({
 60 | 				code,
 61 | 				codeVerifier,
 62 | 				redirectURI,
 63 | 				options,
 64 | 				tokenEndpoint,
 65 | 			});
 66 | 		},
 67 | 		refreshAccessToken: options.refreshAccessToken
 68 | 			? options.refreshAccessToken
 69 | 			: async (refreshToken) => {
 70 | 					return refreshAccessToken({
 71 | 						refreshToken,
 72 | 						options: {
 73 | 							clientId: options.clientId,
 74 | 							clientKey: options.clientKey,
 75 | 							clientSecret: options.clientSecret,
 76 | 						},
 77 | 						tokenEndpoint: "https://api.dropbox.com/oauth2/token",
 78 | 					});
 79 | 				},
 80 | 		async getUserInfo(token) {
 81 | 			if (options.getUserInfo) {
 82 | 				return options.getUserInfo(token);
 83 | 			}
 84 | 			const { data: profile, error } = await betterFetch<DropboxProfile>(
 85 | 				"https://api.dropboxapi.com/2/users/get_current_account",
 86 | 				{
 87 | 					method: "POST",
 88 | 					headers: {
 89 | 						Authorization: `Bearer ${token.accessToken}`,
 90 | 					},
 91 | 				},
 92 | 			);
 93 | 
 94 | 			if (error) {
 95 | 				return null;
 96 | 			}
 97 | 			const userMap = await options.mapProfileToUser?.(profile);
 98 | 			return {
 99 | 				user: {
100 | 					id: profile.account_id,
101 | 					name: profile.name?.display_name,
102 | 					email: profile.email,
103 | 					emailVerified: profile.email_verified || false,
104 | 					image: profile.profile_photo_url,
105 | 					...userMap,
106 | 				},
107 | 				data: profile,
108 | 			};
109 | 		},
110 | 		options,
111 | 	} satisfies OAuthProvider<DropboxProfile>;
112 | };
113 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/authentication/discord.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Discord
 3 | description: Discord provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your Discord credentials
 9 |         To use Discord sign in, you need a client ID and client secret. You can get them from the [Discord Developer Portal](https://discord.com/developers/applications).
10 | 
11 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/discord` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
12 |     </Step>
13 | 
14 |   <Step>
15 |         ### Configure the provider
16 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
17 | 
18 |         ```ts title="auth.ts" 
19 |         import { betterAuth } from "better-auth"
20 |         
21 |         export const auth = betterAuth({ 
22 |             socialProviders: {
23 |                 discord: { // [!code highlight]
24 |                     clientId: process.env.DISCORD_CLIENT_ID as string, // [!code highlight]
25 |                     clientSecret: process.env.DISCORD_CLIENT_SECRET as string, // [!code highlight]
26 |                 }, // [!code highlight]
27 |             },
28 |         })
29 |         ```
30 |     </Step>
31 | </Steps>
32 | 
33 | ## Usage
34 | 
35 | ### Sign In with Discord 
36 | 
37 | To sign in with Discord, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
38 | - `provider`: The provider to use. It should be set to `discord`.
39 | 
40 | ```ts title="auth-client.ts"
41 | import { createAuthClient } from "better-auth/client"
42 | const authClient =  createAuthClient()
43 | 
44 | const signIn = async () => {
45 |     const data = await authClient.signIn.social({
46 |         provider: "discord"
47 |     })
48 | }
49 | ```
50 | 
51 | ## Options
52 | 
53 | For the full list of options supported by all social providers, check the [Provider Options](/docs/concepts/oauth#provider-options).
54 | 
55 | ### Bot Permissions (Optional)
56 | 
57 | If you're using the `bot` scope with Discord OAuth, you can specify bot permissions using the `permissions` option. It can either be a bitwise value (e.g `2048 | 16384` for Send Messages and Embed Links) or a specific permission value (e.g `16384` for Embed Links).
58 | 
59 | ```ts title="auth.ts" 
60 | import { betterAuth } from "better-auth"
61 | 
62 | export const auth = betterAuth({ 
63 |     socialProviders: {
64 |         discord: {
65 |             clientId: process.env.DISCORD_CLIENT_ID as string,
66 |             clientSecret: process.env.DISCORD_CLIENT_SECRET as string,
67 |             permissions: 2048 | 16384, // Send Messages + Embed Links // [!code highlight]
68 |         }, 
69 |     },
70 | })
71 | ```
72 | 
73 | **Note:** The `permissions` parameter only works when the `bot` scope is included in your OAuth2 scopes. Read more about [Discord bot permissions](https://discord.com/developers/docs/topics/permissions).
```

--------------------------------------------------------------------------------
/packages/better-auth/src/cookies/cookie-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | interface CookieAttributes {
  2 | 	value: string;
  3 | 	"max-age"?: number;
  4 | 	expires?: Date;
  5 | 	domain?: string;
  6 | 	path?: string;
  7 | 	secure?: boolean;
  8 | 	httponly?: boolean;
  9 | 	samesite?: "strict" | "lax" | "none";
 10 | 	[key: string]: any;
 11 | }
 12 | 
 13 | export function parseSetCookieHeader(
 14 | 	setCookie: string,
 15 | ): Map<string, CookieAttributes> {
 16 | 	const cookies = new Map<string, CookieAttributes>();
 17 | 	const cookieArray = setCookie.split(", ");
 18 | 
 19 | 	cookieArray.forEach((cookieString) => {
 20 | 		const parts = cookieString.split(";").map((part) => part.trim());
 21 | 		const [nameValue, ...attributes] = parts;
 22 | 		const [name, ...valueParts] = (nameValue || "").split("=");
 23 | 
 24 | 		const value = valueParts.join("=");
 25 | 
 26 | 		if (!name || value === undefined) {
 27 | 			return;
 28 | 		}
 29 | 
 30 | 		const attrObj: CookieAttributes = { value };
 31 | 
 32 | 		attributes.forEach((attribute) => {
 33 | 			const [attrName, ...attrValueParts] = attribute!.split("=");
 34 | 			const attrValue = attrValueParts.join("=");
 35 | 
 36 | 			const normalizedAttrName = attrName!.trim().toLowerCase();
 37 | 
 38 | 			switch (normalizedAttrName) {
 39 | 				case "max-age":
 40 | 					attrObj["max-age"] = attrValue
 41 | 						? parseInt(attrValue.trim(), 10)
 42 | 						: undefined;
 43 | 					break;
 44 | 				case "expires":
 45 | 					attrObj.expires = attrValue ? new Date(attrValue.trim()) : undefined;
 46 | 					break;
 47 | 				case "domain":
 48 | 					attrObj.domain = attrValue ? attrValue.trim() : undefined;
 49 | 					break;
 50 | 				case "path":
 51 | 					attrObj.path = attrValue ? attrValue.trim() : undefined;
 52 | 					break;
 53 | 				case "secure":
 54 | 					attrObj.secure = true;
 55 | 					break;
 56 | 				case "httponly":
 57 | 					attrObj.httponly = true;
 58 | 					break;
 59 | 				case "samesite":
 60 | 					attrObj.samesite = attrValue
 61 | 						? (attrValue.trim().toLowerCase() as "strict" | "lax" | "none")
 62 | 						: undefined;
 63 | 					break;
 64 | 				default:
 65 | 					// Handle any other attributes
 66 | 					attrObj[normalizedAttrName] = attrValue ? attrValue.trim() : true;
 67 | 					break;
 68 | 			}
 69 | 		});
 70 | 
 71 | 		cookies.set(name, attrObj);
 72 | 	});
 73 | 
 74 | 	return cookies;
 75 | }
 76 | 
 77 | export function setCookieToHeader(headers: Headers) {
 78 | 	return (context: { response: Response }) => {
 79 | 		const setCookieHeader = context.response.headers.get("set-cookie");
 80 | 		if (!setCookieHeader) {
 81 | 			return;
 82 | 		}
 83 | 
 84 | 		const cookieMap = new Map<string, string>();
 85 | 
 86 | 		const existingCookiesHeader = headers.get("cookie") || "";
 87 | 		existingCookiesHeader.split(";").forEach((cookie) => {
 88 | 			const [name, ...rest] = cookie!.trim().split("=");
 89 | 			if (name && rest.length > 0) {
 90 | 				cookieMap.set(name, rest.join("="));
 91 | 			}
 92 | 		});
 93 | 
 94 | 		const setCookieHeaders = setCookieHeader.split(",");
 95 | 		setCookieHeaders.forEach((header) => {
 96 | 			const cookies = parseSetCookieHeader(header);
 97 | 			cookies.forEach((value, name) => {
 98 | 				cookieMap.set(name, value.value);
 99 | 			});
100 | 		});
101 | 
102 | 		const updatedCookies = Array.from(cookieMap.entries())
103 | 			.map(([name, value]) => `${name}=${value}`)
104 | 			.join("; ");
105 | 		headers.set("cookie", updatedCookies);
106 | 	};
107 | }
108 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/(auth)/reset-password/page.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | "use client";
 2 | 
 3 | import { Alert, AlertDescription } from "@/components/ui/alert";
 4 | import { Button } from "@/components/ui/button";
 5 | import {
 6 | 	Card,
 7 | 	CardContent,
 8 | 	CardDescription,
 9 | 	CardHeader,
10 | 	CardTitle,
11 | } from "@/components/ui/card";
12 | import { Label } from "@/components/ui/label";
13 | import { PasswordInput } from "@/components/ui/password-input";
14 | import { client } from "@/lib/auth-client";
15 | import { AlertCircle } from "lucide-react";
16 | import { useRouter } from "next/navigation";
17 | import { useState } from "react";
18 | import { toast } from "sonner";
19 | 
20 | export default function ResetPassword() {
21 | 	const [password, setPassword] = useState("");
22 | 	const [confirmPassword, setConfirmPassword] = useState("");
23 | 	const [isSubmitting, setIsSubmitting] = useState(false);
24 | 	const [error, setError] = useState("");
25 | 	const router = useRouter();
26 | 	async function handleSubmit(e: React.FormEvent) {
27 | 		e.preventDefault();
28 | 		setIsSubmitting(true);
29 | 		setError("");
30 | 		const res = await client.resetPassword({
31 | 			newPassword: password,
32 | 			token: new URLSearchParams(window.location.search).get("token")!,
33 | 		});
34 | 		if (res.error) {
35 | 			toast.error(res.error.message);
36 | 		}
37 | 		setIsSubmitting(false);
38 | 		router.push("/sign-in");
39 | 	}
40 | 	return (
41 | 		<div className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
42 | 			<Card className="w-[350px]">
43 | 				<CardHeader>
44 | 					<CardTitle>Reset password</CardTitle>
45 | 					<CardDescription>
46 | 						Enter new password and confirm it to reset your password
47 | 					</CardDescription>
48 | 				</CardHeader>
49 | 				<CardContent>
50 | 					<form onSubmit={handleSubmit}>
51 | 						<div className="grid w-full items-center gap-2">
52 | 							<div className="flex flex-col space-y-1.5">
53 | 								<Label htmlFor="email">New password</Label>
54 | 								<PasswordInput
55 | 									id="password"
56 | 									value={password}
57 | 									onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
58 | 										setPassword(e.target.value)
59 | 									}
60 | 									autoComplete="password"
61 | 									placeholder="Password"
62 | 								/>
63 | 							</div>
64 | 							<div className="flex flex-col space-y-1.5">
65 | 								<Label htmlFor="email">Confirm password</Label>
66 | 								<PasswordInput
67 | 									id="password"
68 | 									value={confirmPassword}
69 | 									onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
70 | 										setConfirmPassword(e.target.value)
71 | 									}
72 | 									autoComplete="password"
73 | 									placeholder="Password"
74 | 								/>
75 | 							</div>
76 | 						</div>
77 | 						{error && (
78 | 							<Alert variant="destructive" className="mt-4">
79 | 								<AlertCircle className="h-4 w-4" />
80 | 								<AlertDescription>{error}</AlertDescription>
81 | 							</Alert>
82 | 						)}
83 | 						<Button
84 | 							className="w-full mt-4"
85 | 							type="submit"
86 | 							disabled={isSubmitting}
87 | 						>
88 | 							{isSubmitting ? "Resetting..." : "Reset password"}
89 | 						</Button>
90 | 					</form>
91 | 				</CardContent>
92 | 			</Card>
93 | 		</div>
94 | 	);
95 | }
96 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/multi-session.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Multi Session
  3 | description: Learn how to use multi-session plugin in Better Auth.
  4 | ---
  5 | 
  6 | The multi-session plugin allows users to maintain multiple active sessions across different accounts in the same browser. This plugin is useful for applications that require users to switch between multiple accounts without logging out.
  7 | 
  8 | ## Installation
  9 | 
 10 | <Steps>
 11 | <Step>
 12 | ### Add the plugin to your **auth** config
 13 | ```ts title="auth.ts"
 14 | import { betterAuth } from "better-auth"
 15 | import { multiSession } from "better-auth/plugins"
 16 | 
 17 | export const auth = betterAuth({
 18 |     plugins: [ // [!code highlight]
 19 |         multiSession(), // [!code highlight]
 20 |     ] // [!code highlight]
 21 | })
 22 | ```
 23 | </Step>
 24 | <Step>
 25 |         ### Add the client Plugin
 26 | 
 27 |         Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor
 28 | 
 29 |         ```ts title="auth-client.ts"
 30 |         import { createAuthClient } from "better-auth/client"
 31 |         import { multiSessionClient } from "better-auth/client/plugins"
 32 | 
 33 |         export const authClient = createAuthClient({
 34 |             plugins: [
 35 |                 multiSessionClient()
 36 |             ]
 37 |         })
 38 |         ```
 39 |         </Step>
 40 | </Steps>    
 41 | 
 42 | 
 43 | ## Usage
 44 | 
 45 | Whenever a user logs in, the plugin will add additional cookie to the browser. This cookie will be used to maintain multiple sessions across different accounts. 
 46 | 
 47 | 
 48 | ### List all device sessions
 49 | 
 50 | To list all active sessions for the current user, you can call the `listDeviceSessions` method.
 51 | 
 52 | <APIMethod
 53 |   path="/multi-session/list-device-sessions"
 54 |   method="GET"
 55 |   requireSession
 56 | >
 57 | ```ts
 58 | type listDeviceSessions = {
 59 | }
 60 | ```
 61 | </APIMethod>
 62 | 
 63 | ### Set active session
 64 | 
 65 | To set the active session, you can call the `setActive` method.
 66 | 
 67 | <APIMethod
 68 |   path="/multi-session/set-active"
 69 |   method="POST"
 70 |   requireSession
 71 | >
 72 | ```ts
 73 | type setActiveSession = {
 74 |     /**
 75 |      * The session token to set as active. 
 76 |      */
 77 |     sessionToken: string = "some-session-token"
 78 | }
 79 | ```
 80 | </APIMethod>
 81 | 
 82 | ### Revoke a session
 83 | 
 84 | To revoke a session, you can call the `revoke` method.
 85 | 
 86 | <APIMethod
 87 |   path="/multi-session/revoke"
 88 |   method="POST"
 89 |   requireSession
 90 | >
 91 | ```ts
 92 | type revokeDeviceSession = {
 93 |     /**
 94 |      * The session token to revoke. 
 95 |      */
 96 |     sessionToken: string = "some-session-token"
 97 | }
 98 | ```
 99 | </APIMethod>
100 | 
101 | ### Signout and Revoke all sessions
102 | 
103 | When a user logs out, the plugin will revoke all active sessions for the user. You can do this by calling the existing `signOut` method, which handles revoking all sessions automatically.
104 | 
105 | ### Max Sessions
106 | 
107 | You can specify the maximum number of sessions a user can have by passing the `maximumSessions` option to the plugin. By default, the plugin allows 5 sessions per device.
108 | 
109 | ```ts title="auth.ts"
110 | import { betterAuth } from "better-auth"
111 | 
112 | export const auth = betterAuth({
113 |     plugins: [
114 |         multiSession({
115 |             maximumSessions: 3
116 |         })
117 |     ]
118 | })
119 | ```
```

--------------------------------------------------------------------------------
/docs/content/docs/adapters/sqlite.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: SQLite
  3 | description: Integrate Better Auth with SQLite.
  4 | ---
  5 | 
  6 | SQLite is a lightweight, serverless, self-contained SQL database engine that is widely used for local data storage in applications.
  7 | Read more [here.](https://www.sqlite.org/)
  8 | 
  9 | ## Example Usage
 10 | 
 11 | Better Auth supports multiple SQLite drivers. Choose the one that best fits your environment:
 12 | 
 13 | ### Better-SQLite3 (Recommended)
 14 | 
 15 | The most popular and stable SQLite driver for Node.js:
 16 | 
 17 | ```ts title="auth.ts"
 18 | import { betterAuth } from "better-auth";
 19 | import Database from "better-sqlite3";
 20 | 
 21 | export const auth = betterAuth({
 22 |   database: new Database("database.sqlite"),
 23 | });
 24 | ```
 25 | 
 26 | <Callout>
 27 |   For more information, read Kysely's documentation to the
 28 |   [SqliteDialect](https://kysely-org.github.io/kysely-apidoc/classes/SqliteDialect.html).
 29 | </Callout>
 30 | 
 31 | ### Node.js Built-in SQLite (Experimental)
 32 | 
 33 | <Callout type="warning">
 34 |   The `node:sqlite` module is still experimental and may change at any time. It requires Node.js 22.5.0 or later.
 35 | </Callout>
 36 | 
 37 | Starting from Node.js 22.5.0, you can use the built-in [SQLite](https://nodejs.org/api/sqlite.html) module:
 38 | 
 39 | ```ts title="auth.ts"
 40 | import { betterAuth } from "better-auth";
 41 | import { DatabaseSync } from "node:sqlite";
 42 | 
 43 | export const auth = betterAuth({
 44 |   database: new DatabaseSync("database.sqlite"),
 45 | });
 46 | ```
 47 | 
 48 | To run your application with Node.js SQLite:
 49 | ```bash
 50 | node your-app.js
 51 | ```
 52 | 
 53 | ### Bun Built-in SQLite
 54 | 
 55 | You can also use the built-in [SQLite](https://bun.com/docs/api/sqlite) module in Bun, which is similar to the Node.js version:
 56 | 
 57 | ```ts title="auth.ts"
 58 | import { betterAuth } from "better-auth";
 59 | import { Database } from "bun:sqlite";
 60 | export const auth = betterAuth({
 61 |   database: new Database("database.sqlite"),
 62 | });
 63 | ```
 64 | 
 65 | ## Schema generation & migration
 66 | 
 67 | The [Better Auth CLI](/docs/concepts/cli) allows you to generate or migrate
 68 | your database schema based on your Better Auth configuration and plugins.
 69 | 
 70 | <table>
 71 |   <thead>
 72 |     <tr className="border-b">
 73 |       <th>
 74 |         <p className="font-bold text-[16px] mb-1">SQLite Schema Generation</p>
 75 |       </th>
 76 |       <th>
 77 |         <p className="font-bold text-[16px] mb-1">SQLite Schema Migration</p>
 78 |       </th>
 79 |     </tr>
 80 |   </thead>
 81 |   <tbody>
 82 |     <tr className="h-10">
 83 |       <td>✅ Supported</td>
 84 |       <td>✅ Supported</td>
 85 |     </tr>
 86 |   </tbody>
 87 | </table>
 88 | 
 89 | ```bash title="Schema Generation"
 90 | npx @better-auth/cli@latest generate
 91 | ```
 92 | 
 93 | ```bash title="Schema Migration"
 94 | npx @better-auth/cli@latest migrate
 95 | ```
 96 | 
 97 | ## Additional Information
 98 | 
 99 | SQLite is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
100 | 
101 | If you're looking for performance improvements or tips, take a look at our guide to <Link href="/docs/guides/optimizing-for-performance">performance optimizations</Link>.
102 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-sqlite-enum.txt:
--------------------------------------------------------------------------------

```
 1 | import { sql } from "drizzle-orm";
 2 | import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
 3 | 
 4 | export const user = sqliteTable("user", {
 5 |   id: text("id").primaryKey(),
 6 |   name: text("name").notNull(),
 7 |   email: text("email").notNull().unique(),
 8 |   emailVerified: integer("email_verified", { mode: "boolean" })
 9 |     .default(false)
10 |     .notNull(),
11 |   image: text("image"),
12 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
13 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
14 |     .notNull(),
15 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
16 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
17 |     .$onUpdate(() => /* @__PURE__ */ new Date())
18 |     .notNull(),
19 |   priority: text({ enum: ["high", "medium", "low"] }),
20 | });
21 | 
22 | export const session = sqliteTable("session", {
23 |   id: text("id").primaryKey(),
24 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
25 |   token: text("token").notNull().unique(),
26 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
27 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
28 |     .notNull(),
29 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
30 |     .$onUpdate(() => /* @__PURE__ */ new Date())
31 |     .notNull(),
32 |   ipAddress: text("ip_address"),
33 |   userAgent: text("user_agent"),
34 |   userId: text("user_id")
35 |     .notNull()
36 |     .references(() => user.id, { onDelete: "cascade" }),
37 | });
38 | 
39 | export const account = sqliteTable("account", {
40 |   id: text("id").primaryKey(),
41 |   accountId: text("account_id").notNull(),
42 |   providerId: text("provider_id").notNull(),
43 |   userId: text("user_id")
44 |     .notNull()
45 |     .references(() => user.id, { onDelete: "cascade" }),
46 |   accessToken: text("access_token"),
47 |   refreshToken: text("refresh_token"),
48 |   idToken: text("id_token"),
49 |   accessTokenExpiresAt: integer("access_token_expires_at", {
50 |     mode: "timestamp_ms",
51 |   }),
52 |   refreshTokenExpiresAt: integer("refresh_token_expires_at", {
53 |     mode: "timestamp_ms",
54 |   }),
55 |   scope: text("scope"),
56 |   password: text("password"),
57 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
58 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
59 |     .notNull(),
60 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
61 |     .$onUpdate(() => /* @__PURE__ */ new Date())
62 |     .notNull(),
63 | });
64 | 
65 | export const verification = sqliteTable("verification", {
66 |   id: text("id").primaryKey(),
67 |   identifier: text("identifier").notNull(),
68 |   value: text("value").notNull(),
69 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
70 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
71 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
72 |     .notNull(),
73 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
74 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
75 |     .$onUpdate(() => /* @__PURE__ */ new Date())
76 |     .notNull(),
77 | });
78 | 
```

--------------------------------------------------------------------------------
/packages/expo/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { BetterAuthPlugin } from "better-auth";
  2 | import {
  3 | 	APIError,
  4 | 	createAuthEndpoint,
  5 | 	createAuthMiddleware,
  6 | } from "better-auth/api";
  7 | import { z } from "zod";
  8 | 
  9 | export interface ExpoOptions {
 10 | 	/**
 11 | 	 * Override origin header for expo API routes
 12 | 	 */
 13 | 	overrideOrigin?: boolean;
 14 | }
 15 | 
 16 | export const expo = (options?: ExpoOptions) => {
 17 | 	return {
 18 | 		id: "expo",
 19 | 		init: (ctx) => {
 20 | 			const trustedOrigins =
 21 | 				process.env.NODE_ENV === "development" ? ["exp://"] : [];
 22 | 
 23 | 			return {
 24 | 				options: {
 25 | 					trustedOrigins,
 26 | 				},
 27 | 			};
 28 | 		},
 29 | 		async onRequest(request, ctx) {
 30 | 			if (!options?.overrideOrigin || request.headers.get("origin")) {
 31 | 				return;
 32 | 			}
 33 | 			/**
 34 | 			 * To bypass origin check from expo, we need to set the origin header to the expo-origin header
 35 | 			 */
 36 | 			const expoOrigin = request.headers.get("expo-origin");
 37 | 			if (!expoOrigin) {
 38 | 				return;
 39 | 			}
 40 | 			const req = request.clone();
 41 | 			req.headers.set("origin", expoOrigin);
 42 | 			return {
 43 | 				request: req,
 44 | 			};
 45 | 		},
 46 | 		hooks: {
 47 | 			after: [
 48 | 				{
 49 | 					matcher(context) {
 50 | 						return !!(
 51 | 							context.path?.startsWith("/callback") ||
 52 | 							context.path?.startsWith("/oauth2/callback")
 53 | 						);
 54 | 					},
 55 | 					handler: createAuthMiddleware(async (ctx) => {
 56 | 						const headers = ctx.context.responseHeaders;
 57 | 						const location = headers?.get("location");
 58 | 						if (!location) {
 59 | 							return;
 60 | 						}
 61 | 						const isProxyURL = location.includes("/oauth-proxy-callback");
 62 | 						if (isProxyURL) {
 63 | 							return;
 64 | 						}
 65 | 						const trustedOrigins = ctx.context.trustedOrigins.filter(
 66 | 							(origin: string) => !origin.startsWith("http"),
 67 | 						);
 68 | 						const isTrustedOrigin = trustedOrigins.some((origin: string) =>
 69 | 							location?.startsWith(origin),
 70 | 						);
 71 | 						if (!isTrustedOrigin) {
 72 | 							return;
 73 | 						}
 74 | 						const cookie = headers?.get("set-cookie");
 75 | 						if (!cookie) {
 76 | 							return;
 77 | 						}
 78 | 						const url = new URL(location);
 79 | 						url.searchParams.set("cookie", cookie);
 80 | 						ctx.setHeader("location", url.toString());
 81 | 					}),
 82 | 				},
 83 | 			],
 84 | 		},
 85 | 		endpoints: {
 86 | 			expoAuthorizationProxy: createAuthEndpoint(
 87 | 				"/expo-authorization-proxy",
 88 | 				{
 89 | 					method: "GET",
 90 | 					query: z.object({
 91 | 						authorizationURL: z.string(),
 92 | 					}),
 93 | 					metadata: {
 94 | 						isAction: false,
 95 | 					},
 96 | 				},
 97 | 				async (ctx) => {
 98 | 					const { authorizationURL } = ctx.query;
 99 | 					const url = new URL(authorizationURL);
100 | 					const state = url.searchParams.get("state");
101 | 					if (!state) {
102 | 						throw new APIError("BAD_REQUEST", {
103 | 							message: "Unexpected error",
104 | 						});
105 | 					}
106 | 					const stateCookie = ctx.context.createAuthCookie("state", {
107 | 						maxAge: 5 * 60 * 1000, // 5 minutes
108 | 					});
109 | 					await ctx.setSignedCookie(
110 | 						stateCookie.name,
111 | 						state,
112 | 						ctx.context.secret,
113 | 						stateCookie.attributes,
114 | 					);
115 | 					return ctx.redirect(ctx.query.authorizationURL);
116 | 				},
117 | 			),
118 | 		},
119 | 	} satisfies BetterAuthPlugin;
120 | };
121 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/lib/email/invitation.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 | 	Body,
  3 | 	Button,
  4 | 	Container,
  5 | 	Column,
  6 | 	Head,
  7 | 	Heading,
  8 | 	Hr,
  9 | 	Html,
 10 | 	Img,
 11 | 	Link,
 12 | 	Preview,
 13 | 	Row,
 14 | 	Section,
 15 | 	Text,
 16 | 	Tailwind,
 17 | } from "@react-email/components";
 18 | 
 19 | interface BetterAuthInviteUserEmailProps {
 20 | 	username?: string;
 21 | 	invitedByUsername?: string;
 22 | 	invitedByEmail?: string;
 23 | 	teamName?: string;
 24 | 	teamImage?: string;
 25 | 	inviteLink?: string;
 26 | }
 27 | 
 28 | export const InviteUserEmail = ({
 29 | 	username,
 30 | 	invitedByUsername,
 31 | 	invitedByEmail,
 32 | 	teamName,
 33 | 	teamImage,
 34 | 	inviteLink,
 35 | }: BetterAuthInviteUserEmailProps) => {
 36 | 	const previewText = `Join ${invitedByUsername} on BetterAuth`;
 37 | 	return (
 38 | 		<Html>
 39 | 			<Head />
 40 | 			<Preview>{previewText}</Preview>
 41 | 			<Tailwind>
 42 | 				<Body className="bg-white my-auto mx-auto font-sans px-2">
 43 | 					<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
 44 | 						<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
 45 | 							Join <strong>{invitedByUsername}</strong> on{" "}
 46 | 							<strong>Better Auth.</strong>
 47 | 						</Heading>
 48 | 						<Text className="text-black text-[14px] leading-[24px]">
 49 | 							Hello there,
 50 | 						</Text>
 51 | 						<Text className="text-black text-[14px] leading-[24px]">
 52 | 							<strong>{invitedByUsername}</strong> (
 53 | 							<Link
 54 | 								href={`mailto:${invitedByEmail}`}
 55 | 								className="text-blue-600 no-underline"
 56 | 							>
 57 | 								{invitedByEmail}
 58 | 							</Link>
 59 | 							) has invited you to the <strong>{teamName}</strong> team on{" "}
 60 | 							<strong>Better Auth</strong>.
 61 | 						</Text>
 62 | 						<Section>
 63 | 							{teamImage ? (
 64 | 								<Row>
 65 | 									<Column align="left">
 66 | 										<Img
 67 | 											className="rounded-full"
 68 | 											src={teamImage}
 69 | 											width="64"
 70 | 											height="64"
 71 | 											fetchPriority="high"
 72 | 										/>
 73 | 									</Column>
 74 | 								</Row>
 75 | 							) : null}
 76 | 						</Section>
 77 | 						<Section className="text-center mt-[32px] mb-[32px]">
 78 | 							<Button
 79 | 								className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
 80 | 								href={inviteLink}
 81 | 							>
 82 | 								Join the team
 83 | 							</Button>
 84 | 						</Section>
 85 | 						<Text className="text-black text-[14px] leading-[24px]">
 86 | 							or copy and paste this URL into your browser:{" "}
 87 | 							<Link href={inviteLink} className="text-blue-600 no-underline">
 88 | 								{inviteLink}
 89 | 							</Link>
 90 | 						</Text>
 91 | 						<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
 92 | 						<Text className="text-[#666666] text-[12px] leading-[24px]">
 93 | 							This invitation was intended for{" "}
 94 | 							<span className="text-black">{username}</span>. If you were not
 95 | 							expecting this invitation, you can ignore this email.
 96 | 						</Text>
 97 | 					</Container>
 98 | 				</Body>
 99 | 			</Tailwind>
100 | 		</Html>
101 | 	);
102 | };
103 | 
104 | export function reactInvitationEmail(props: BetterAuthInviteUserEmailProps) {
105 | 	console.log(props);
106 | 	return <InviteUserEmail {...props} />;
107 | }
108 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/huggingface.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	validateAuthorizationCode,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | 
  9 | export interface HuggingFaceProfile {
 10 | 	sub: string;
 11 | 	name: string;
 12 | 	preferred_username: string;
 13 | 	profile: string;
 14 | 	picture: string;
 15 | 	website?: string;
 16 | 	email?: string;
 17 | 	email_verified?: boolean;
 18 | 	isPro: boolean;
 19 | 	canPay?: boolean;
 20 | 	orgs?: {
 21 | 		sub: string;
 22 | 		name: string;
 23 | 		picture: string;
 24 | 		preferred_username: string;
 25 | 		isEnterprise: boolean | "plus";
 26 | 		canPay?: boolean;
 27 | 		roleInOrg?: "admin" | "write" | "contributor" | "read";
 28 | 		pendingSSO?: boolean;
 29 | 		missingMFA?: boolean;
 30 | 		resourceGroups?: {
 31 | 			sub: string;
 32 | 			name: string;
 33 | 			role: "admin" | "write" | "contributor" | "read";
 34 | 		}[];
 35 | 	};
 36 | }
 37 | 
 38 | export interface HuggingFaceOptions
 39 | 	extends ProviderOptions<HuggingFaceProfile> {
 40 | 	clientId: string;
 41 | }
 42 | 
 43 | export const huggingface = (options: HuggingFaceOptions) => {
 44 | 	return {
 45 | 		id: "huggingface",
 46 | 		name: "Hugging Face",
 47 | 		createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
 48 | 			const _scopes = options.disableDefaultScope
 49 | 				? []
 50 | 				: ["openid", "profile", "email"];
 51 | 			options.scope && _scopes.push(...options.scope);
 52 | 			scopes && _scopes.push(...scopes);
 53 | 			return createAuthorizationURL({
 54 | 				id: "huggingface",
 55 | 				options,
 56 | 				authorizationEndpoint: "https://huggingface.co/oauth/authorize",
 57 | 				scopes: _scopes,
 58 | 				state,
 59 | 				codeVerifier,
 60 | 				redirectURI,
 61 | 			});
 62 | 		},
 63 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 64 | 			return validateAuthorizationCode({
 65 | 				code,
 66 | 				codeVerifier,
 67 | 				redirectURI,
 68 | 				options,
 69 | 				tokenEndpoint: "https://huggingface.co/oauth/token",
 70 | 			});
 71 | 		},
 72 | 		refreshAccessToken: options.refreshAccessToken
 73 | 			? options.refreshAccessToken
 74 | 			: async (refreshToken) => {
 75 | 					return refreshAccessToken({
 76 | 						refreshToken,
 77 | 						options: {
 78 | 							clientId: options.clientId,
 79 | 							clientKey: options.clientKey,
 80 | 							clientSecret: options.clientSecret,
 81 | 						},
 82 | 						tokenEndpoint: "https://huggingface.co/oauth/token",
 83 | 					});
 84 | 				},
 85 | 		async getUserInfo(token) {
 86 | 			if (options.getUserInfo) {
 87 | 				return options.getUserInfo(token);
 88 | 			}
 89 | 			const { data: profile, error } = await betterFetch<HuggingFaceProfile>(
 90 | 				"https://huggingface.co/oauth/userinfo",
 91 | 				{
 92 | 					method: "GET",
 93 | 					headers: {
 94 | 						Authorization: `Bearer ${token.accessToken}`,
 95 | 					},
 96 | 				},
 97 | 			);
 98 | 			if (error) {
 99 | 				return null;
100 | 			}
101 | 			const userMap = await options.mapProfileToUser?.(profile);
102 | 			return {
103 | 				user: {
104 | 					id: profile.sub,
105 | 					name: profile.name || profile.preferred_username,
106 | 					email: profile.email,
107 | 					image: profile.picture,
108 | 					emailVerified: profile.email_verified ?? false,
109 | 					...userMap,
110 | 				},
111 | 				data: profile,
112 | 			};
113 | 		},
114 | 		options,
115 | 	} satisfies OAuthProvider<HuggingFaceProfile>;
116 | };
117 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/app/page.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { SignInButton, SignInFallback } from "@/components/sign-in-btn";
 2 | import { Suspense } from "react";
 3 | 
 4 | const features = [
 5 | 	{
 6 | 		name: "Email & Password",
 7 | 		link: "https://www.better-auth.com/docs/authentication/email-password",
 8 | 	},
 9 | 	{
10 | 		name: "Organization | Teams",
11 | 		link: "https://www.better-auth.com/docs/plugins/organization",
12 | 	},
13 | 	{
14 | 		name: "Passkeys",
15 | 		link: "https://www.better-auth.com/docs/plugins/passkey",
16 | 	},
17 | 	{
18 | 		name: "Multi Factor",
19 | 		link: "https://www.better-auth.com/docs/plugins/2fa",
20 | 	},
21 | 	{
22 | 		name: "Password Reset",
23 | 		link: "https://www.better-auth.com/docs/authentication/email-password#request-password-reset",
24 | 	},
25 | 	{
26 | 		name: "Email Verification",
27 | 		link: "https://www.better-auth.com/docs/authentication/email-password#email-verification",
28 | 	},
29 | 	{
30 | 		name: "Roles & Permissions",
31 | 		link: "https://www.better-auth.com/docs/plugins/organization#roles",
32 | 	},
33 | 	{
34 | 		name: "Rate Limiting",
35 | 		link: "https://www.better-auth.com/docs/reference/security#rate-limiting",
36 | 	},
37 | 	{
38 | 		name: "Session Management",
39 | 		link: "https://www.better-auth.com/docs/concepts/session-management",
40 | 	},
41 | ];
42 | 
43 | export default async function Home() {
44 | 	return (
45 | 		<div className="min-h-[80vh] flex items-center justify-center overflow-hidden no-visible-scrollbar px-6 md:px-0">
46 | 			<main className="flex flex-col gap-4 row-start-2 items-center justify-center">
47 | 				<div className="flex flex-col gap-1">
48 | 					<h3 className="font-bold text-4xl text-black dark:text-white text-center">
49 | 						Better Auth.
50 | 					</h3>
51 | 					<p className="text-center break-words text-sm md:text-base">
52 | 						Official demo to showcase{" "}
53 | 						<a
54 | 							href="https://better-auth.com"
55 | 							target="_blank"
56 | 							className="italic underline"
57 | 						>
58 | 							better-auth.
59 | 						</a>{" "}
60 | 						features and capabilities. <br />
61 | 					</p>
62 | 				</div>
63 | 				<div className="md:w-10/12 w-full flex flex-col gap-4">
64 | 					<div className="flex flex-col gap-3 pt-2 flex-wrap">
65 | 						<div className="border-y py-2 border-dotted bg-secondary/60 opacity-80">
66 | 							<div className="text-xs flex items-center gap-2 justify-center text-muted-foreground ">
67 | 								<span className="text-center">
68 | 									All features on this demo are implemented with Better Auth
69 | 									without any custom backend code
70 | 								</span>
71 | 							</div>
72 | 						</div>
73 | 						<div className="flex gap-2 justify-center flex-wrap">
74 | 							{features.map((feature) => (
75 | 								<a
76 | 									className="border-b pb-1 text-muted-foreground text-xs cursor-pointer hover:text-foreground duration-150 ease-in-out transition-all hover:border-foreground flex items-center gap-1"
77 | 									key={feature.name}
78 | 									href={feature.link}
79 | 								>
80 | 									{feature.name}
81 | 								</a>
82 | 							))}
83 | 						</div>
84 | 					</div>
85 | 					{/* @ts-ignore */}
86 | 					<Suspense fallback={<SignInFallback />}>
87 | 						{/* @ts-ignore */}
88 | 						<SignInButton />
89 | 					</Suspense>
90 | 				</div>
91 | 			</main>
92 | 		</div>
93 | 	);
94 | }
95 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-mysql-number-id.txt:
--------------------------------------------------------------------------------

```
 1 | import {
 2 |   mysqlTable,
 3 |   varchar,
 4 |   text,
 5 |   timestamp,
 6 |   boolean,
 7 |   int,
 8 | } from "drizzle-orm/mysql-core";
 9 | 
10 | export const custom_user = mysqlTable("custom_user", {
11 |   id: int("id").autoincrement().primaryKey(),
12 |   name: text("name").notNull(),
13 |   email: varchar("email", { length: 255 }).notNull().unique(),
14 |   emailVerified: boolean("email_verified").default(false).notNull(),
15 |   image: text("image"),
16 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
17 |   updatedAt: timestamp("updated_at", { fsp: 3 })
18 |     .defaultNow()
19 |     .$onUpdate(() => /* @__PURE__ */ new Date())
20 |     .notNull(),
21 |   twoFactorEnabled: boolean("two_factor_enabled").default(false),
22 |   username: varchar("username", { length: 255 }).unique(),
23 |   displayUsername: text("display_username"),
24 | });
25 | 
26 | export const custom_session = mysqlTable("custom_session", {
27 |   id: int("id").autoincrement().primaryKey(),
28 |   expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
29 |   token: varchar("token", { length: 255 }).notNull().unique(),
30 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
31 |   updatedAt: timestamp("updated_at", { fsp: 3 })
32 |     .$onUpdate(() => /* @__PURE__ */ new Date())
33 |     .notNull(),
34 |   ipAddress: text("ip_address"),
35 |   userAgent: text("user_agent"),
36 |   userId: int("user_id")
37 |     .notNull()
38 |     .references(() => custom_user.id, { onDelete: "cascade" }),
39 | });
40 | 
41 | export const custom_account = mysqlTable("custom_account", {
42 |   id: int("id").autoincrement().primaryKey(),
43 |   accountId: text("account_id").notNull(),
44 |   providerId: text("provider_id").notNull(),
45 |   userId: int("user_id")
46 |     .notNull()
47 |     .references(() => custom_user.id, { onDelete: "cascade" }),
48 |   accessToken: text("access_token"),
49 |   refreshToken: text("refresh_token"),
50 |   idToken: text("id_token"),
51 |   accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }),
52 |   refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }),
53 |   scope: text("scope"),
54 |   password: text("password"),
55 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
56 |   updatedAt: timestamp("updated_at", { fsp: 3 })
57 |     .$onUpdate(() => /* @__PURE__ */ new Date())
58 |     .notNull(),
59 | });
60 | 
61 | export const custom_verification = mysqlTable("custom_verification", {
62 |   id: int("id").autoincrement().primaryKey(),
63 |   identifier: text("identifier").notNull(),
64 |   value: text("value").notNull(),
65 |   expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
66 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
67 |   updatedAt: timestamp("updated_at", { fsp: 3 })
68 |     .defaultNow()
69 |     .$onUpdate(() => /* @__PURE__ */ new Date())
70 |     .notNull(),
71 | });
72 | 
73 | export const twoFactor = mysqlTable("two_factor", {
74 |   id: int("id").autoincrement().primaryKey(),
75 |   secret: text("secret").notNull(),
76 |   backupCodes: text("backup_codes").notNull(),
77 |   userId: int("user_id")
78 |     .notNull()
79 |     .references(() => custom_user.id, { onDelete: "cascade" }),
80 | });
81 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/figma.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import { BetterAuthError } from "../error";
  3 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  4 | import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
  5 | import { logger } from "../env";
  6 | import { refreshAccessToken } from "../oauth2";
  7 | 
  8 | export interface FigmaProfile {
  9 | 	id: string;
 10 | 	email: string;
 11 | 	handle: string;
 12 | 	img_url: string;
 13 | }
 14 | 
 15 | export interface FigmaOptions extends ProviderOptions<FigmaProfile> {
 16 | 	clientId: string;
 17 | }
 18 | 
 19 | export const figma = (options: FigmaOptions) => {
 20 | 	return {
 21 | 		id: "figma",
 22 | 		name: "Figma",
 23 | 		async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
 24 | 			if (!options.clientId || !options.clientSecret) {
 25 | 				logger.error(
 26 | 					"Client Id and Client Secret are required for Figma. Make sure to provide them in the options.",
 27 | 				);
 28 | 				throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
 29 | 			}
 30 | 			if (!codeVerifier) {
 31 | 				throw new BetterAuthError("codeVerifier is required for Figma");
 32 | 			}
 33 | 
 34 | 			const _scopes = options.disableDefaultScope ? [] : ["file_read"];
 35 | 			options.scope && _scopes.push(...options.scope);
 36 | 			scopes && _scopes.push(...scopes);
 37 | 
 38 | 			const url = await createAuthorizationURL({
 39 | 				id: "figma",
 40 | 				options,
 41 | 				authorizationEndpoint: "https://www.figma.com/oauth",
 42 | 				scopes: _scopes,
 43 | 				state,
 44 | 				codeVerifier,
 45 | 				redirectURI,
 46 | 			});
 47 | 
 48 | 			return url;
 49 | 		},
 50 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 51 | 			return validateAuthorizationCode({
 52 | 				code,
 53 | 				codeVerifier,
 54 | 				redirectURI,
 55 | 				options,
 56 | 				tokenEndpoint: "https://www.figma.com/api/oauth/token",
 57 | 			});
 58 | 		},
 59 | 		refreshAccessToken: options.refreshAccessToken
 60 | 			? options.refreshAccessToken
 61 | 			: async (refreshToken) => {
 62 | 					return refreshAccessToken({
 63 | 						refreshToken,
 64 | 						options: {
 65 | 							clientId: options.clientId,
 66 | 							clientKey: options.clientKey,
 67 | 							clientSecret: options.clientSecret,
 68 | 						},
 69 | 						tokenEndpoint: "https://www.figma.com/api/oauth/token",
 70 | 					});
 71 | 				},
 72 | 		async getUserInfo(token) {
 73 | 			if (options.getUserInfo) {
 74 | 				return options.getUserInfo(token);
 75 | 			}
 76 | 
 77 | 			try {
 78 | 				const { data: profile } = await betterFetch<FigmaProfile>(
 79 | 					"https://api.figma.com/v1/me",
 80 | 					{
 81 | 						headers: {
 82 | 							Authorization: `Bearer ${token.accessToken}`,
 83 | 						},
 84 | 					},
 85 | 				);
 86 | 
 87 | 				if (!profile) {
 88 | 					logger.error("Failed to fetch user from Figma");
 89 | 					return null;
 90 | 				}
 91 | 
 92 | 				const userMap = await options.mapProfileToUser?.(profile);
 93 | 
 94 | 				return {
 95 | 					user: {
 96 | 						id: profile.id,
 97 | 						name: profile.handle,
 98 | 						email: profile.email,
 99 | 						image: profile.img_url,
100 | 						emailVerified: !!profile.email,
101 | 						...userMap,
102 | 					},
103 | 					data: profile,
104 | 				};
105 | 			} catch (error) {
106 | 				logger.error("Failed to fetch user info from Figma:", error);
107 | 				return null;
108 | 			}
109 | 		},
110 | 		options,
111 | 	} satisfies OAuthProvider<FigmaProfile>;
112 | };
113 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/api/routes/sign-in.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, vi } from "vitest";
  2 | import { getTestInstance } from "../../test-utils/test-instance";
  3 | import { parseSetCookieHeader } from "../../cookies";
  4 | import { APIError } from "better-call";
  5 | import { BASE_ERROR_CODES } from "@better-auth/core/error";
  6 | 
  7 | /**
  8 |  * More test can be found in `session.test.ts`
  9 |  */
 10 | describe("sign-in", async (it) => {
 11 | 	const { auth, testUser, cookieSetter } = await getTestInstance();
 12 | 
 13 | 	it("should return a response with a set-cookie header", async () => {
 14 | 		const signInRes = await auth.api.signInEmail({
 15 | 			body: {
 16 | 				email: testUser.email,
 17 | 				password: testUser.password,
 18 | 			},
 19 | 			asResponse: true,
 20 | 		});
 21 | 		const setCookie = signInRes.headers.get("set-cookie");
 22 | 		const parsed = parseSetCookieHeader(setCookie || "");
 23 | 		expect(parsed.get("better-auth.session_token")).toBeDefined();
 24 | 	});
 25 | 
 26 | 	it("should read the ip address and user agent from the headers", async () => {
 27 | 		const headerObj = {
 28 | 			"X-Forwarded-For": "127.0.0.1",
 29 | 			"User-Agent": "Test",
 30 | 		};
 31 | 		const headers = new Headers(headerObj);
 32 | 		const signInRes = await auth.api.signInEmail({
 33 | 			body: {
 34 | 				email: testUser.email,
 35 | 				password: testUser.password,
 36 | 			},
 37 | 			asResponse: true,
 38 | 			headers,
 39 | 		});
 40 | 		cookieSetter(headers)({
 41 | 			response: signInRes,
 42 | 		});
 43 | 		const session = await auth.api.getSession({
 44 | 			headers,
 45 | 		});
 46 | 		expect(session?.session.ipAddress).toBe(headerObj["X-Forwarded-For"]);
 47 | 		expect(session?.session.userAgent).toBe(headerObj["User-Agent"]);
 48 | 	});
 49 | 
 50 | 	it("verification email will be sent if sendOnSignIn is enabled", async () => {
 51 | 		const sendVerificationEmail = vi.fn();
 52 | 		const { auth, testUser } = await getTestInstance({
 53 | 			emailVerification: {
 54 | 				sendOnSignIn: true,
 55 | 				sendVerificationEmail,
 56 | 			},
 57 | 			emailAndPassword: {
 58 | 				enabled: true,
 59 | 				requireEmailVerification: true,
 60 | 			},
 61 | 		});
 62 | 
 63 | 		expect(sendVerificationEmail).toHaveBeenCalledTimes(1);
 64 | 
 65 | 		await expect(
 66 | 			auth.api.signInEmail({
 67 | 				body: {
 68 | 					email: testUser.email,
 69 | 					password: testUser.password,
 70 | 				},
 71 | 			}),
 72 | 		).rejects.toThrowError(
 73 | 			new APIError("FORBIDDEN", {
 74 | 				message: BASE_ERROR_CODES.EMAIL_NOT_VERIFIED,
 75 | 			}),
 76 | 		);
 77 | 
 78 | 		expect(sendVerificationEmail).toHaveBeenCalledTimes(2);
 79 | 	});
 80 | 
 81 | 	it("verification email will not be sent if sendOnSignIn is disabled", async () => {
 82 | 		const sendVerificationEmail = vi.fn();
 83 | 		const { auth, testUser } = await getTestInstance({
 84 | 			emailVerification: {
 85 | 				sendOnSignIn: false,
 86 | 				sendVerificationEmail,
 87 | 			},
 88 | 			emailAndPassword: {
 89 | 				enabled: true,
 90 | 				requireEmailVerification: true,
 91 | 			},
 92 | 		});
 93 | 
 94 | 		expect(sendVerificationEmail).toHaveBeenCalledTimes(1);
 95 | 
 96 | 		await expect(
 97 | 			auth.api.signInEmail({
 98 | 				body: {
 99 | 					email: testUser.email,
100 | 					password: testUser.password,
101 | 				},
102 | 			}),
103 | 		).rejects.toThrowError(
104 | 			new APIError("FORBIDDEN", {
105 | 				message: BASE_ERROR_CODES.EMAIL_NOT_VERIFIED,
106 | 			}),
107 | 		);
108 | 
109 | 		expect(sendVerificationEmail).toHaveBeenCalledTimes(1);
110 | 	});
111 | });
112 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/authentication/reddit.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Reddit
 3 | description: Reddit provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your Reddit Credentials
 9 |         To use Reddit sign in, you need a client ID and client secret. You can get them from the [Reddit Developer Portal](https://www.reddit.com/prefs/apps).
10 | 
11 |         1. Click "Create App" or "Create Another App"
12 |         2. Select "web app" as the application type
13 |         3. Set the redirect URL to `http://localhost:3000/api/auth/callback/reddit` for local development
14 |         4. For production, set it to your application's domain (e.g. `https://example.com/api/auth/callback/reddit`)
15 |         5. After creating the app, you'll get the client ID (under the app name) and client secret
16 | 
17 |         If you change the base path of the auth routes, make sure to update the redirect URL accordingly.
18 |     </Step>
19 | 
20 |     <Step>
21 |         ### Configure the provider
22 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
23 | 
24 |         ```ts title="auth.ts"
25 |         import { betterAuth } from "better-auth"
26 |         
27 |         export const auth = betterAuth({
28 |             socialProviders: {
29 |                 reddit: {
30 |                     clientId: process.env.REDDIT_CLIENT_ID as string,
31 |                     clientSecret: process.env.REDDIT_CLIENT_SECRET as string,
32 |                 },
33 |             },
34 |         })
35 |         ```
36 |     </Step>
37 | 
38 |     <Step>
39 |         ### Sign In with Reddit
40 |         To sign in with Reddit, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
41 |         - `provider`: The provider to use. It should be set to `reddit`.
42 | 
43 |         ```ts title="auth-client.ts"
44 |         import { createAuthClient } from "better-auth/client"
45 |         const authClient = createAuthClient()
46 |         
47 |         const signIn = async () => {
48 |             const data = await authClient.signIn.social({
49 |                 provider: "reddit"
50 |             })
51 |         }
52 |         ```
53 |     </Step>
54 | </Steps>
55 | 
56 | ## Additional Configuration
57 | 
58 | ### Scopes
59 | By default, Reddit provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration:
60 | 
61 | ```ts title="auth.ts"
62 | export const auth = betterAuth({
63 |     socialProviders: {
64 |         reddit: {
65 |             clientId: process.env.REDDIT_CLIENT_ID as string,
66 |             clientSecret: process.env.REDDIT_CLIENT_SECRET as string,
67 |             duration: "permanent",
68 |             scope: ["read", "submit"] // Add required scopes
69 |         },
70 |     },
71 | })
72 | ```
73 | 
74 | Common Reddit scopes include:
75 | - `identity`: Access basic account information
76 | - `read`: Access posts and comments
77 | - `submit`: Submit posts and comments
78 | - `subscribe`: Manage subreddit subscriptions
79 | - `history`: Access voting history
80 | 
81 | For a complete list of available scopes, refer to the [Reddit OAuth2 documentation](https://www.reddit.com/dev/api/oauth).
82 | 
```

--------------------------------------------------------------------------------
/docs/components/markdown.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { remark } from "remark";
  2 | import remarkGfm from "remark-gfm";
  3 | import remarkRehype from "remark-rehype";
  4 | import { toJsxRuntime } from "hast-util-to-jsx-runtime";
  5 | import {
  6 | 	Children,
  7 | 	type ComponentProps,
  8 | 	type ReactElement,
  9 | 	type ReactNode,
 10 | 	Suspense,
 11 | 	use,
 12 | 	useDeferredValue,
 13 | } from "react";
 14 | import { Fragment, jsx, jsxs } from "react/jsx-runtime";
 15 | import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock";
 16 | import defaultMdxComponents from "fumadocs-ui/mdx";
 17 | import { visit } from "unist-util-visit";
 18 | import type { ElementContent, Root, RootContent } from "hast";
 19 | 
 20 | export interface Processor {
 21 | 	process: (content: string) => Promise<ReactNode>;
 22 | }
 23 | 
 24 | export function rehypeWrapWords() {
 25 | 	return (tree: Root) => {
 26 | 		visit(tree, ["text", "element"], (node, index, parent) => {
 27 | 			if (node.type === "element" && node.tagName === "pre") return "skip";
 28 | 			if (node.type !== "text" || !parent || index === undefined) return;
 29 | 
 30 | 			const words = node.value.split(/(?=\s)/);
 31 | 
 32 | 			// Create new span nodes for each word and whitespace
 33 | 			const newNodes: ElementContent[] = words.flatMap((word) => {
 34 | 				if (word.length === 0) return [];
 35 | 
 36 | 				return {
 37 | 					type: "element",
 38 | 					tagName: "span",
 39 | 					properties: {
 40 | 						class: "animate-fd-fade-in",
 41 | 					},
 42 | 					children: [{ type: "text", value: word }],
 43 | 				};
 44 | 			});
 45 | 
 46 | 			Object.assign(node, {
 47 | 				type: "element",
 48 | 				tagName: "span",
 49 | 				properties: {},
 50 | 				children: newNodes,
 51 | 			} satisfies RootContent);
 52 | 			return "skip";
 53 | 		});
 54 | 	};
 55 | }
 56 | 
 57 | function createProcessor(): Processor {
 58 | 	const processor = remark()
 59 | 		.use(remarkGfm)
 60 | 		.use(remarkRehype)
 61 | 		.use(rehypeWrapWords);
 62 | 
 63 | 	return {
 64 | 		async process(content) {
 65 | 			const nodes = processor.parse({ value: content });
 66 | 			const hast = await processor.run(nodes);
 67 | 
 68 | 			return toJsxRuntime(hast, {
 69 | 				development: false,
 70 | 				jsx,
 71 | 				jsxs,
 72 | 				Fragment,
 73 | 				components: {
 74 | 					...defaultMdxComponents,
 75 | 					pre: Pre,
 76 | 					img: undefined, // use JSX
 77 | 				},
 78 | 			});
 79 | 		},
 80 | 	};
 81 | }
 82 | 
 83 | function Pre(props: ComponentProps<"pre">) {
 84 | 	const code = Children.only(props.children) as ReactElement;
 85 | 	const codeProps = code.props as ComponentProps<"code">;
 86 | 	const content = codeProps.children;
 87 | 	if (typeof content !== "string") return null;
 88 | 
 89 | 	let lang =
 90 | 		codeProps.className
 91 | 			?.split(" ")
 92 | 			.find((v) => v.startsWith("language-"))
 93 | 			?.slice("language-".length) ?? "text";
 94 | 
 95 | 	if (lang === "mdx") lang = "md";
 96 | 
 97 | 	return <DynamicCodeBlock lang={lang} code={content.trimEnd()} />;
 98 | }
 99 | 
100 | const processor = createProcessor();
101 | 
102 | export function Markdown({ text }: { text: string }) {
103 | 	const deferredText = useDeferredValue(text);
104 | 
105 | 	return (
106 | 		<Suspense fallback={<p className="invisible">{text}</p>}>
107 | 			<Renderer text={deferredText} />
108 | 		</Suspense>
109 | 	);
110 | }
111 | 
112 | const cache = new Map<string, Promise<ReactNode>>();
113 | 
114 | function Renderer({ text }: { text: string }) {
115 | 	const result = cache.get(text) ?? processor.process(text);
116 | 	cache.set(text, result);
117 | 
118 | 	return use(result);
119 | }
120 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/jwt/sign.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { importJWK, SignJWT, type JWTPayload } from "jose";
  2 | import { BetterAuthError } from "@better-auth/core/error";
  3 | import { symmetricDecrypt } from "../../crypto";
  4 | import type { JwtOptions } from "./types";
  5 | import { getJwksAdapter } from "./adapter";
  6 | import { createJwk, toExpJWT } from "./utils";
  7 | import type { GenericEndpointContext } from "@better-auth/core";
  8 | 
  9 | export async function signJWT(
 10 | 	ctx: GenericEndpointContext,
 11 | 	config: {
 12 | 		options?: JwtOptions;
 13 | 		payload: JWTPayload;
 14 | 	},
 15 | ) {
 16 | 	const { options, payload } = config;
 17 | 
 18 | 	// Iat
 19 | 	const nowSeconds = Math.floor(Date.now() / 1000);
 20 | 	const iat = payload.iat;
 21 | 
 22 | 	// Exp
 23 | 	let exp = payload.exp;
 24 | 	const defaultExp = toExpJWT(
 25 | 		options?.jwt?.expirationTime ?? "15m",
 26 | 		iat ?? nowSeconds,
 27 | 	);
 28 | 	exp = exp ?? defaultExp;
 29 | 
 30 | 	// Nbf
 31 | 	const nbf = payload.nbf;
 32 | 
 33 | 	// Iss
 34 | 	const iss = payload.iss;
 35 | 	const defaultIss = options?.jwt?.issuer ?? ctx.context.options.baseURL!;
 36 | 
 37 | 	// Aud
 38 | 	const aud = payload.aud;
 39 | 	const defaultAud = options?.jwt?.audience ?? ctx.context.options.baseURL!;
 40 | 
 41 | 	// Custom/remote signing function
 42 | 	if (options?.jwt?.sign) {
 43 | 		const jwtPayload = {
 44 | 			...payload,
 45 | 			iat,
 46 | 			exp,
 47 | 			nbf,
 48 | 			iss: iss ?? defaultIss,
 49 | 			aud: aud ?? defaultAud,
 50 | 		};
 51 | 		return options.jwt.sign(jwtPayload);
 52 | 	}
 53 | 
 54 | 	const adapter = getJwksAdapter(ctx.context.adapter);
 55 | 	let key = await adapter.getLatestKey();
 56 | 	const privateKeyEncryptionEnabled =
 57 | 		!options?.jwks?.disablePrivateKeyEncryption;
 58 | 
 59 | 	if (key === undefined) {
 60 | 		key = await createJwk(ctx, options);
 61 | 	}
 62 | 
 63 | 	let privateWebKey = privateKeyEncryptionEnabled
 64 | 		? await symmetricDecrypt({
 65 | 				key: ctx.context.secret,
 66 | 				data: JSON.parse(key.privateKey),
 67 | 			}).catch(() => {
 68 | 				throw new BetterAuthError(
 69 | 					"Failed to decrypt private key. Make sure the secret currently in use is the same as the one used to encrypt the private key. If you are using a different secret, either clean up your JWKS or disable private key encryption.",
 70 | 				);
 71 | 			})
 72 | 		: key.privateKey;
 73 | 	const alg = key.alg ?? options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
 74 | 	const privateKey = await importJWK(JSON.parse(privateWebKey), alg);
 75 | 
 76 | 	const jwt = new SignJWT(payload)
 77 | 		.setProtectedHeader({
 78 | 			alg,
 79 | 			kid: key.id,
 80 | 		})
 81 | 		.setExpirationTime(exp)
 82 | 		.setIssuer(iss ?? defaultIss)
 83 | 		.setAudience(aud ?? defaultAud);
 84 | 	if (iat) jwt.setIssuedAt(iat);
 85 | 	if (payload.sub) jwt.setSubject(payload.sub);
 86 | 	if (payload.nbf) jwt.setNotBefore(payload.nbf);
 87 | 	if (payload.jti) jwt.setJti(payload.jti);
 88 | 	return await jwt.sign(privateKey);
 89 | }
 90 | 
 91 | export async function getJwtToken(
 92 | 	ctx: GenericEndpointContext,
 93 | 	options?: JwtOptions,
 94 | ) {
 95 | 	const payload = !options?.jwt?.definePayload
 96 | 		? ctx.context.session!.user
 97 | 		: await options?.jwt.definePayload(ctx.context.session!);
 98 | 
 99 | 	return await signJWT(ctx, {
100 | 		options,
101 | 		payload: {
102 | 			iat: Math.floor(Date.now() / 1000),
103 | 			...payload,
104 | 			sub:
105 | 				(await options?.jwt?.getSubject?.(ctx.context.session!)) ??
106 | 				ctx.context.session!.user.id,
107 | 		},
108 | 	});
109 | }
110 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/open-api/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { generator } from "./generator";
  2 | import { logo } from "./logo";
  3 | import type { BetterAuthPlugin } from "@better-auth/core";
  4 | import type { LiteralString } from "../../types/helper";
  5 | 
  6 | import { APIError } from "../../api";
  7 | import { createAuthEndpoint } from "@better-auth/core/api";
  8 | 
  9 | export type { FieldSchema, Path, OpenAPIModelSchema } from "./generator";
 10 | 
 11 | type ScalarTheme =
 12 | 	| "alternate"
 13 | 	| "default"
 14 | 	| "moon"
 15 | 	| "purple"
 16 | 	| "solarized"
 17 | 	| "bluePlanet"
 18 | 	| "saturn"
 19 | 	| "kepler"
 20 | 	| "mars"
 21 | 	| "deepSpace"
 22 | 	| "laserwave"
 23 | 	| "none";
 24 | 
 25 | const getHTML = (
 26 | 	apiReference: Record<string, any>,
 27 | 	theme?: ScalarTheme,
 28 | ) => `<!doctype html>
 29 | <html>
 30 |   <head>
 31 |     <title>Scalar API Reference</title>
 32 |     <meta charset="utf-8" />
 33 |     <meta
 34 |       name="viewport"
 35 |       content="width=device-width, initial-scale=1" />
 36 |   </head>
 37 |   <body>
 38 |     <script
 39 |       id="api-reference"
 40 |       type="application/json">
 41 |     ${JSON.stringify(apiReference)}
 42 |     </script>
 43 | 	 <script>
 44 |       var configuration = {
 45 | 	  	favicon: "data:image/svg+xml;utf8,${encodeURIComponent(logo)}",
 46 | 	   	theme: "${theme || "default"}",
 47 |         metaData: {
 48 | 			title: "Better Auth API",
 49 | 			description: "API Reference for your Better Auth Instance",
 50 | 		}
 51 |       }
 52 | 
 53 |       document.getElementById('api-reference').dataset.configuration =
 54 |         JSON.stringify(configuration)
 55 |     </script>
 56 | 	  <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
 57 |   </body>
 58 | </html>`;
 59 | 
 60 | export interface OpenAPIOptions {
 61 | 	/**
 62 | 	 * The path to the OpenAPI reference page
 63 | 	 *
 64 | 	 * keep in mind that this path will be appended to the base URL `/api/auth` path
 65 | 	 * by default, so if you set this to `/reference`, the full path will be `/api/auth/reference`
 66 | 	 *
 67 | 	 * @default "/reference"
 68 | 	 */
 69 | 	path?: LiteralString;
 70 | 	/**
 71 | 	 * Disable the default reference page that is generated by Scalar
 72 | 	 *
 73 | 	 * @default false
 74 | 	 */
 75 | 	disableDefaultReference?: boolean;
 76 | 	/**
 77 | 	 * Theme of the OpenAPI reference page.
 78 | 	 *
 79 | 	 * @default "default"
 80 | 	 */
 81 | 	theme?: ScalarTheme;
 82 | }
 83 | 
 84 | export const openAPI = <O extends OpenAPIOptions>(options?: O) => {
 85 | 	const path = (options?.path ?? "/reference") as "/reference";
 86 | 	return {
 87 | 		id: "open-api",
 88 | 		endpoints: {
 89 | 			generateOpenAPISchema: createAuthEndpoint(
 90 | 				"/open-api/generate-schema",
 91 | 				{
 92 | 					method: "GET",
 93 | 				},
 94 | 				async (ctx) => {
 95 | 					const schema = await generator(ctx.context, ctx.context.options);
 96 | 					return ctx.json(schema);
 97 | 				},
 98 | 			),
 99 | 			openAPIReference: createAuthEndpoint(
100 | 				path,
101 | 				{
102 | 					method: "GET",
103 | 					metadata: {
104 | 						isAction: false,
105 | 					},
106 | 				},
107 | 				async (ctx) => {
108 | 					if (options?.disableDefaultReference) {
109 | 						throw new APIError("NOT_FOUND");
110 | 					}
111 | 					const schema = await generator(ctx.context, ctx.context.options);
112 | 					return new Response(getHTML(schema, options?.theme), {
113 | 						headers: {
114 | 							"Content-Type": "text/html",
115 | 						},
116 | 					});
117 | 				},
118 | 			),
119 | 		},
120 | 	} satisfies BetterAuthPlugin;
121 | };
122 | 
123 | export type * from "./generator";
124 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/introduction.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Introduction
 3 | description: Introduction to Better Auth.
 4 | ---
 5 | 
 6 | Better Auth is a framework-agnostic, universal authentication and authorization framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities. Whether you need 2FA, passkey, multi-tenancy, multi-session support, or even enterprise features like SSO, creating your own IDP, it lets you focus on building your application instead of reinventing the wheel.
 7 | 
 8 | ## Features
 9 | 
10 | Better Auth aims to be the most comprehensive auth library. It provides a wide range of features out of the box and allows you to extend it with plugins. Here are some of the features:
11 | 
12 | <Features/>
13 | 
14 | ...and much more!
15 | 
16 | ---
17 | 
18 | ## AI tooling
19 | 
20 | ### LLMs.txt
21 | 
22 | Better Auth exposes an `LLMs.txt` that helps AI models understand how to integrate and interact with your authentication system. See it at [https://better-auth.com/llms.txt](https://better-auth.com/llms.txt).
23 | 
24 | ### MCP
25 | 
26 | Better Auth provides an MCP server so you can use it with any AI model that supports the Model Context Protocol (MCP).
27 | 
28 | <AddToCursor />
29 | 
30 | #### CLI Options
31 | 
32 | Use the Better Auth CLI to easily add the MCP server to your preferred client:
33 | 
34 | <Tabs items={["Cursor", "Claude Code", "Open Code", "Manual"]}>
35 |     <Tab value="Cursor">
36 |       ```bash title="terminal"
37 |       pnpm @better-auth/cli mcp --cursor
38 |       ```
39 |     </Tab>
40 |     <Tab value="Claude Code">
41 |       ```bash title="terminal"
42 |       pnpm @better-auth/cli mcp --claude-code
43 |       ```
44 |     </Tab>
45 |     <Tab value="Open Code">
46 |       ```bash title="terminal"
47 |       pnpm @better-auth/cli mcp --open-code
48 |       ```
49 |     </Tab>
50 |     <Tab value="Manual">
51 |       ```bash title="terminal"
52 |       pnpm @better-auth/cli mcp --manual
53 |       ```
54 |     </Tab>
55 | </Tabs>
56 | 
57 | #### Manual Configuration
58 | 
59 | Alternatively, you can manually configure the MCP server for each client:
60 | 
61 | <Tabs items={["Claude Code", "Open Code", "Manual"]}>
62 |     <Tab value="Claude Code">
63 |       ```bash title="terminal"
64 |       claude mcp add --transport http better-auth https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp
65 |       ```
66 |     </Tab>
67 |     <Tab value="Open Code">
68 |       ```json title="opencode.json"
69 |         {
70 |             "$schema": "https://opencode.ai/config.json",
71 |             "mcp": {
72 |                 "Better Auth": {
73 |                     "type": "remote",
74 |                     "url": "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp",
75 |                     "enabled": true,
76 |                 }
77 |             }
78 |         }
79 |       ```
80 |     </Tab>
81 |     <Tab value="Manual">
82 |      ```json title="mcp.json"
83 |     {
84 |         "Better Auth": {
85 |             "url": "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp"
86 |         }
87 |     }
88 |     ```
89 |     </Tab>
90 | </Tabs>
91 | 
92 | <Callout>
93 | We provide a first‑party MCP, powered by [Chonkie](https://chonkie.ai). You can alternatively use [`context7`](https://context7.com/) and other MCP providers.
94 | </Callout>
95 | 
```
Page 9/69FirstPrevNextLast