#
tokens: 49368/50000 28/1099 files (page 10/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 10 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

--------------------------------------------------------------------------------
/demo/nextjs/app/(auth)/two-factor/otp/page.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { Button } from "@/components/ui/button";
  4 | import {
  5 | 	Card,
  6 | 	CardContent,
  7 | 	CardDescription,
  8 | 	CardHeader,
  9 | 	CardTitle,
 10 | } from "@/components/ui/card";
 11 | import { Input } from "@/components/ui/input";
 12 | import { Label } from "@/components/ui/label";
 13 | import { client } from "@/lib/auth-client";
 14 | import { AlertCircle, CheckCircle2, Mail } from "lucide-react";
 15 | import { useRouter } from "next/navigation";
 16 | import { useState } from "react";
 17 | 
 18 | export default function Component() {
 19 | 	const [otp, setOtp] = useState("");
 20 | 	const [isOtpSent, setIsOtpSent] = useState(false);
 21 | 	const [message, setMessage] = useState("");
 22 | 	const [isError, setIsError] = useState(false);
 23 | 	const [isValidated, setIsValidated] = useState(false);
 24 | 
 25 | 	// In a real app, this email would come from your authentication context
 26 | 	const userEmail = "[email protected]";
 27 | 
 28 | 	const requestOTP = async () => {
 29 | 		const res = await client.twoFactor.sendOtp();
 30 | 		// In a real app, this would call your backend API to send the OTP
 31 | 		setMessage("OTP sent to your email");
 32 | 		setIsError(false);
 33 | 		setIsOtpSent(true);
 34 | 	};
 35 | 	const router = useRouter();
 36 | 
 37 | 	const validateOTP = async () => {
 38 | 		const res = await client.twoFactor.verifyOtp({
 39 | 			code: otp,
 40 | 		});
 41 | 		if (res.data) {
 42 | 			setMessage("OTP validated successfully");
 43 | 			setIsError(false);
 44 | 			setIsValidated(true);
 45 | 			router.push("/");
 46 | 		} else {
 47 | 			setIsError(true);
 48 | 			setMessage("Invalid OTP");
 49 | 		}
 50 | 	};
 51 | 	return (
 52 | 		<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
 53 | 			<Card className="w-[350px]">
 54 | 				<CardHeader>
 55 | 					<CardTitle>Two-Factor Authentication</CardTitle>
 56 | 					<CardDescription>
 57 | 						Verify your identity with a one-time password
 58 | 					</CardDescription>
 59 | 				</CardHeader>
 60 | 				<CardContent>
 61 | 					<div className="grid items-center w-full gap-4">
 62 | 						{!isOtpSent ? (
 63 | 							<Button onClick={requestOTP} className="w-full">
 64 | 								<Mail className="w-4 h-4 mr-2" /> Send OTP to Email
 65 | 							</Button>
 66 | 						) : (
 67 | 							<>
 68 | 								<div className="flex flex-col space-y-1.5">
 69 | 									<Label htmlFor="otp">One-Time Password</Label>
 70 | 									<Label className="py-2">
 71 | 										Check your email at {userEmail} for the OTP
 72 | 									</Label>
 73 | 									<Input
 74 | 										id="otp"
 75 | 										placeholder="Enter 6-digit OTP"
 76 | 										value={otp}
 77 | 										onChange={(e) => setOtp(e.target.value)}
 78 | 										maxLength={6}
 79 | 									/>
 80 | 								</div>
 81 | 								<Button
 82 | 									onClick={validateOTP}
 83 | 									disabled={otp.length !== 6 || isValidated}
 84 | 								>
 85 | 									Validate OTP
 86 | 								</Button>
 87 | 							</>
 88 | 						)}
 89 | 					</div>
 90 | 					{message && (
 91 | 						<div
 92 | 							className={`flex items-center gap-2 mt-4 ${
 93 | 								isError ? "text-red-500" : "text-primary"
 94 | 							}`}
 95 | 						>
 96 | 							{isError ? (
 97 | 								<AlertCircle className="w-4 h-4" />
 98 | 							) : (
 99 | 								<CheckCircle2 className="w-4 h-4" />
100 | 							)}
101 | 							<p className="text-sm">{message}</p>
102 | 						</div>
103 | 					)}
104 | 				</CardContent>
105 | 			</Card>
106 | 		</main>
107 | 	);
108 | }
109 | 
```

--------------------------------------------------------------------------------
/docs/public/plus.svg:
--------------------------------------------------------------------------------

```
1 | <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" viewBox="0 0 800 800" width="800" height="800"><g stroke-width="3.5" stroke="hsla(0, 0%, 100%, 1.00)" fill="none"><rect width="200" height="8.578947368421051" x="-100" y="-4.289473684210526" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="-4.289473684210526" y="-100" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="300" y="-4.289473684210526" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="395.7105263157895" y="-100" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="700" y="-4.289473684210526" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="795.7105263157895" y="-100" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="-100" y="395.7105263157895" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="-4.289473684210526" y="300" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="300" y="395.7105263157895" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="395.7105263157895" y="300" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="700" y="395.7105263157895" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="795.7105263157895" y="300" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="-100" y="795.7105263157895" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="-4.289473684210526" y="700" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="300" y="795.7105263157895" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="395.7105263157895" y="700" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="200" height="8.578947368421051" x="700" y="795.7105263157895" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect><rect width="8.578947368421051" height="200" x="795.7105263157895" y="700" stroke="none" fill="hsla(0, 0%, 100%, 1.00)" rx="4.289473684210526"></rect></g></svg>
2 | 
```

--------------------------------------------------------------------------------
/packages/core/src/oauth2/refresh-access-token.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
  3 | import { base64 } from "@better-auth/utils/base64";
  4 | 
  5 | export function createRefreshAccessTokenRequest({
  6 | 	refreshToken,
  7 | 	options,
  8 | 	authentication,
  9 | 	extraParams,
 10 | 	resource,
 11 | }: {
 12 | 	refreshToken: string;
 13 | 	options: Partial<ProviderOptions>;
 14 | 	authentication?: "basic" | "post";
 15 | 	extraParams?: Record<string, string>;
 16 | 	resource?: string | string[];
 17 | }) {
 18 | 	const body = new URLSearchParams();
 19 | 	const headers: Record<string, any> = {
 20 | 		"content-type": "application/x-www-form-urlencoded",
 21 | 		accept: "application/json",
 22 | 	};
 23 | 
 24 | 	body.set("grant_type", "refresh_token");
 25 | 	body.set("refresh_token", refreshToken);
 26 | 	// Use standard Base64 encoding for HTTP Basic Auth (OAuth2 spec, RFC 7617)
 27 | 	// Fixes compatibility with providers like Notion, Twitter, etc.
 28 | 	if (authentication === "basic") {
 29 | 		const primaryClientId = Array.isArray(options.clientId)
 30 | 			? options.clientId[0]
 31 | 			: options.clientId;
 32 | 		if (primaryClientId) {
 33 | 			headers["authorization"] =
 34 | 				"Basic " +
 35 | 				base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`);
 36 | 		} else {
 37 | 			headers["authorization"] =
 38 | 				"Basic " + base64.encode(`:${options.clientSecret ?? ""}`);
 39 | 		}
 40 | 	} else {
 41 | 		const primaryClientId = Array.isArray(options.clientId)
 42 | 			? options.clientId[0]
 43 | 			: options.clientId;
 44 | 		body.set("client_id", primaryClientId);
 45 | 		if (options.clientSecret) {
 46 | 			body.set("client_secret", options.clientSecret);
 47 | 		}
 48 | 	}
 49 | 
 50 | 	if (resource) {
 51 | 		if (typeof resource === "string") {
 52 | 			body.append("resource", resource);
 53 | 		} else {
 54 | 			for (const _resource of resource) {
 55 | 				body.append("resource", _resource);
 56 | 			}
 57 | 		}
 58 | 	}
 59 | 	if (extraParams) {
 60 | 		for (const [key, value] of Object.entries(extraParams)) {
 61 | 			body.set(key, value);
 62 | 		}
 63 | 	}
 64 | 
 65 | 	return {
 66 | 		body,
 67 | 		headers,
 68 | 	};
 69 | }
 70 | 
 71 | export async function refreshAccessToken({
 72 | 	refreshToken,
 73 | 	options,
 74 | 	tokenEndpoint,
 75 | 	authentication,
 76 | 	extraParams,
 77 | }: {
 78 | 	refreshToken: string;
 79 | 	options: Partial<ProviderOptions>;
 80 | 	tokenEndpoint: string;
 81 | 	authentication?: "basic" | "post";
 82 | 	extraParams?: Record<string, string>;
 83 | 	/** @deprecated always "refresh_token" */
 84 | 	grantType?: string;
 85 | }): Promise<OAuth2Tokens> {
 86 | 	const { body, headers } = createRefreshAccessTokenRequest({
 87 | 		refreshToken,
 88 | 		options,
 89 | 		authentication,
 90 | 		extraParams,
 91 | 	});
 92 | 
 93 | 	const { data, error } = await betterFetch<{
 94 | 		access_token: string;
 95 | 		refresh_token?: string;
 96 | 		expires_in?: number;
 97 | 		token_type?: string;
 98 | 		scope?: string;
 99 | 		id_token?: string;
100 | 	}>(tokenEndpoint, {
101 | 		method: "POST",
102 | 		body,
103 | 		headers,
104 | 	});
105 | 	if (error) {
106 | 		throw error;
107 | 	}
108 | 	const tokens: OAuth2Tokens = {
109 | 		accessToken: data.access_token,
110 | 		refreshToken: data.refresh_token,
111 | 		tokenType: data.token_type,
112 | 		scopes: data.scope?.split(" "),
113 | 		idToken: data.id_token,
114 | 	};
115 | 
116 | 	if (data.expires_in) {
117 | 		const now = new Date();
118 | 		tokens.accessTokenExpiresAt = new Date(
119 | 			now.getTime() + data.expires_in * 1000,
120 | 		);
121 | 	}
122 | 
123 | 	return tokens;
124 | }
125 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import { Drawer as DrawerPrimitive } from "vaul";
  5 | 
  6 | import { cn } from "@/lib/utils";
  7 | 
  8 | const Drawer = ({
  9 | 	shouldScaleBackground = true,
 10 | 	...props
 11 | }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
 12 | 	<DrawerPrimitive.Root
 13 | 		shouldScaleBackground={shouldScaleBackground}
 14 | 		{...props}
 15 | 	/>
 16 | );
 17 | Drawer.displayName = "Drawer";
 18 | 
 19 | const DrawerTrigger = DrawerPrimitive.Trigger;
 20 | 
 21 | const DrawerPortal = DrawerPrimitive.Portal;
 22 | 
 23 | const DrawerClose = DrawerPrimitive.Close;
 24 | 
 25 | const DrawerOverlay = ({
 26 | 	ref,
 27 | 	className,
 28 | 	...props
 29 | }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> & {
 30 | 	ref: React.RefObject<React.ElementRef<typeof DrawerPrimitive.Overlay>>;
 31 | }) => (
 32 | 	<DrawerPrimitive.Overlay
 33 | 		ref={ref}
 34 | 		className={cn("fixed inset-0 z-50 bg-black/80", className)}
 35 | 		{...props}
 36 | 	/>
 37 | );
 38 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
 39 | 
 40 | const DrawerContent = ({
 41 | 	ref,
 42 | 	className,
 43 | 	children,
 44 | 	...props
 45 | }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
 46 | 	ref: React.RefObject<React.ElementRef<typeof DrawerPrimitive.Content>>;
 47 | }) => (
 48 | 	<DrawerPortal>
 49 | 		<DrawerOverlay />
 50 | 		<DrawerPrimitive.Content
 51 | 			ref={ref}
 52 | 			className={cn(
 53 | 				"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
 54 | 				className,
 55 | 			)}
 56 | 			{...props}
 57 | 		>
 58 | 			<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
 59 | 			{children}
 60 | 		</DrawerPrimitive.Content>
 61 | 	</DrawerPortal>
 62 | );
 63 | DrawerContent.displayName = "DrawerContent";
 64 | 
 65 | const DrawerHeader = ({
 66 | 	className,
 67 | 	...props
 68 | }: React.HTMLAttributes<HTMLDivElement>) => (
 69 | 	<div
 70 | 		className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
 71 | 		{...props}
 72 | 	/>
 73 | );
 74 | DrawerHeader.displayName = "DrawerHeader";
 75 | 
 76 | const DrawerFooter = ({
 77 | 	className,
 78 | 	...props
 79 | }: React.HTMLAttributes<HTMLDivElement>) => (
 80 | 	<div
 81 | 		className={cn("mt-auto flex flex-col gap-2 p-4", className)}
 82 | 		{...props}
 83 | 	/>
 84 | );
 85 | DrawerFooter.displayName = "DrawerFooter";
 86 | 
 87 | const DrawerTitle = ({
 88 | 	ref,
 89 | 	className,
 90 | 	...props
 91 | }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title> & {
 92 | 	ref: React.RefObject<React.ElementRef<typeof DrawerPrimitive.Title>>;
 93 | }) => (
 94 | 	<DrawerPrimitive.Title
 95 | 		ref={ref}
 96 | 		className={cn(
 97 | 			"text-lg font-semibold leading-none tracking-tight",
 98 | 			className,
 99 | 		)}
100 | 		{...props}
101 | 	/>
102 | );
103 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
104 | 
105 | const DrawerDescription = ({
106 | 	ref,
107 | 	className,
108 | 	...props
109 | }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description> & {
110 | 	ref: React.RefObject<React.ElementRef<typeof DrawerPrimitive.Description>>;
111 | }) => (
112 | 	<DrawerPrimitive.Description
113 | 		ref={ref}
114 | 		className={cn("text-sm text-muted-foreground", className)}
115 | 		{...props}
116 | 	/>
117 | );
118 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
119 | 
120 | export {
121 | 	Drawer,
122 | 	DrawerPortal,
123 | 	DrawerOverlay,
124 | 	DrawerTrigger,
125 | 	DrawerClose,
126 | 	DrawerContent,
127 | 	DrawerHeader,
128 | 	DrawerFooter,
129 | 	DrawerTitle,
130 | 	DrawerDescription,
131 | };
132 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	getOAuth2Tokens,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | import { base64 } from "@better-auth/utils/base64";
  9 | 
 10 | export interface RedditProfile {
 11 | 	id: string;
 12 | 	name: string;
 13 | 	icon_img: string | null;
 14 | 	has_verified_email: boolean;
 15 | 	oauth_client_id: string;
 16 | 	verified: boolean;
 17 | }
 18 | 
 19 | export interface RedditOptions extends ProviderOptions<RedditProfile> {
 20 | 	clientId: string;
 21 | 	duration?: string;
 22 | }
 23 | 
 24 | export const reddit = (options: RedditOptions) => {
 25 | 	return {
 26 | 		id: "reddit",
 27 | 		name: "Reddit",
 28 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
 29 | 			const _scopes = options.disableDefaultScope ? [] : ["identity"];
 30 | 			options.scope && _scopes.push(...options.scope);
 31 | 			scopes && _scopes.push(...scopes);
 32 | 			return createAuthorizationURL({
 33 | 				id: "reddit",
 34 | 				options,
 35 | 				authorizationEndpoint: "https://www.reddit.com/api/v1/authorize",
 36 | 				scopes: _scopes,
 37 | 				state,
 38 | 				redirectURI,
 39 | 				duration: options.duration,
 40 | 			});
 41 | 		},
 42 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 43 | 			const body = new URLSearchParams({
 44 | 				grant_type: "authorization_code",
 45 | 				code,
 46 | 				redirect_uri: options.redirectURI || redirectURI,
 47 | 			});
 48 | 			const headers = {
 49 | 				"content-type": "application/x-www-form-urlencoded",
 50 | 				accept: "text/plain",
 51 | 				"user-agent": "better-auth",
 52 | 				Authorization: `Basic ${base64.encode(
 53 | 					`${options.clientId}:${options.clientSecret}`,
 54 | 				)}`,
 55 | 			};
 56 | 
 57 | 			const { data, error } = await betterFetch<object>(
 58 | 				"https://www.reddit.com/api/v1/access_token",
 59 | 				{
 60 | 					method: "POST",
 61 | 					headers,
 62 | 					body: body.toString(),
 63 | 				},
 64 | 			);
 65 | 
 66 | 			if (error) {
 67 | 				throw error;
 68 | 			}
 69 | 
 70 | 			return getOAuth2Tokens(data);
 71 | 		},
 72 | 
 73 | 		refreshAccessToken: options.refreshAccessToken
 74 | 			? options.refreshAccessToken
 75 | 			: async (refreshToken) => {
 76 | 					return refreshAccessToken({
 77 | 						refreshToken,
 78 | 						options: {
 79 | 							clientId: options.clientId,
 80 | 							clientKey: options.clientKey,
 81 | 							clientSecret: options.clientSecret,
 82 | 						},
 83 | 						authentication: "basic",
 84 | 						tokenEndpoint: "https://www.reddit.com/api/v1/access_token",
 85 | 					});
 86 | 				},
 87 | 		async getUserInfo(token) {
 88 | 			if (options.getUserInfo) {
 89 | 				return options.getUserInfo(token);
 90 | 			}
 91 | 
 92 | 			const { data: profile, error } = await betterFetch<RedditProfile>(
 93 | 				"https://oauth.reddit.com/api/v1/me",
 94 | 				{
 95 | 					headers: {
 96 | 						Authorization: `Bearer ${token.accessToken}`,
 97 | 						"User-Agent": "better-auth",
 98 | 					},
 99 | 				},
100 | 			);
101 | 
102 | 			if (error) {
103 | 				return null;
104 | 			}
105 | 
106 | 			const userMap = await options.mapProfileToUser?.(profile);
107 | 
108 | 			return {
109 | 				user: {
110 | 					id: profile.id,
111 | 					name: profile.name,
112 | 					email: profile.oauth_client_id,
113 | 					emailVerified: profile.has_verified_email,
114 | 					image: profile.icon_img?.split("?")[0]!,
115 | 					...userMap,
116 | 				},
117 | 				data: profile,
118 | 			};
119 | 		},
120 | 		options,
121 | 	} satisfies OAuthProvider<RedditProfile>;
122 | };
123 | 
```

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

```markdown
 1 | ---
 2 | title: GitHub
 3 | description: GitHub provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your GitHub credentials
 9 |         To use GitHub sign in, you need a client ID and client secret. You can get them from the [GitHub Developer Portal](https://github.com/settings/developers).
10 | 
11 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/github` 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 | 
13 |         Important: You MUST include the user:email scope in your GitHub app. See details below.
14 |     </Step>
15 | 
16 |   <Step>
17 |         ### Configure the provider
18 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
19 | 
20 |         ```ts title="auth.ts"
21 |         import { betterAuth } from "better-auth"
22 |         
23 |         export const auth = betterAuth({
24 |             socialProviders: {
25 |                 github: { // [!code highlight]
26 |                     clientId: process.env.GITHUB_CLIENT_ID as string, // [!code highlight]
27 |                     clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // [!code highlight]
28 |                 }, // [!code highlight]
29 |             },
30 |         })
31 |         ```
32 |     </Step>
33 |        <Step>
34 |         ### Sign In with GitHub
35 |         To sign in with GitHub, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
36 |         - `provider`: The provider to use. It should be set to `github`.
37 | 
38 |         ```ts title="auth-client.ts"  
39 |         import { createAuthClient } from "better-auth/client"
40 |         const authClient =  createAuthClient()
41 |         
42 |         const signIn = async () => {
43 |             const data = await authClient.signIn.social({
44 |                 provider: "github"
45 |             })
46 |         }
47 |         ```
48 |     </Step>
49 | </Steps>
50 | 
51 | ## Usage
52 | 
53 | ### Setting up your Github app
54 | 
55 | Github has two types of apps: Github apps and OAuth apps.
56 | 
57 | For OAuth apps, you don't have to do anything special (just follow the steps above). For Github apps, you DO have to add one more thing, which is enable it to read the user's email:
58 | 
59 | 1. After creating your app, go to *Permissions and Events* > *Account Permissions* > *Email Addresses* and select "Read-Only"
60 | 
61 | 2. Save changes. 
62 | 
63 | That's all! Now you can copy the Client ID and Client Secret of your app! 
64 | 
65 | <Callout>
66 | If you get "email_not_found" error, it's because you selected a Github app & did not configure this part!
67 | </Callout>
68 | 
69 | ### Why don't I have a refresh token?
70 | 
71 | Github doesn't issue refresh tokens for OAuth apps. For regular OAuth apps,
72 | GitHub issues access tokens that remain valid indefinitely unless the user revokes them,
73 | the app revokes them, or they go unused for a year.
74 | There's no need for a refresh token because the access token doesn't expire on a short interval like Google or Discord.
75 | 
```

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

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

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import { type OAuthProvider, type ProviderOptions } from "../oauth2";
  3 | import {
  4 | 	createAuthorizationURL,
  5 | 	validateAuthorizationCode,
  6 | 	refreshAccessToken,
  7 | } from "../oauth2";
  8 | 
  9 | export interface VkProfile {
 10 | 	user: {
 11 | 		user_id: string;
 12 | 		first_name: string;
 13 | 		last_name: string;
 14 | 		email?: string;
 15 | 		phone?: number;
 16 | 		avatar?: string;
 17 | 		sex?: number;
 18 | 		verified?: boolean;
 19 | 		birthday: string;
 20 | 	};
 21 | }
 22 | 
 23 | export interface VkOption extends ProviderOptions {
 24 | 	clientId: string;
 25 | 	scheme?: "light" | "dark";
 26 | }
 27 | 
 28 | export const vk = (options: VkOption) => {
 29 | 	return {
 30 | 		id: "vk",
 31 | 		name: "VK",
 32 | 		async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
 33 | 			const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
 34 | 			options.scope && _scopes.push(...options.scope);
 35 | 			scopes && _scopes.push(...scopes);
 36 | 			const authorizationEndpoint = "https://id.vk.com/authorize";
 37 | 
 38 | 			return createAuthorizationURL({
 39 | 				id: "vk",
 40 | 				options,
 41 | 				authorizationEndpoint,
 42 | 				scopes: _scopes,
 43 | 				state,
 44 | 				redirectURI,
 45 | 				codeVerifier,
 46 | 			});
 47 | 		},
 48 | 		validateAuthorizationCode: async ({
 49 | 			code,
 50 | 			codeVerifier,
 51 | 			redirectURI,
 52 | 			deviceId,
 53 | 		}) => {
 54 | 			return validateAuthorizationCode({
 55 | 				code,
 56 | 				codeVerifier,
 57 | 				redirectURI: options.redirectURI || redirectURI,
 58 | 				options,
 59 | 				deviceId,
 60 | 				tokenEndpoint: "https://id.vk.com/oauth2/auth",
 61 | 			});
 62 | 		},
 63 | 		refreshAccessToken: options.refreshAccessToken
 64 | 			? options.refreshAccessToken
 65 | 			: async (refreshToken) => {
 66 | 					return refreshAccessToken({
 67 | 						refreshToken,
 68 | 						options: {
 69 | 							clientId: options.clientId,
 70 | 							clientKey: options.clientKey,
 71 | 							clientSecret: options.clientSecret,
 72 | 						},
 73 | 						tokenEndpoint: "https://id.vk.com/oauth2/auth",
 74 | 					});
 75 | 				},
 76 | 		async getUserInfo(data) {
 77 | 			if (options.getUserInfo) {
 78 | 				return options.getUserInfo(data);
 79 | 			}
 80 | 			if (!data.accessToken) {
 81 | 				return null;
 82 | 			}
 83 | 			const formBody = new URLSearchParams({
 84 | 				access_token: data.accessToken,
 85 | 				client_id: options.clientId,
 86 | 			}).toString();
 87 | 			const { data: profile, error } = await betterFetch<VkProfile>(
 88 | 				"https://id.vk.com/oauth2/user_info",
 89 | 				{
 90 | 					method: "POST",
 91 | 					headers: {
 92 | 						"Content-Type": "application/x-www-form-urlencoded",
 93 | 					},
 94 | 					body: formBody,
 95 | 				},
 96 | 			);
 97 | 			if (error) {
 98 | 				return null;
 99 | 			}
100 | 			if (!profile.user.email) {
101 | 				return null;
102 | 			}
103 | 
104 | 			const userMap = await options.mapProfileToUser?.(profile);
105 | 
106 | 			return {
107 | 				user: {
108 | 					id: profile.user.user_id,
109 | 					first_name: profile.user.first_name,
110 | 					last_name: profile.user.last_name,
111 | 					email: profile.user.email,
112 | 					image: profile.user.avatar,
113 | 					/** @note VK does not provide emailVerified*/
114 | 					emailVerified: !!profile.user.email,
115 | 					birthday: profile.user.birthday,
116 | 					sex: profile.user.sex,
117 | 					name: `${profile.user.first_name} ${profile.user.last_name}`,
118 | 					...userMap,
119 | 				},
120 | 				data: profile,
121 | 			};
122 | 		},
123 | 		options,
124 | 	} satisfies OAuthProvider<VkProfile>;
125 | };
126 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-mysql-passkey-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 | });
22 | 
23 | export const custom_session = mysqlTable("custom_session", {
24 |   id: int("id").autoincrement().primaryKey(),
25 |   expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
26 |   token: varchar("token", { length: 255 }).notNull().unique(),
27 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
28 |   updatedAt: timestamp("updated_at", { fsp: 3 })
29 |     .$onUpdate(() => /* @__PURE__ */ new Date())
30 |     .notNull(),
31 |   ipAddress: text("ip_address"),
32 |   userAgent: text("user_agent"),
33 |   userId: int("user_id")
34 |     .notNull()
35 |     .references(() => custom_user.id, { onDelete: "cascade" }),
36 | });
37 | 
38 | export const custom_account = mysqlTable("custom_account", {
39 |   id: int("id").autoincrement().primaryKey(),
40 |   accountId: text("account_id").notNull(),
41 |   providerId: text("provider_id").notNull(),
42 |   userId: int("user_id")
43 |     .notNull()
44 |     .references(() => custom_user.id, { onDelete: "cascade" }),
45 |   accessToken: text("access_token"),
46 |   refreshToken: text("refresh_token"),
47 |   idToken: text("id_token"),
48 |   accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }),
49 |   refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }),
50 |   scope: text("scope"),
51 |   password: text("password"),
52 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
53 |   updatedAt: timestamp("updated_at", { fsp: 3 })
54 |     .$onUpdate(() => /* @__PURE__ */ new Date())
55 |     .notNull(),
56 | });
57 | 
58 | export const custom_verification = mysqlTable("custom_verification", {
59 |   id: int("id").autoincrement().primaryKey(),
60 |   identifier: text("identifier").notNull(),
61 |   value: text("value").notNull(),
62 |   expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
63 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
64 |   updatedAt: timestamp("updated_at", { fsp: 3 })
65 |     .defaultNow()
66 |     .$onUpdate(() => /* @__PURE__ */ new Date())
67 |     .notNull(),
68 | });
69 | 
70 | export const passkey = mysqlTable("passkey", {
71 |   id: int("id").autoincrement().primaryKey(),
72 |   name: text("name"),
73 |   publicKey: text("public_key").notNull(),
74 |   userId: int("user_id")
75 |     .notNull()
76 |     .references(() => custom_user.id, { onDelete: "cascade" }),
77 |   credentialID: text("credential_id").notNull(),
78 |   counter: int("counter").notNull(),
79 |   deviceType: text("device_type").notNull(),
80 |   backedUp: boolean("backed_up").notNull(),
81 |   transports: text("transports"),
82 |   createdAt: timestamp("created_at", { fsp: 3 }),
83 |   aaguid: text("aaguid"),
84 | });
85 | 
```

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

```markdown
 1 | ---
 2 | title: GitLab
 3 | description: GitLab provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your GitLab credentials
 9 |         To use GitLab sign in, you need a client ID and client secret. [GitLab OAuth documentation](https://docs.gitlab.com/ee/api/oauth2.html).
10 | 
11 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/gitlab` 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 |                 gitlab: { // [!code highlight]
24 |                     clientId: process.env.GITLAB_CLIENT_ID as string, // [!code highlight]
25 |                     clientSecret: process.env.GITLAB_CLIENT_SECRET as string, // [!code highlight]
26 |                     issuer: process.env.GITLAB_ISSUER as string, // [!code highlight]
27 |                 }, // [!code highlight]
28 |             },
29 |         })
30 |         ```
31 | 
32 |         #### Configuration Options
33 | 
34 |         - `clientId`: Your GitLab application's Client ID
35 |         - `clientSecret`: Your GitLab application's Client Secret  
36 |         - `issuer`: (Optional) The URL of your GitLab instance. Use this for self-hosted GitLab servers.
37 |             - Default: `"https://gitlab.com"` (GitLab.com)
38 |             - Example: `"https://gitlab.company.com"`
39 | 
40 |         <Callout type="info">
41 |             The `issuer` option is useful when using a self-hosted GitLab instance. If you're using GitLab.com, you can omit this option as it defaults to `https://gitlab.com`.
42 |         </Callout>
43 | 
44 |         #### Example with self-hosted GitLab
45 | 
46 |         ```ts title="auth.ts"
47 |         export const auth = betterAuth({
48 |             socialProviders: {
49 |                 gitlab: {
50 |                     clientId: process.env.GITLAB_CLIENT_ID as string,
51 |                     clientSecret: process.env.GITLAB_CLIENT_SECRET as string,
52 |                     issuer: "https://gitlab.company.com", // Your self-hosted GitLab URL // [!code highlight]
53 |                 },
54 |             },
55 |         })
56 |         ```
57 |     </Step>
58 |        <Step>
59 |         ### Sign In with GitLab
60 |         To sign in with GitLab, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
61 |         - `provider`: The provider to use. It should be set to `gitlab`.
62 | 
63 |         ```ts title="auth-client.ts"  
64 |         import { createAuthClient } from "better-auth/client"
65 |         const authClient =  createAuthClient()
66 |         
67 |         const signIn = async () => {
68 |             const data = await authClient.signIn.social({
69 |                 provider: "gitlab"
70 |             })
71 |         }
72 |         ```
73 |     </Step>
74 | </Steps>
75 | 
```

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

```markdown
 1 | ---
 2 | title: Linear
 3 | description: Linear provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your Linear credentials
 9 |         To use Linear sign in, you need a client ID and client secret. You can get them from the [Linear Developer Portal](https://linear.app/settings/api).
10 | 
11 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/linear` 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 | 
13 |         When creating your OAuth application in Linear, you'll need to specify the required scopes. The default scope is `read`, but you can also request additional scopes like `write` if needed.
14 |     </Step>
15 | 
16 |     <Step>
17 |         ### Configure the provider
18 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
19 | 
20 |         ```ts title="auth.ts"
21 |         import { betterAuth } from "better-auth"
22 |         
23 |         export const auth = betterAuth({
24 |             socialProviders: {
25 |                 linear: { // [!code highlight]
26 |                     clientId: process.env.LINEAR_CLIENT_ID as string, // [!code highlight]
27 |                     clientSecret: process.env.LINEAR_CLIENT_SECRET as string, // [!code highlight]
28 |                 }, // [!code highlight]
29 |             },
30 |         })
31 |         ```
32 |     </Step>
33 | 
34 |     <Step>
35 |         ### Sign In with Linear
36 |         To sign in with Linear, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
37 |         - `provider`: The provider to use. It should be set to `linear`.
38 | 
39 |         ```ts title="auth-client.ts"  
40 |         import { createAuthClient } from "better-auth/client"
41 |         const authClient = createAuthClient()
42 |         
43 |         const signIn = async () => {
44 |             const data = await authClient.signIn.social({
45 |                 provider: "linear"
46 |             })
47 |         }
48 |         ```
49 |     </Step>
50 | 
51 |     <Step>
52 |         ### Available scopes
53 |         Linear OAuth supports the following scopes:
54 |         - `read` (default): Read access for the user's account
55 |         - `write`: Write access for the user's account
56 |         - `issues:create`: Allows creating new issues and their attachments
57 |         - `comments:create`: Allows creating new issue comments
58 |         - `timeSchedule:write`: Allows creating and modifying time schedules
59 |         - `admin`: Full access to admin level endpoints (use with caution)
60 | 
61 |         You can specify additional scopes when configuring the provider:
62 | 
63 |         ```ts title="auth.ts"
64 |         export const auth = betterAuth({
65 |             socialProviders: {
66 |                 linear: {
67 |                     clientId: process.env.LINEAR_CLIENT_ID as string,
68 |                     clientSecret: process.env.LINEAR_CLIENT_SECRET as string,
69 |                     scope: ["read", "write"] // [!code highlight]
70 |                 },
71 |             },
72 |         })
73 |         ```
74 |     </Step>
75 | </Steps>
76 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import { validateAuthorizationCode, refreshAccessToken } from "../oauth2";
  4 | 
  5 | export interface RobloxProfile extends Record<string, any> {
  6 | 	/** the user's id */
  7 | 	sub: string;
  8 | 	/** the user's username */
  9 | 	preferred_username: string;
 10 | 	/** the user's display name, will return the same value as the preferred_username if not set */
 11 | 	nickname: string;
 12 | 	/** the user's display name, again, will return the same value as the preferred_username if not set */
 13 | 	name: string;
 14 | 	/** the account creation date as a unix timestamp in seconds */
 15 | 	created_at: number;
 16 | 	/** the user's profile URL */
 17 | 	profile: string;
 18 | 	/** the user's avatar URL */
 19 | 	picture: string;
 20 | }
 21 | 
 22 | export interface RobloxOptions extends ProviderOptions<RobloxProfile> {
 23 | 	clientId: string;
 24 | 	prompt?:
 25 | 		| "none"
 26 | 		| "consent"
 27 | 		| "login"
 28 | 		| "select_account"
 29 | 		| "select_account consent";
 30 | }
 31 | 
 32 | export const roblox = (options: RobloxOptions) => {
 33 | 	return {
 34 | 		id: "roblox",
 35 | 		name: "Roblox",
 36 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
 37 | 			const _scopes = options.disableDefaultScope ? [] : ["openid", "profile"];
 38 | 			options.scope && _scopes.push(...options.scope);
 39 | 			scopes && _scopes.push(...scopes);
 40 | 			return new URL(
 41 | 				`https://apis.roblox.com/oauth/v1/authorize?scope=${_scopes.join(
 42 | 					"+",
 43 | 				)}&response_type=code&client_id=${
 44 | 					options.clientId
 45 | 				}&redirect_uri=${encodeURIComponent(
 46 | 					options.redirectURI || redirectURI,
 47 | 				)}&state=${state}&prompt=${options.prompt || "select_account consent"}`,
 48 | 			);
 49 | 		},
 50 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 51 | 			return validateAuthorizationCode({
 52 | 				code,
 53 | 				redirectURI: options.redirectURI || redirectURI,
 54 | 				options,
 55 | 				tokenEndpoint: "https://apis.roblox.com/oauth/v1/token",
 56 | 				authentication: "post",
 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://apis.roblox.com/oauth/v1/token",
 70 | 					});
 71 | 				},
 72 | 		async getUserInfo(token) {
 73 | 			if (options.getUserInfo) {
 74 | 				return options.getUserInfo(token);
 75 | 			}
 76 | 			const { data: profile, error } = await betterFetch<RobloxProfile>(
 77 | 				"https://apis.roblox.com/oauth/v1/userinfo",
 78 | 				{
 79 | 					headers: {
 80 | 						authorization: `Bearer ${token.accessToken}`,
 81 | 					},
 82 | 				},
 83 | 			);
 84 | 
 85 | 			if (error) {
 86 | 				return null;
 87 | 			}
 88 | 
 89 | 			const userMap = await options.mapProfileToUser?.(profile);
 90 | 
 91 | 			return {
 92 | 				user: {
 93 | 					id: profile.sub,
 94 | 					name: profile.nickname || profile.preferred_username || "",
 95 | 					image: profile.picture,
 96 | 					email: profile.preferred_username || null, // Roblox does not provide email
 97 | 					emailVerified: true,
 98 | 					...userMap,
 99 | 				},
100 | 				data: {
101 | 					...profile,
102 | 				},
103 | 			};
104 | 		},
105 | 		options,
106 | 	} satisfies OAuthProvider<RobloxProfile>;
107 | };
108 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-mysql-passkey.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: varchar("id", { length: 36 }).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 | });
22 | 
23 | export const custom_session = mysqlTable("custom_session", {
24 |   id: varchar("id", { length: 36 }).primaryKey(),
25 |   expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
26 |   token: varchar("token", { length: 255 }).notNull().unique(),
27 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
28 |   updatedAt: timestamp("updated_at", { fsp: 3 })
29 |     .$onUpdate(() => /* @__PURE__ */ new Date())
30 |     .notNull(),
31 |   ipAddress: text("ip_address"),
32 |   userAgent: text("user_agent"),
33 |   userId: varchar("user_id", { length: 36 })
34 |     .notNull()
35 |     .references(() => custom_user.id, { onDelete: "cascade" }),
36 | });
37 | 
38 | export const custom_account = mysqlTable("custom_account", {
39 |   id: varchar("id", { length: 36 }).primaryKey(),
40 |   accountId: text("account_id").notNull(),
41 |   providerId: text("provider_id").notNull(),
42 |   userId: varchar("user_id", { length: 36 })
43 |     .notNull()
44 |     .references(() => custom_user.id, { onDelete: "cascade" }),
45 |   accessToken: text("access_token"),
46 |   refreshToken: text("refresh_token"),
47 |   idToken: text("id_token"),
48 |   accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }),
49 |   refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }),
50 |   scope: text("scope"),
51 |   password: text("password"),
52 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
53 |   updatedAt: timestamp("updated_at", { fsp: 3 })
54 |     .$onUpdate(() => /* @__PURE__ */ new Date())
55 |     .notNull(),
56 | });
57 | 
58 | export const custom_verification = mysqlTable("custom_verification", {
59 |   id: varchar("id", { length: 36 }).primaryKey(),
60 |   identifier: text("identifier").notNull(),
61 |   value: text("value").notNull(),
62 |   expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
63 |   createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
64 |   updatedAt: timestamp("updated_at", { fsp: 3 })
65 |     .defaultNow()
66 |     .$onUpdate(() => /* @__PURE__ */ new Date())
67 |     .notNull(),
68 | });
69 | 
70 | export const passkey = mysqlTable("passkey", {
71 |   id: varchar("id", { length: 36 }).primaryKey(),
72 |   name: text("name"),
73 |   publicKey: text("public_key").notNull(),
74 |   userId: varchar("user_id", { length: 36 })
75 |     .notNull()
76 |     .references(() => custom_user.id, { onDelete: "cascade" }),
77 |   credentialID: text("credential_id").notNull(),
78 |   counter: int("counter").notNull(),
79 |   deviceType: text("device_type").notNull(),
80 |   backedUp: boolean("backed_up").notNull(),
81 |   transports: text("transports"),
82 |   createdAt: timestamp("created_at", { fsp: 3 }),
83 |   aaguid: text("aaguid"),
84 | });
85 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import { useState, useTransition } from "react";
  4 | import { useRouter, useSearchParams } from "next/navigation";
  5 | import { client, useSession } from "@/lib/auth-client";
  6 | import { Card } from "@/components/ui/card";
  7 | import { Button } from "@/components/ui/button";
  8 | import { Alert, AlertDescription } from "@/components/ui/alert";
  9 | import { Loader2, Check, X } from "lucide-react";
 10 | 
 11 | export default function DeviceApprovalPage() {
 12 | 	const router = useRouter();
 13 | 	const searchParams = useSearchParams();
 14 | 	const userCode = searchParams.get("user_code");
 15 | 	const { data: session } = useSession();
 16 | 	const [isApprovePending, startApproveTransition] = useTransition();
 17 | 	const [isDenyPending, startDenyTransition] = useTransition();
 18 | 	const [error, setError] = useState<string | null>(null);
 19 | 
 20 | 	const handleApprove = () => {
 21 | 		if (!userCode) return;
 22 | 
 23 | 		setError(null);
 24 | 
 25 | 		startApproveTransition(async () => {
 26 | 			try {
 27 | 				await client.device.approve({
 28 | 					userCode,
 29 | 				});
 30 | 				router.push("/device/success");
 31 | 			} catch (err: any) {
 32 | 				setError(err.error?.message || "Failed to approve device");
 33 | 			}
 34 | 		});
 35 | 	};
 36 | 
 37 | 	const handleDeny = () => {
 38 | 		if (!userCode) return;
 39 | 
 40 | 		setError(null);
 41 | 
 42 | 		startDenyTransition(async () => {
 43 | 			try {
 44 | 				await client.device.deny({
 45 | 					userCode,
 46 | 				});
 47 | 				router.push("/device/denied");
 48 | 			} catch (err: any) {
 49 | 				setError(err.error?.message || "Failed to deny device");
 50 | 			}
 51 | 		});
 52 | 	};
 53 | 
 54 | 	if (!session) {
 55 | 		return null;
 56 | 	}
 57 | 
 58 | 	return (
 59 | 		<div className="flex min-h-screen items-center justify-center p-4">
 60 | 			<Card className="w-full max-w-md p-6">
 61 | 				<div className="space-y-4">
 62 | 					<div className="text-center">
 63 | 						<h1 className="text-2xl font-bold">Approve Device</h1>
 64 | 						<p className="text-muted-foreground mt-2">
 65 | 							A device is requesting access to your account
 66 | 						</p>
 67 | 					</div>
 68 | 
 69 | 					<div className="space-y-4">
 70 | 						<div className="rounded-lg bg-muted p-4">
 71 | 							<p className="text-sm font-medium">Device Code</p>
 72 | 							<p className="font-mono text-lg">{userCode}</p>
 73 | 						</div>
 74 | 
 75 | 						<div className="rounded-lg bg-muted p-4">
 76 | 							<p className="text-sm font-medium">Signed in as</p>
 77 | 							<p>{session.user.email}</p>
 78 | 						</div>
 79 | 
 80 | 						{error && (
 81 | 							<Alert variant="destructive">
 82 | 								<AlertDescription>{error}</AlertDescription>
 83 | 							</Alert>
 84 | 						)}
 85 | 
 86 | 						<div className="flex gap-3">
 87 | 							<Button
 88 | 								onClick={handleDeny}
 89 | 								variant="outline"
 90 | 								className="flex-1"
 91 | 								disabled={isDenyPending}
 92 | 							>
 93 | 								{isDenyPending ? (
 94 | 									<Loader2 className="h-4 w-4 animate-spin" />
 95 | 								) : (
 96 | 									<>
 97 | 										<X className="mr-2 h-4 w-4" />
 98 | 										Deny
 99 | 									</>
100 | 								)}
101 | 							</Button>
102 | 							<Button
103 | 								onClick={handleApprove}
104 | 								className="flex-1"
105 | 								disabled={isApprovePending}
106 | 							>
107 | 								{isApprovePending ? (
108 | 									<Loader2 className="h-4 w-4 animate-spin" />
109 | 								) : (
110 | 									<>
111 | 										<Check className="mr-2 h-4 w-4" />
112 | 										Approve
113 | 									</>
114 | 								)}
115 | 							</Button>
116 | 						</div>
117 | 					</div>
118 | 				</div>
119 | 			</Card>
120 | 		</div>
121 | 	);
122 | }
123 | 
```

--------------------------------------------------------------------------------
/packages/core/src/env/env-impl.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /// <reference types="node" />
  2 | /// <reference types="bun" />
  3 | //https://github.com/unjs/std-env/blob/main/src/env.ts
  4 | 
  5 | const _envShim = Object.create(null);
  6 | 
  7 | export type EnvObject = Record<string, string | undefined>;
  8 | 
  9 | const _getEnv = (useShim?: boolean) =>
 10 | 	globalThis.process?.env ||
 11 | 	//@ts-expect-error
 12 | 	globalThis.Deno?.env.toObject() ||
 13 | 	//@ts-expect-error
 14 | 	globalThis.__env__ ||
 15 | 	(useShim ? _envShim : globalThis);
 16 | 
 17 | export const env = new Proxy<EnvObject>(_envShim, {
 18 | 	get(_, prop) {
 19 | 		const env = _getEnv();
 20 | 		return env[prop as any] ?? _envShim[prop];
 21 | 	},
 22 | 	has(_, prop) {
 23 | 		const env = _getEnv();
 24 | 		return prop in env || prop in _envShim;
 25 | 	},
 26 | 	set(_, prop, value) {
 27 | 		const env = _getEnv(true);
 28 | 		env[prop as any] = value;
 29 | 		return true;
 30 | 	},
 31 | 	deleteProperty(_, prop) {
 32 | 		if (!prop) {
 33 | 			return false;
 34 | 		}
 35 | 		const env = _getEnv(true);
 36 | 		delete env[prop as any];
 37 | 		return true;
 38 | 	},
 39 | 	ownKeys() {
 40 | 		const env = _getEnv(true);
 41 | 		return Object.keys(env);
 42 | 	},
 43 | });
 44 | 
 45 | function toBoolean(val: boolean | string | undefined) {
 46 | 	return val ? val !== "false" : false;
 47 | }
 48 | 
 49 | export const nodeENV =
 50 | 	(typeof process !== "undefined" && process.env && process.env.NODE_ENV) || "";
 51 | 
 52 | /** Detect if `NODE_ENV` environment variable is `production` */
 53 | export const isProduction = nodeENV === "production";
 54 | 
 55 | /** Detect if `NODE_ENV` environment variable is `dev` or `development` */
 56 | export const isDevelopment = nodeENV === "dev" || nodeENV === "development";
 57 | 
 58 | /** Detect if `NODE_ENV` environment variable is `test` */
 59 | export const isTest = () => nodeENV === "test" || toBoolean(env.TEST);
 60 | 
 61 | /**
 62 |  * Get environment variable with fallback
 63 |  */
 64 | export function getEnvVar<Fallback extends string>(
 65 | 	key: string,
 66 | 	fallback?: Fallback,
 67 | ): Fallback extends string ? string : string | undefined {
 68 | 	if (typeof process !== "undefined" && process.env) {
 69 | 		return process.env[key] ?? (fallback as any);
 70 | 	}
 71 | 
 72 | 	// @ts-expect-error deno
 73 | 	if (typeof Deno !== "undefined") {
 74 | 		// @ts-expect-error deno
 75 | 		return Deno.env.get(key) ?? (fallback as string);
 76 | 	}
 77 | 
 78 | 	// Handle Bun
 79 | 	if (typeof Bun !== "undefined") {
 80 | 		return Bun.env[key] ?? (fallback as string);
 81 | 	}
 82 | 
 83 | 	return fallback as any;
 84 | }
 85 | 
 86 | /**
 87 |  * Get boolean environment variable
 88 |  */
 89 | export function getBooleanEnvVar(key: string, fallback = true): boolean {
 90 | 	const value = getEnvVar(key);
 91 | 	if (!value) return fallback;
 92 | 	return value !== "0" && value.toLowerCase() !== "false" && value !== "";
 93 | }
 94 | 
 95 | /**
 96 |  * Common environment variables used in Better Auth
 97 |  */
 98 | export const ENV = Object.freeze({
 99 | 	get BETTER_AUTH_SECRET() {
100 | 		return getEnvVar("BETTER_AUTH_SECRET");
101 | 	},
102 | 	get AUTH_SECRET() {
103 | 		return getEnvVar("AUTH_SECRET");
104 | 	},
105 | 	get BETTER_AUTH_TELEMETRY() {
106 | 		return getEnvVar("BETTER_AUTH_TELEMETRY");
107 | 	},
108 | 	get BETTER_AUTH_TELEMETRY_ID() {
109 | 		return getEnvVar("BETTER_AUTH_TELEMETRY_ID");
110 | 	},
111 | 	get NODE_ENV() {
112 | 		return getEnvVar("NODE_ENV", "development");
113 | 	},
114 | 	get PACKAGE_VERSION() {
115 | 		return getEnvVar("PACKAGE_VERSION", "0.0.0");
116 | 	},
117 | 	get BETTER_AUTH_TELEMETRY_ENDPOINT() {
118 | 		return getEnvVar(
119 | 			"BETTER_AUTH_TELEMETRY_ENDPOINT",
120 | 			"https://telemetry.better-auth.com/v1/track",
121 | 		);
122 | 	},
123 | });
124 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/query.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 | 	BetterFetchError,
  3 | 	type BetterFetch,
  4 | 	type BetterFetchOption,
  5 | } from "@better-fetch/fetch";
  6 | import { atom, onMount, type PreinitializedWritableAtom } from "nanostores";
  7 | import type { SessionQueryParams } from "./types";
  8 | 
  9 | // SSR detection
 10 | const isServer = typeof window === "undefined";
 11 | 
 12 | export const useAuthQuery = <T>(
 13 | 	initializedAtom:
 14 | 		| PreinitializedWritableAtom<any>
 15 | 		| PreinitializedWritableAtom<any>[],
 16 | 	path: string,
 17 | 	$fetch: BetterFetch,
 18 | 	options?:
 19 | 		| ((value: {
 20 | 				data: null | T;
 21 | 				error: null | BetterFetchError;
 22 | 				isPending: boolean;
 23 | 		  }) => BetterFetchOption)
 24 | 		| BetterFetchOption,
 25 | ) => {
 26 | 	const value = atom<{
 27 | 		data: null | T;
 28 | 		error: null | BetterFetchError;
 29 | 		isPending: boolean;
 30 | 		isRefetching: boolean;
 31 | 		refetch: (queryParams?: { query?: SessionQueryParams }) => void;
 32 | 	}>({
 33 | 		data: null,
 34 | 		error: null,
 35 | 		isPending: true,
 36 | 		isRefetching: false,
 37 | 		refetch: (queryParams?: { query?: SessionQueryParams }) => {
 38 | 			return fn(queryParams);
 39 | 		},
 40 | 	});
 41 | 
 42 | 	const fn = (queryParams?: { query?: SessionQueryParams }) => {
 43 | 		const opts =
 44 | 			typeof options === "function"
 45 | 				? options({
 46 | 						data: value.get().data,
 47 | 						error: value.get().error,
 48 | 						isPending: value.get().isPending,
 49 | 					})
 50 | 				: options;
 51 | 
 52 | 		$fetch<T>(path, {
 53 | 			...opts,
 54 | 			query: {
 55 | 				...opts?.query,
 56 | 				...queryParams?.query,
 57 | 			},
 58 | 			async onSuccess(context) {
 59 | 				value.set({
 60 | 					data: context.data,
 61 | 					error: null,
 62 | 					isPending: false,
 63 | 					isRefetching: false,
 64 | 					refetch: value.value.refetch,
 65 | 				});
 66 | 				await opts?.onSuccess?.(context);
 67 | 			},
 68 | 			async onError(context) {
 69 | 				const { request } = context;
 70 | 				const retryAttempts =
 71 | 					typeof request.retry === "number"
 72 | 						? request.retry
 73 | 						: request.retry?.attempts;
 74 | 				const retryAttempt = request.retryAttempt || 0;
 75 | 				if (retryAttempts && retryAttempt < retryAttempts) return;
 76 | 				value.set({
 77 | 					error: context.error,
 78 | 					data: null,
 79 | 					isPending: false,
 80 | 					isRefetching: false,
 81 | 					refetch: value.value.refetch,
 82 | 				});
 83 | 				await opts?.onError?.(context);
 84 | 			},
 85 | 			async onRequest(context) {
 86 | 				const currentValue = value.get();
 87 | 				value.set({
 88 | 					isPending: currentValue.data === null,
 89 | 					data: currentValue.data,
 90 | 					error: null,
 91 | 					isRefetching: true,
 92 | 					refetch: value.value.refetch,
 93 | 				});
 94 | 				await opts?.onRequest?.(context);
 95 | 			},
 96 | 		}).catch((error) => {
 97 | 			value.set({
 98 | 				error,
 99 | 				data: null,
100 | 				isPending: false,
101 | 				isRefetching: false,
102 | 				refetch: value.value.refetch,
103 | 			});
104 | 		});
105 | 	};
106 | 	initializedAtom = Array.isArray(initializedAtom)
107 | 		? initializedAtom
108 | 		: [initializedAtom];
109 | 	let isMounted = false;
110 | 
111 | 	for (const initAtom of initializedAtom) {
112 | 		initAtom.subscribe(() => {
113 | 			if (isServer) {
114 | 				// On server, don't trigger fetch
115 | 				return;
116 | 			}
117 | 			if (isMounted) {
118 | 				fn();
119 | 			} else {
120 | 				onMount(value, () => {
121 | 					const timeoutId = setTimeout(() => {
122 | 						if (!isMounted) {
123 | 							fn();
124 | 							isMounted = true;
125 | 						}
126 | 					}, 0);
127 | 					return () => {
128 | 						value.off();
129 | 						initAtom.off();
130 | 						clearTimeout(timeoutId);
131 | 					};
132 | 				});
133 | 			}
134 | 		});
135 | 	}
136 | 	return value;
137 | };
138 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/kysely-adapter/dialect.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Kysely, MssqlDialect } from "kysely";
  2 | import {
  3 | 	type Dialect,
  4 | 	MysqlDialect,
  5 | 	PostgresDialect,
  6 | 	SqliteDialect,
  7 | } from "kysely";
  8 | import type { BetterAuthOptions } from "@better-auth/core";
  9 | import type { KyselyDatabaseType } from "./types";
 10 | 
 11 | export function getKyselyDatabaseType(
 12 | 	db: BetterAuthOptions["database"],
 13 | ): KyselyDatabaseType | null {
 14 | 	if (!db) {
 15 | 		return null;
 16 | 	}
 17 | 	if ("dialect" in db) {
 18 | 		return getKyselyDatabaseType(db.dialect as Dialect);
 19 | 	}
 20 | 	if ("createDriver" in db) {
 21 | 		if (db instanceof SqliteDialect) {
 22 | 			return "sqlite";
 23 | 		}
 24 | 		if (db instanceof MysqlDialect) {
 25 | 			return "mysql";
 26 | 		}
 27 | 		if (db instanceof PostgresDialect) {
 28 | 			return "postgres";
 29 | 		}
 30 | 		if (db instanceof MssqlDialect) {
 31 | 			return "mssql";
 32 | 		}
 33 | 	}
 34 | 	if ("aggregate" in db) {
 35 | 		return "sqlite";
 36 | 	}
 37 | 
 38 | 	if ("getConnection" in db) {
 39 | 		return "mysql";
 40 | 	}
 41 | 	if ("connect" in db) {
 42 | 		return "postgres";
 43 | 	}
 44 | 	if ("fileControl" in db) {
 45 | 		return "sqlite";
 46 | 	}
 47 | 	if ("open" in db && "close" in db && "prepare" in db) {
 48 | 		return "sqlite";
 49 | 	}
 50 | 	return null;
 51 | }
 52 | 
 53 | export const createKyselyAdapter = async (config: BetterAuthOptions) => {
 54 | 	const db = config.database;
 55 | 
 56 | 	if (!db) {
 57 | 		return {
 58 | 			kysely: null,
 59 | 			databaseType: null,
 60 | 			transaction: undefined,
 61 | 		};
 62 | 	}
 63 | 
 64 | 	if ("db" in db) {
 65 | 		return {
 66 | 			kysely: db.db,
 67 | 			databaseType: db.type,
 68 | 			transaction: db.transaction,
 69 | 		};
 70 | 	}
 71 | 
 72 | 	if ("dialect" in db) {
 73 | 		return {
 74 | 			kysely: new Kysely<any>({ dialect: db.dialect }),
 75 | 			databaseType: db.type,
 76 | 			transaction: db.transaction,
 77 | 		};
 78 | 	}
 79 | 
 80 | 	let dialect: Dialect | undefined = undefined;
 81 | 
 82 | 	const databaseType = getKyselyDatabaseType(db);
 83 | 
 84 | 	if ("createDriver" in db) {
 85 | 		dialect = db;
 86 | 	}
 87 | 
 88 | 	if ("aggregate" in db && !("createSession" in db)) {
 89 | 		dialect = new SqliteDialect({
 90 | 			database: db,
 91 | 		});
 92 | 	}
 93 | 
 94 | 	if ("getConnection" in db) {
 95 | 		// @ts-expect-error - mysql2/promise
 96 | 		dialect = new MysqlDialect(db);
 97 | 	}
 98 | 
 99 | 	if ("connect" in db) {
100 | 		dialect = new PostgresDialect({
101 | 			pool: db,
102 | 		});
103 | 	}
104 | 
105 | 	if ("fileControl" in db) {
106 | 		const { BunSqliteDialect } = await import("./bun-sqlite-dialect");
107 | 		dialect = new BunSqliteDialect({
108 | 			database: db,
109 | 		});
110 | 	}
111 | 
112 | 	if ("createSession" in db && typeof window === "undefined") {
113 | 		let DatabaseSync: typeof import("node:sqlite").DatabaseSync | undefined =
114 | 			undefined;
115 | 		try {
116 | 			let nodeSqlite: string = "node:sqlite";
117 | 			// Ignore both Vite and Webpack for dynamic import as they both try to pre-bundle 'node:sqlite' which might fail
118 | 			// It's okay because we are in a try-catch block
119 | 			({ DatabaseSync } = await import(
120 | 				/* @vite-ignore */
121 | 				/* webpackIgnore: true */
122 | 				nodeSqlite
123 | 			));
124 | 		} catch (error: unknown) {
125 | 			if (
126 | 				error !== null &&
127 | 				typeof error === "object" &&
128 | 				"code" in error &&
129 | 				error.code !== "ERR_UNKNOWN_BUILTIN_MODULE"
130 | 			) {
131 | 				throw error;
132 | 			}
133 | 		}
134 | 		if (DatabaseSync && db instanceof DatabaseSync) {
135 | 			const { NodeSqliteDialect } = await import("./node-sqlite-dialect");
136 | 			dialect = new NodeSqliteDialect({
137 | 				database: db,
138 | 			});
139 | 		}
140 | 	}
141 | 
142 | 	return {
143 | 		kysely: dialect ? new Kysely<any>({ dialect }) : null,
144 | 		databaseType,
145 | 		transaction: undefined,
146 | 	};
147 | };
148 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/db/secondary-storage.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { beforeEach, describe, expect, it } from "vitest";
  2 | import { getTestInstance } from "../test-utils/test-instance";
  3 | import { safeJSONParse } from "../utils/json";
  4 | 
  5 | describe("secondary storage - get returns JSON string", async () => {
  6 | 	let store = new Map<string, string>();
  7 | 
  8 | 	const { client, signInWithTestUser } = await getTestInstance({
  9 | 		secondaryStorage: {
 10 | 			set(key, value, ttl) {
 11 | 				store.set(key, value);
 12 | 			},
 13 | 			get(key) {
 14 | 				return store.get(key) || null;
 15 | 			},
 16 | 			delete(key) {
 17 | 				store.delete(key);
 18 | 			},
 19 | 		},
 20 | 		rateLimit: {
 21 | 			enabled: false,
 22 | 		},
 23 | 	});
 24 | 
 25 | 	beforeEach(() => {
 26 | 		store.clear();
 27 | 	});
 28 | 
 29 | 	it("should work end-to-end with string return", async () => {
 30 | 		expect(store.size).toBe(0);
 31 | 		const { headers } = await signInWithTestUser();
 32 | 		expect(store.size).toBe(2);
 33 | 
 34 | 		const s1 = await client.getSession({
 35 | 			fetchOptions: { headers },
 36 | 		});
 37 | 		expect(s1.data).toMatchObject({
 38 | 			session: {
 39 | 				userId: expect.any(String),
 40 | 				token: expect.any(String),
 41 | 				expiresAt: expect.any(Date),
 42 | 				ipAddress: expect.any(String),
 43 | 				userAgent: expect.any(String),
 44 | 			},
 45 | 			user: {
 46 | 				id: expect.any(String),
 47 | 				name: "test user",
 48 | 				email: "[email protected]",
 49 | 				emailVerified: false,
 50 | 				image: null,
 51 | 				createdAt: expect.any(Date),
 52 | 				updatedAt: expect.any(Date),
 53 | 			},
 54 | 		});
 55 | 
 56 | 		const list = await client.listSessions({ fetchOptions: { headers } });
 57 | 		expect(list.data?.length).toBe(1);
 58 | 
 59 | 		const token = s1.data!.session.token;
 60 | 		const revoke = await client.revokeSession({
 61 | 			fetchOptions: { headers },
 62 | 			token,
 63 | 		});
 64 | 		expect(revoke.data?.status).toBe(true);
 65 | 
 66 | 		const after = await client.getSession({ fetchOptions: { headers } });
 67 | 		expect(after.data).toBeNull();
 68 | 		expect(store.size).toBe(0);
 69 | 	});
 70 | });
 71 | 
 72 | describe("secondary storage - get returns already-parsed object", async () => {
 73 | 	let store = new Map<string, any>();
 74 | 
 75 | 	const { client, signInWithTestUser } = await getTestInstance({
 76 | 		secondaryStorage: {
 77 | 			set(key, value, ttl) {
 78 | 				store.set(key, safeJSONParse(value));
 79 | 			},
 80 | 			get(key) {
 81 | 				return store.get(key);
 82 | 			},
 83 | 			delete(key) {
 84 | 				store.delete(key);
 85 | 			},
 86 | 		},
 87 | 		rateLimit: {
 88 | 			enabled: false,
 89 | 		},
 90 | 	});
 91 | 
 92 | 	beforeEach(() => {
 93 | 		store.clear();
 94 | 	});
 95 | 
 96 | 	it("should work end-to-end with object return", async () => {
 97 | 		const { headers } = await signInWithTestUser();
 98 | 
 99 | 		const s1 = await client.getSession({ fetchOptions: { headers } });
100 | 		expect(s1.data).not.toBeNull();
101 | 
102 | 		const userId = s1.data!.session.userId;
103 | 		const activeList = store.get(`active-sessions-${userId}`);
104 | 		expect(Array.isArray(activeList)).toBe(true);
105 | 		expect(activeList.length).toBe(1);
106 | 
107 | 		const list = await client.listSessions({ fetchOptions: { headers } });
108 | 		expect(list.data?.length).toBe(1);
109 | 
110 | 		const token = s1.data!.session.token;
111 | 		const revoke = await client.revokeSession({
112 | 			fetchOptions: { headers },
113 | 			token,
114 | 		});
115 | 		expect(revoke.data?.status).toBe(true);
116 | 
117 | 		const after = await client.getSession({ fetchOptions: { headers } });
118 | 		expect(after.data).toBeNull();
119 | 		const activeAfter = store.get(`active-sessions-${userId}`);
120 | 		expect(activeAfter ?? null).toBeNull();
121 | 	});
122 | });
123 | 
```

--------------------------------------------------------------------------------
/packages/core/src/types/plugin.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { Migration } from "kysely";
  2 | import type { AuthContext } from "./context";
  3 | import type {
  4 | 	Endpoint,
  5 | 	EndpointContext,
  6 | 	InputContext,
  7 | 	Middleware,
  8 | } from "better-call";
  9 | import type { BetterAuthPluginDBSchema } from "../db";
 10 | import type { LiteralString } from "./helper";
 11 | import type { BetterAuthOptions } from "./init-options";
 12 | import type { AuthMiddleware } from "../api";
 13 | 
 14 | type Awaitable<T> = T | Promise<T>;
 15 | type DeepPartial<T> = T extends Function
 16 | 	? T
 17 | 	: T extends object
 18 | 		? { [K in keyof T]?: DeepPartial<T[K]> }
 19 | 		: T;
 20 | 
 21 | export type HookEndpointContext = Partial<
 22 | 	EndpointContext<string, any> & Omit<InputContext<string, any>, "method">
 23 | > & {
 24 | 	path: string;
 25 | 	context: AuthContext & {
 26 | 		returned?: unknown;
 27 | 		responseHeaders?: Headers;
 28 | 	};
 29 | 	headers?: Headers | undefined;
 30 | };
 31 | 
 32 | export type BetterAuthPlugin = {
 33 | 	id: LiteralString;
 34 | 	/**
 35 | 	 * The init function is called when the plugin is initialized.
 36 | 	 * You can return a new context or modify the existing context.
 37 | 	 */
 38 | 	init?: (ctx: AuthContext) =>
 39 | 		| Awaitable<{
 40 | 				context?: DeepPartial<Omit<AuthContext, "options">>;
 41 | 				options?: Partial<BetterAuthOptions>;
 42 | 		  }>
 43 | 		| void
 44 | 		| Promise<void>;
 45 | 	endpoints?: {
 46 | 		[key: string]: Endpoint;
 47 | 	};
 48 | 	middlewares?: {
 49 | 		path: string;
 50 | 		middleware: Middleware;
 51 | 	}[];
 52 | 	onRequest?: (
 53 | 		request: Request,
 54 | 		ctx: AuthContext,
 55 | 	) => Promise<
 56 | 		| {
 57 | 				response: Response;
 58 | 		  }
 59 | 		| {
 60 | 				request: Request;
 61 | 		  }
 62 | 		| void
 63 | 	>;
 64 | 	onResponse?: (
 65 | 		response: Response,
 66 | 		ctx: AuthContext,
 67 | 	) => Promise<{
 68 | 		response: Response;
 69 | 	} | void>;
 70 | 	hooks?: {
 71 | 		before?: {
 72 | 			matcher: (context: HookEndpointContext) => boolean;
 73 | 			handler: AuthMiddleware;
 74 | 		}[];
 75 | 		after?: {
 76 | 			matcher: (context: HookEndpointContext) => boolean;
 77 | 			handler: AuthMiddleware;
 78 | 		}[];
 79 | 	};
 80 | 	/**
 81 | 	 * Schema the plugin needs
 82 | 	 *
 83 | 	 * This will also be used to migrate the database. If the fields are dynamic from the plugins
 84 | 	 * configuration each time the configuration is changed a new migration will be created.
 85 | 	 *
 86 | 	 * NOTE: If you want to create migrations manually using
 87 | 	 * migrations option or any other way you
 88 | 	 * can disable migration per table basis.
 89 | 	 *
 90 | 	 * @example
 91 | 	 * ```ts
 92 | 	 * schema: {
 93 | 	 * 	user: {
 94 | 	 * 		fields: {
 95 | 	 * 			email: {
 96 | 	 * 				 type: "string",
 97 | 	 * 			},
 98 | 	 * 			emailVerified: {
 99 | 	 * 				type: "boolean",
100 | 	 * 				defaultValue: false,
101 | 	 * 			},
102 | 	 * 		},
103 | 	 * 	}
104 | 	 * } as AuthPluginSchema
105 | 	 * ```
106 | 	 */
107 | 	schema?: BetterAuthPluginDBSchema;
108 | 	/**
109 | 	 * The migrations of the plugin. If you define schema that will automatically create
110 | 	 * migrations for you.
111 | 	 *
112 | 	 * ⚠️ Only uses this if you dont't want to use the schema option and you disabled migrations for
113 | 	 * the tables.
114 | 	 */
115 | 	migrations?: Record<string, Migration>;
116 | 	/**
117 | 	 * The options of the plugin
118 | 	 */
119 | 	options?: Record<string, any> | undefined;
120 | 	/**
121 | 	 * types to be inferred
122 | 	 */
123 | 	$Infer?: Record<string, any>;
124 | 	/**
125 | 	 * The rate limit rules to apply to specific paths.
126 | 	 */
127 | 	rateLimit?: {
128 | 		window: number;
129 | 		max: number;
130 | 		pathMatcher: (path: string) => boolean;
131 | 	}[];
132 | 	/**
133 | 	 * The error codes returned by the plugin
134 | 	 */
135 | 	$ERROR_CODES?: Record<string, string>;
136 | };
137 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/open-api.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Open API
 3 | description: Open API reference for Better Auth.
 4 | ---
 5 | 
 6 | This is a plugin that provides an Open API reference for Better Auth. It shows all endpoints added by plugins and the core. It also provides a way to test the endpoints. It uses [Scalar](https://scalar.com/) to display the Open API reference.
 7 | 
 8 | 
 9 | <Callout>
10 | This plugin is still in the early stages of development. We are working on adding more features to it and filling in the gaps.
11 | </Callout>
12 | 
13 | 
14 | ## Installation
15 | 
16 | <Steps>
17 |     <Step>
18 |     ### Add the plugin to your **auth** config
19 |     ```ts title="auth.ts"
20 |     import { betterAuth } from "better-auth"
21 |     import { openAPI } from "better-auth/plugins"
22 | 
23 |     export const auth = betterAuth({
24 |         plugins: [ // [!code highlight]
25 |             openAPI(), // [!code highlight]
26 |         ] // [!code highlight]
27 |     })
28 |     ```
29 |     </Step>
30 |     <Step>
31 |     ### Navigate to `/api/auth/reference` to view the Open API reference
32 |     
33 |     Each plugin endpoints are grouped by the plugin name. The core endpoints are grouped under the `Default` group. And Model schemas are grouped under the `Models` group.
34 | 
35 |     ![Open API reference](/open-api-reference.png)
36 |     </Step>
37 | </Steps>    
38 | 
39 | 
40 | 
41 | ## Usage 
42 | 
43 | The Open API reference is generated using the [OpenAPI 3.0](https://swagger.io/specification/) specification. You can use the reference to generate client libraries, documentation, and more.
44 | 
45 | The reference is generated using the [Scalar](https://scalar.com/) library. Scalar provides a way to view and test the endpoints. You can test the endpoints by clicking on the `Try it out` button and providing the required parameters.
46 | 
47 | ![Open API reference](/open-api-reference.png)
48 | 
49 | ### Generated Schema
50 | 
51 | To get the generated Open API schema directly as JSON, you can do `auth.api.generateOpenAPISchema()`. This will return the Open API schema as a JSON object.
52 | 
53 | ```ts
54 | import { auth } from "~/lib/auth"
55 | 
56 | const openAPISchema = await auth.api.generateOpenAPISchema()
57 | console.log(openAPISchema)
58 | ```
59 | 
60 | ### Using Scalar with Multiple Sources
61 | 
62 | If you're using Scalar for your API documentation, you can add Better Auth as an additional source alongside your main API:
63 | 
64 | When using Hono with Scalar for OpenAPI documentation, you can integrate Better Auth by adding it as a source:
65 | 
66 | ```ts
67 | app.get("/docs", Scalar({
68 |   pageTitle: "API Documentation", 
69 |   sources: [
70 |     { url: "/api/open-api", title: "API" },
71 |     // Better Auth schema generation endpoint
72 |     { url: "/api/auth/open-api/generate-schema", title: "Auth" },
73 |   ],
74 | }));
75 | ```
76 | 
77 | ## Configuration
78 | 
79 | `path` - The path where the Open API reference is served. Default is `/api/auth/reference`. You can change it to any path you like, but keep in mind that it will be appended to the base path of your auth server.
80 | 
81 | `disableDefaultReference` - If set to `true`, the default Open API reference UI by Scalar will be disabled. Default is `false`.
82 | 
83 | This allows you to display both your application's API and Better Auth's authentication endpoints in a unified documentation interface.
84 | 
85 | `theme` - Allows you to change the theme of the OpenAPI reference page. Default is `default`.
86 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import { useState } from "react";
  4 | import { motion } from "framer-motion";
  5 | import { cn } from "@/lib/utils";
  6 | 
  7 | type Tab = {
  8 | 	title: string;
  9 | 	value: string;
 10 | 	content?: string | React.ReactNode | any;
 11 | };
 12 | 
 13 | export const Tabs = ({
 14 | 	tabs: propTabs,
 15 | 	containerClassName,
 16 | 	activeTabClassName,
 17 | 	tabClassName,
 18 | 	contentClassName,
 19 | }: {
 20 | 	tabs: Tab[];
 21 | 	containerClassName?: string;
 22 | 	activeTabClassName?: string;
 23 | 	tabClassName?: string;
 24 | 	contentClassName?: string;
 25 | }) => {
 26 | 	const [active, setActive] = useState<Tab>(propTabs[0]);
 27 | 	const [tabs, setTabs] = useState<Tab[]>(propTabs);
 28 | 
 29 | 	const moveSelectedTabToTop = (idx: number) => {
 30 | 		const newTabs = [...propTabs];
 31 | 		const selectedTab = newTabs.splice(idx, 1);
 32 | 		newTabs.unshift(selectedTab[0]);
 33 | 		setTabs(newTabs);
 34 | 		setActive(newTabs[0]);
 35 | 	};
 36 | 
 37 | 	const [hovering, setHovering] = useState(false);
 38 | 
 39 | 	return (
 40 | 		<>
 41 | 			<div
 42 | 				className={cn(
 43 | 					"flex flex-row items-center justify-start mt-0 perspective-[1000px] relative overflow-auto sm:overflow-visible no-visible-scrollbar border-x w-full border-t max-w-max bg-opacity-0",
 44 | 					containerClassName,
 45 | 				)}
 46 | 			>
 47 | 				{propTabs.map((tab, idx) => (
 48 | 					<button
 49 | 						key={tab.title}
 50 | 						onClick={() => {
 51 | 							moveSelectedTabToTop(idx);
 52 | 						}}
 53 | 						onMouseEnter={() => setHovering(true)}
 54 | 						onMouseLeave={() => setHovering(false)}
 55 | 						className={cn(
 56 | 							"relative px-4 py-2 rounded-full opacity-80 hover:opacity-100",
 57 | 							tabClassName,
 58 | 						)}
 59 | 						style={{
 60 | 							transformStyle: "preserve-3d",
 61 | 						}}
 62 | 					>
 63 | 						{active.value === tab.value && (
 64 | 							<motion.div
 65 | 								transition={{
 66 | 									duration: 0.2,
 67 | 									delay: 0.1,
 68 | 
 69 | 									type: "keyframes",
 70 | 								}}
 71 | 								animate={{
 72 | 									x: tabs.indexOf(tab) === 0 ? [0, 0, 0] : [0, 0, 0],
 73 | 								}}
 74 | 								className={cn(
 75 | 									"absolute inset-0 bg-gray-200 dark:bg-zinc-900/90 opacity-100",
 76 | 									activeTabClassName,
 77 | 								)}
 78 | 							/>
 79 | 						)}
 80 | 
 81 | 						<span
 82 | 							className={cn(
 83 | 								"relative block text-black dark:text-white",
 84 | 								active.value === tab.value
 85 | 									? "text-opacity-100 font-medium"
 86 | 									: "opacity-40 ",
 87 | 							)}
 88 | 						>
 89 | 							{tab.title}
 90 | 						</span>
 91 | 					</button>
 92 | 				))}
 93 | 			</div>
 94 | 			<FadeInDiv
 95 | 				tabs={tabs}
 96 | 				active={active}
 97 | 				key={active.value}
 98 | 				hovering={hovering}
 99 | 				className={cn("", contentClassName)}
100 | 			/>
101 | 		</>
102 | 	);
103 | };
104 | 
105 | export const FadeInDiv = ({
106 | 	className,
107 | 	tabs,
108 | }: {
109 | 	className?: string;
110 | 	key?: string;
111 | 	tabs: Tab[];
112 | 	active: Tab;
113 | 	hovering?: boolean;
114 | }) => {
115 | 	const isActive = (tab: Tab) => {
116 | 		return tab.value === tabs[0].value;
117 | 	};
118 | 	return (
119 | 		<div className="relative w-full h-full">
120 | 			{tabs.map((tab, idx) => (
121 | 				<motion.div
122 | 					key={tab.value}
123 | 					style={{
124 | 						scale: 1 - idx * 0.1,
125 | 						zIndex: -idx,
126 | 						opacity: idx < 3 ? 1 - idx * 0.1 : 0,
127 | 					}}
128 | 					animate={{
129 | 						transition: {
130 | 							duration: 0.2,
131 | 							delay: 0.1,
132 | 							type: "keyframes",
133 | 						},
134 | 					}}
135 | 					className={cn(
136 | 						"w-full h-full",
137 | 						isActive(tab) ? "" : "hidden",
138 | 						className,
139 | 					)}
140 | 				>
141 | 					{tab.content}
142 | 				</motion.div>
143 | 			))}
144 | 		</div>
145 | 	);
146 | };
147 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import ReactMarkdown from "react-markdown";
  4 | import remarkGfm from "remark-gfm";
  5 | import rehypeHighlight from "rehype-highlight";
  6 | import "highlight.js/styles/dark.css";
  7 | import { Pre } from "fumadocs-ui/components/codeblock";
  8 | 
  9 | interface MarkdownRendererProps {
 10 | 	content: string;
 11 | 	className?: string;
 12 | }
 13 | 
 14 | export function MarkdownRenderer({
 15 | 	content,
 16 | 	className = "",
 17 | }: MarkdownRendererProps) {
 18 | 	return (
 19 | 		<div className={`markdown-content px-2 ${className}`}>
 20 | 			<ReactMarkdown
 21 | 				remarkPlugins={[remarkGfm]}
 22 | 				rehypePlugins={[[rehypeHighlight]]}
 23 | 				components={{
 24 | 					pre: (props) => (
 25 | 						<div className="my-4 max-w-full overflow-hidden">
 26 | 							<Pre {...props} />
 27 | 						</div>
 28 | 					),
 29 | 
 30 | 					code: ({ className, children, ...props }: any) => {
 31 | 						const isInline = !className?.includes("language-");
 32 | 
 33 | 						if (isInline) {
 34 | 							return (
 35 | 								<code
 36 | 									className="bg-muted px-1.5 py-0.5 rounded text-xs font-mono"
 37 | 									{...props}
 38 | 								>
 39 | 									{children}
 40 | 								</code>
 41 | 							);
 42 | 						}
 43 | 
 44 | 						return (
 45 | 							<code className={className} {...props}>
 46 | 								{children}
 47 | 							</code>
 48 | 						);
 49 | 					},
 50 | 
 51 | 					h1: ({ children }) => (
 52 | 						<h1 className="text-lg font-bold mt-6 mb-3 first:mt-0">
 53 | 							{children}
 54 | 						</h1>
 55 | 					),
 56 | 					h2: ({ children }) => (
 57 | 						<h2 className="text-base font-semibold mt-5 mb-3">{children}</h2>
 58 | 					),
 59 | 					h3: ({ children }) => (
 60 | 						<h3 className="text-sm font-semibold mt-4 mb-2">{children}</h3>
 61 | 					),
 62 | 					h4: ({ children }) => (
 63 | 						<h4 className="text-sm font-medium mt-3 mb-2">{children}</h4>
 64 | 					),
 65 | 					h5: ({ children }) => (
 66 | 						<h5 className="text-xs font-medium mt-3 mb-2">{children}</h5>
 67 | 					),
 68 | 					h6: ({ children }) => (
 69 | 						<h6 className="text-xs font-medium mt-3 mb-2">{children}</h6>
 70 | 					),
 71 | 
 72 | 					p: ({ children }) => (
 73 | 						<p className="text-sm leading-relaxed mb-3 last:mb-0">{children}</p>
 74 | 					),
 75 | 
 76 | 					a: ({ href, children }) => (
 77 | 						<a
 78 | 							href={href}
 79 | 							target="_blank"
 80 | 							rel="noopener noreferrer"
 81 | 							className="text-primary underline hover:text-primary/80 text-sm transition-colors"
 82 | 						>
 83 | 							{children}
 84 | 						</a>
 85 | 					),
 86 | 					blockquote: ({ children }) => (
 87 | 						<blockquote className="border-l-4 border-muted-foreground/20 pl-4 my-4 text-sm italic">
 88 | 							{children}
 89 | 						</blockquote>
 90 | 					),
 91 | 
 92 | 					table: ({ children }) => (
 93 | 						<div className="overflow-x-auto my-4 max-w-full">
 94 | 							<table className="min-w-full text-sm border-collapse border border-border">
 95 | 								{children}
 96 | 							</table>
 97 | 						</div>
 98 | 					),
 99 | 					th: ({ children }) => (
100 | 						<th className="border border-border px-2 py-1 bg-muted text-left font-medium">
101 | 							{children}
102 | 						</th>
103 | 					),
104 | 					td: ({ children }) => (
105 | 						<td className="border border-border px-2 py-1">{children}</td>
106 | 					),
107 | 
108 | 					hr: () => <hr className="my-6 border-border" />,
109 | 
110 | 					strong: ({ children }) => (
111 | 						<strong className="font-semibold">{children}</strong>
112 | 					),
113 | 					em: ({ children }) => <em className="italic">{children}</em>,
114 | 				}}
115 | 			>
116 | 				{content}
117 | 			</ReactMarkdown>
118 | 		</div>
119 | 	);
120 | }
121 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/tsdown.config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { defineConfig } from "tsdown";
  2 | 
  3 | export default defineConfig({
  4 | 	dts: true,
  5 | 	format: ["esm", "cjs"],
  6 | 	entry: [
  7 | 		"./src/index.ts",
  8 | 		"./src/social-providers/index.ts",
  9 | 		"./src/client/index.ts",
 10 | 		"./src/client/plugins/index.ts",
 11 | 		"./src/types/index.ts",
 12 | 		"./src/crypto/index.ts",
 13 | 		"./src/cookies/index.ts",
 14 | 		"./src/adapters/prisma-adapter/index.ts",
 15 | 		"./src/adapters/drizzle-adapter/index.ts",
 16 | 		"./src/adapters/mongodb-adapter/index.ts",
 17 | 		"./src/adapters/kysely-adapter/index.ts",
 18 | 		"./src/adapters/memory-adapter/index.ts",
 19 | 		"./src/adapters/test.ts",
 20 | 		"./src/adapters/index.ts",
 21 | 		"./src/db/index.ts",
 22 | 		"./src/oauth2/index.ts",
 23 | 		"./src/client/react/index.ts",
 24 | 		"./src/client/vue/index.ts",
 25 | 		"./src/client/svelte/index.ts",
 26 | 		"./src/client/solid/index.ts",
 27 | 		"./src/client/lynx/index.ts",
 28 | 		"./src/plugins/index.ts",
 29 | 		"./src/plugins/access/index.ts",
 30 | 		"./src/api/index.ts",
 31 | 		"./src/integrations/svelte-kit.ts",
 32 | 		"./src/integrations/solid-start.ts",
 33 | 		"./src/integrations/next-js.ts",
 34 | 		"./src/integrations/react-start.ts",
 35 | 		"./src/integrations/node.ts",
 36 | 		"./src/plugins/admin/index.ts",
 37 | 		"./src/plugins/admin/access/index.ts",
 38 | 		"./src/plugins/anonymous/index.ts",
 39 | 		"./src/plugins/bearer/index.ts",
 40 | 		"./src/plugins/captcha/index.ts",
 41 | 		"./src/plugins/custom-session/index.ts",
 42 | 		"./src/plugins/device-authorization/index.ts",
 43 | 		"./src/plugins/email-otp/index.ts",
 44 | 		"./src/plugins/generic-oauth/index.ts",
 45 | 		"./src/plugins/jwt/index.ts",
 46 | 		"./src/plugins/magic-link/index.ts",
 47 | 		"./src/plugins/multi-session/index.ts",
 48 | 		"./src/plugins/one-tap/index.ts",
 49 | 		"./src/plugins/open-api/index.ts",
 50 | 		"./src/plugins/oidc-provider/index.ts",
 51 | 		"./src/plugins/oauth-proxy/index.ts",
 52 | 		"./src/plugins/organization/index.ts",
 53 | 		"./src/plugins/organization/access/index.ts",
 54 | 		"./src/plugins/passkey/index.ts",
 55 | 		"./src/plugins/phone-number/index.ts",
 56 | 		"./src/plugins/two-factor/index.ts",
 57 | 		"./src/plugins/username/index.ts",
 58 | 		"./src/plugins/haveibeenpwned/index.ts",
 59 | 		"./src/plugins/one-time-token/index.ts",
 60 | 		"./src/plugins/siwe/index.ts",
 61 | 		"./src/test-utils/index.ts",
 62 | 	],
 63 | 	external: [
 64 | 		"prisma",
 65 | 		"@prisma/client",
 66 | 		"better-sqlite3",
 67 | 		"react",
 68 | 		"vue",
 69 | 		"solid-js",
 70 | 		"solid-js/store",
 71 | 		"next/headers",
 72 | 		"$app/environment",
 73 | 		"vitest",
 74 | 		"@vitest/runner",
 75 | 		"@vitest/utils",
 76 | 		"@vitest/expect",
 77 | 		"@vitest/snapshot",
 78 | 		"@vitest/spy",
 79 | 		"chai",
 80 | 		"mongodb",
 81 | 		"drizzle-orm",
 82 | 		"pathe",
 83 | 		"std-env",
 84 | 		"magic-string",
 85 | 		"pretty-format",
 86 | 		"p-limit",
 87 | 		"tinyspy",
 88 | 		"next/dist/compiled/@edge-runtime/cookies",
 89 | 		"@tanstack/react-start",
 90 | 		"@tanstack/start-server-core",
 91 | 		"bson",
 92 | 		"mongodb-connection-string-url",
 93 | 		"@mongodb-js/saslprep",
 94 | 		"kerberos",
 95 | 		"@mongodb-js/zstd",
 96 | 		"nanostores",
 97 | 		"@aws-sdk/credential-providers",
 98 | 		"mongodb-client-encryption",
 99 | 		"@vue/runtime-dom",
100 | 		"@vue/runtime-core",
101 | 		"@vue/shared",
102 | 		"@vue/reactivity",
103 | 		"@vue/compiler-dom",
104 | 		"@vue/compiler-core",
105 | 		"@babel/types",
106 | 		"@babel/parser",
107 | 		"punycode",
108 | 		"@sveltejs/kit",
109 | 		"svelte/compiler",
110 | 		"@sveltejs/vite-plugin-svelte",
111 | 		"csstype",
112 | 		"siwe",
113 | 		"@lynx-js/react",
114 | 	],
115 | 	treeshake: true,
116 | 	clean: true,
117 | });
118 | 
```

--------------------------------------------------------------------------------
/docs/components/anchor-scroll-fix.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { useEffect, useRef } from "react";
  4 | 
  5 | export function AnchorScroll() {
  6 | 	const scrollTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
  7 | 	const isScrollingRef = useRef(false);
  8 | 
  9 | 	useEffect(() => {
 10 | 		function calculateScrollOffset() {
 11 | 			const root = document.documentElement;
 12 | 			const navHeight = parseInt(
 13 | 				getComputedStyle(root).getPropertyValue("--fd-nav-height") || "56",
 14 | 			);
 15 | 			const bannerHeight = parseInt(
 16 | 				getComputedStyle(root).getPropertyValue("--fd-banner-height") || "0",
 17 | 			);
 18 | 			const tocnavHeight = parseInt(
 19 | 				getComputedStyle(root).getPropertyValue("--fd-tocnav-height") || "0",
 20 | 			);
 21 | 
 22 | 			return navHeight + bannerHeight + tocnavHeight + 24;
 23 | 		}
 24 | 
 25 | 		function smoothScrollToElement(element: HTMLElement) {
 26 | 			if (isScrollingRef.current) return;
 27 | 
 28 | 			isScrollingRef.current = true;
 29 | 			document.documentElement.setAttribute("data-anchor-scrolling", "true");
 30 | 
 31 | 			const elementRect = element.getBoundingClientRect();
 32 | 			const scrollOffset = calculateScrollOffset();
 33 | 			const targetPosition =
 34 | 				window.pageYOffset + elementRect.top - scrollOffset;
 35 | 
 36 | 			// Simple smooth scroll animation
 37 | 			const startPosition = window.pageYOffset;
 38 | 			const distance = targetPosition - startPosition;
 39 | 			const duration = Math.min(500, Math.abs(distance) * 0.3);
 40 | 			const startTime = performance.now();
 41 | 
 42 | 			function animateScroll(currentTime: number) {
 43 | 				const elapsed = currentTime - startTime;
 44 | 				const progress = Math.min(elapsed / duration, 1);
 45 | 				const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
 46 | 				const currentPosition =
 47 | 					startPosition + distance * easeOutCubic(progress);
 48 | 
 49 | 				window.scrollTo(0, currentPosition);
 50 | 
 51 | 				if (progress < 1) {
 52 | 					requestAnimationFrame(animateScroll);
 53 | 				} else {
 54 | 					document.documentElement.removeAttribute("data-anchor-scrolling");
 55 | 					isScrollingRef.current = false;
 56 | 				}
 57 | 			}
 58 | 
 59 | 			requestAnimationFrame(animateScroll);
 60 | 		}
 61 | 
 62 | 		function handleAnchorScroll() {
 63 | 			if (window.location.hash) {
 64 | 				const element = document.getElementById(window.location.hash.slice(1));
 65 | 				if (element) {
 66 | 					scrollTimeoutRef.current = setTimeout(
 67 | 						() => smoothScrollToElement(element),
 68 | 						100,
 69 | 					);
 70 | 				}
 71 | 			}
 72 | 		}
 73 | 
 74 | 		function handleHashChange() {
 75 | 			const element = document.getElementById(window.location.hash.slice(1));
 76 | 			if (element) smoothScrollToElement(element);
 77 | 		}
 78 | 
 79 | 		function handleAnchorClick(event: Event) {
 80 | 			const link = (event.target as HTMLElement).closest(
 81 | 				'a[href^="#"]',
 82 | 			) as HTMLAnchorElement;
 83 | 
 84 | 			if (link?.hash) {
 85 | 				event.preventDefault();
 86 | 				const element = document.getElementById(link.hash.slice(1));
 87 | 
 88 | 				if (element) {
 89 | 					history.pushState(null, "", link.hash);
 90 | 					smoothScrollToElement(element);
 91 | 				}
 92 | 			}
 93 | 		}
 94 | 
 95 | 		handleAnchorScroll();
 96 | 		window.addEventListener("hashchange", handleHashChange);
 97 | 		document.addEventListener("click", handleAnchorClick);
 98 | 
 99 | 		return () => {
100 | 			if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
101 | 			window.removeEventListener("hashchange", handleHashChange);
102 | 			document.removeEventListener("click", handleAnchorClick);
103 | 		};
104 | 	}, []);
105 | 
106 | 	return null;
107 | }
108 | 
```

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

```markdown
 1 | ---
 2 | title: TikTok
 3 | description: TikTok provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step>
 8 |         ### Get your TikTok Credentials
 9 |         To integrate with TikTok, you need to obtain API credentials by creating an application in the [TikTok Developer Portal](https://developers.tiktok.com/apps).
10 | 
11 |         Follow these steps:
12 |         1. Create an account on the TikTok Developer Portal
13 |         2. Create a new application
14 |         3. Set up a sandbox environment for testing
15 |         4. Configure your redirect URL (must be HTTPS)
16 |         5. Note your Client Secret and Client Key
17 | 
18 |         <Callout type="info">
19 |             - The TikTok API does not work with localhost. You need to use a public domain for the redirect URL and HTTPS for local testing. You can use [NGROK](https://ngrok.com/) or another similar tool for this.
20 |             - For testing, you will need to use the [Sandbox mode](https://developers.tiktok.com/blog/introducing-sandbox), which you can enable in the TikTok Developer Portal.
21 |             - The default scope is `user.info.profile`. For additional scopes, refer to the [Available Scopes](https://developers.tiktok.com/doc/tiktok-api-scopes/) documentation.
22 |         </Callout>
23 | 
24 |         Make sure to set the redirect URL to a valid HTTPS domain 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.
25 | 
26 |         <Callout type="info">
27 |             - The TikTok API does not provide email addresses. As a workaround, this implementation uses the user's `username` value for the `email` field, which is why it requires the `user.info.profile` scope instead of just `user.info.basic`.
28 |             - For production use, you will need to request approval from TikTok for the scopes you intend to use.
29 |         </Callout>
30 |     </Step>
31 | 
32 |   <Step>
33 |         ### Configure the provider
34 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
35 | 
36 |         ```ts title="auth.ts"
37 |         import { betterAuth } from "better-auth"
38 | 
39 |         export const auth = betterAuth({
40 |             socialProviders: {
41 |                 tiktok: { // [!code highlight]
42 |                     clientSecret: process.env.TIKTOK_CLIENT_SECRET as string, // [!code highlight]
43 |                     clientKey: process.env.TIKTOK_CLIENT_KEY as string, // [!code highlight]
44 |                 }, // [!code highlight]
45 |             },
46 |         })
47 |         ```
48 |     </Step>
49 |        <Step>
50 |         ### Sign In with TikTok
51 |         To sign in with TikTok, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
52 |         - `provider`: The provider to use. It should be set to `tiktok`.
53 | 
54 |         ```ts title="auth-client.ts"
55 |         import { createAuthClient } from "better-auth/client"
56 |         const authClient =  createAuthClient()
57 | 
58 |         const signIn = async () => {
59 |             const data = await authClient.signIn.social({
60 |                 provider: "tiktok"
61 |             })
62 |         }
63 |         ```
64 |     </Step>
65 | 
66 | </Steps>
67 | 
```

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

```typescript
  1 | "use client";
  2 | 
  3 | import { useState } from "react";
  4 | import { useRouter } from "next/navigation";
  5 | import {
  6 | 	Card,
  7 | 	CardContent,
  8 | 	CardDescription,
  9 | 	CardHeader,
 10 | 	CardTitle,
 11 | } from "@/components/ui/card";
 12 | import { Input } from "@/components/ui/input";
 13 | import { Label } from "@/components/ui/label";
 14 | import { Button } from "@/components/ui/button";
 15 | import { AlertCircle, Loader2 } from "lucide-react";
 16 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
 17 | import { client } from "@/lib/auth-client";
 18 | 
 19 | export default function RegisterOAuthClient() {
 20 | 	const [name, setName] = useState("");
 21 | 	const [logo, setLogo] = useState<File | null>(null);
 22 | 	const [redirectUri, setRedirectUri] = useState("");
 23 | 	const [isSubmitting, setIsSubmitting] = useState(false);
 24 | 	const [error, setError] = useState<string | null>(null);
 25 | 	const router = useRouter();
 26 | 
 27 | 	const handleSubmit = async (e: React.FormEvent) => {
 28 | 		e.preventDefault();
 29 | 		setIsSubmitting(true);
 30 | 		setError(null);
 31 | 
 32 | 		if (!name || !logo || !redirectUri) {
 33 | 			setError("All fields are required");
 34 | 			setIsSubmitting(false);
 35 | 			return;
 36 | 		}
 37 | 		const res = await client.oauth2.register({
 38 | 			client_name: name,
 39 | 			redirect_uris: [redirectUri],
 40 | 		});
 41 | 		setIsSubmitting(false);
 42 | 	};
 43 | 
 44 | 	return (
 45 | 		<div className="container mx-auto py-10">
 46 | 			<Card className="max-w-md mx-auto">
 47 | 				<CardHeader>
 48 | 					<CardTitle>Register OAuth Client</CardTitle>
 49 | 					<CardDescription>
 50 | 						Provide details to register a new OAuth client as a provider.
 51 | 					</CardDescription>
 52 | 				</CardHeader>
 53 | 				<CardContent>
 54 | 					<form onSubmit={handleSubmit} className="space-y-4">
 55 | 						<div className="space-y-2">
 56 | 							<Label htmlFor="name">Name</Label>
 57 | 							<Input
 58 | 								id="name"
 59 | 								value={name}
 60 | 								onChange={(e) => setName(e.target.value)}
 61 | 								placeholder="Enter client name"
 62 | 							/>
 63 | 						</div>
 64 | 						<div className="space-y-2">
 65 | 							<Label htmlFor="logo">Logo</Label>
 66 | 							<Input
 67 | 								id="logo"
 68 | 								type="file"
 69 | 								onChange={(e) => setLogo(e.target.files?.[0] || null)}
 70 | 								accept="image/*"
 71 | 							/>
 72 | 						</div>
 73 | 						<div className="space-y-2">
 74 | 							<Label htmlFor="redirectUri">Redirect URI</Label>
 75 | 							<Input
 76 | 								id="redirectUri"
 77 | 								value={redirectUri}
 78 | 								onChange={(e) => setRedirectUri(e.target.value)}
 79 | 								placeholder="https://your-app.com/callback"
 80 | 							/>
 81 | 						</div>
 82 | 						{error && (
 83 | 							<Alert variant="destructive">
 84 | 								<AlertCircle className="h-4 w-4" />
 85 | 								<AlertTitle>Error</AlertTitle>
 86 | 								<AlertDescription>{error}</AlertDescription>
 87 | 							</Alert>
 88 | 						)}
 89 | 						<Button type="submit" className="w-full" disabled={isSubmitting}>
 90 | 							{isSubmitting ? (
 91 | 								<>
 92 | 									<Loader2 className="mr-2 h-4 w-4 animate-spin" />
 93 | 									Registering...
 94 | 								</>
 95 | 							) : (
 96 | 								"Register Client"
 97 | 							)}
 98 | 						</Button>
 99 | 					</form>
100 | 				</CardContent>
101 | 			</Card>
102 | 		</div>
103 | 	);
104 | }
105 | 
106 | async function convertImageToBase64(file: File): Promise<string> {
107 | 	return new Promise((resolve, reject) => {
108 | 		const reader = new FileReader();
109 | 		reader.onloadend = () => resolve(reader.result as string);
110 | 		reader.onerror = reject;
111 | 		reader.readAsDataURL(file);
112 | 	});
113 | }
114 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/atlassian.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 AtlassianProfile {
  9 | 	account_type?: string;
 10 | 	account_id: string;
 11 | 	email?: string;
 12 | 	name: string;
 13 | 	picture?: string;
 14 | 	nickname?: string;
 15 | 	locale?: string;
 16 | 	extended_profile?: {
 17 | 		job_title?: string;
 18 | 		organization?: string;
 19 | 		department?: string;
 20 | 		location?: string;
 21 | 	};
 22 | }
 23 | export interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
 24 | 	clientId: string;
 25 | }
 26 | 
 27 | export const atlassian = (options: AtlassianOptions) => {
 28 | 	return {
 29 | 		id: "atlassian",
 30 | 		name: "Atlassian",
 31 | 
 32 | 		async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
 33 | 			if (!options.clientId || !options.clientSecret) {
 34 | 				logger.error("Client Id and Secret are required for Atlassian");
 35 | 				throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
 36 | 			}
 37 | 			if (!codeVerifier) {
 38 | 				throw new BetterAuthError("codeVerifier is required for Atlassian");
 39 | 			}
 40 | 
 41 | 			const _scopes = options.disableDefaultScope
 42 | 				? []
 43 | 				: ["read:jira-user", "offline_access"];
 44 | 			options.scope && _scopes.push(...options.scope);
 45 | 			scopes && _scopes.push(...scopes);
 46 | 
 47 | 			return createAuthorizationURL({
 48 | 				id: "atlassian",
 49 | 				options,
 50 | 				authorizationEndpoint: "https://auth.atlassian.com/authorize",
 51 | 				scopes: _scopes,
 52 | 				state,
 53 | 				codeVerifier,
 54 | 				redirectURI,
 55 | 				additionalParams: {
 56 | 					audience: "api.atlassian.com",
 57 | 				},
 58 | 				prompt: options.prompt,
 59 | 			});
 60 | 		},
 61 | 
 62 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 63 | 			return validateAuthorizationCode({
 64 | 				code,
 65 | 				codeVerifier,
 66 | 				redirectURI,
 67 | 				options,
 68 | 				tokenEndpoint: "https://auth.atlassian.com/oauth/token",
 69 | 			});
 70 | 		},
 71 | 
 72 | 		refreshAccessToken: options.refreshAccessToken
 73 | 			? options.refreshAccessToken
 74 | 			: async (refreshToken) => {
 75 | 					return refreshAccessToken({
 76 | 						refreshToken,
 77 | 						options: {
 78 | 							clientId: options.clientId,
 79 | 							clientSecret: options.clientSecret,
 80 | 						},
 81 | 						tokenEndpoint: "https://auth.atlassian.com/oauth/token",
 82 | 					});
 83 | 				},
 84 | 
 85 | 		async getUserInfo(token) {
 86 | 			if (options.getUserInfo) {
 87 | 				return options.getUserInfo(token);
 88 | 			}
 89 | 
 90 | 			if (!token.accessToken) {
 91 | 				return null;
 92 | 			}
 93 | 
 94 | 			try {
 95 | 				const { data: profile } = await betterFetch<{
 96 | 					account_id: string;
 97 | 					name: string;
 98 | 					email?: string;
 99 | 					picture?: string;
100 | 				}>("https://api.atlassian.com/me", {
101 | 					headers: { Authorization: `Bearer ${token.accessToken}` },
102 | 				});
103 | 
104 | 				if (!profile) return null;
105 | 
106 | 				const userMap = await options.mapProfileToUser?.(profile);
107 | 
108 | 				return {
109 | 					user: {
110 | 						id: profile.account_id,
111 | 						name: profile.name,
112 | 						email: profile.email,
113 | 						image: profile.picture,
114 | 						emailVerified: false,
115 | 						...userMap,
116 | 					},
117 | 					data: profile,
118 | 				};
119 | 			} catch (error) {
120 | 				logger.error("Failed to fetch user info from Figma:", error);
121 | 				return null;
122 | 			}
123 | 		},
124 | 
125 | 		options,
126 | 	} satisfies OAuthProvider<AtlassianProfile>;
127 | };
128 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/concepts/cookies.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Cookies
 3 | description: Learn how cookies are used in Better Auth.
 4 | ---
 5 | 
 6 | Cookies are used to store data such as session tokens, OAuth state, and more. All cookies are signed using the `secret` key provided in the auth options.
 7 | 
 8 | ### Cookie Prefix
 9 | 
10 | By default, Better Auth cookies follow the format `${prefix}.${cookie_name}`. The default prefix is "better-auth". You can change the prefix by setting `cookiePrefix` in the `advanced` object of the auth options.
11 | 
12 | ```ts title="auth.ts"
13 | import { betterAuth } from "better-auth"
14 | 
15 | export const auth = betterAuth({
16 |     advanced: {
17 |         cookiePrefix: "my-app"
18 |     }
19 | })
20 | ```
21 | 
22 | ### Custom Cookies
23 | 
24 | All cookies are `httpOnly` and `secure` when the server is running in production mode.
25 | 
26 | If you want to set custom cookie names and attributes, you can do so by setting `cookieOptions` in the `advanced` object of the auth options.
27 | 
28 | By default, Better Auth uses the following cookies:
29 | 
30 | - `session_token` to store the session token
31 | - `session_data` to store the session data if cookie cache is enabled
32 | - `dont_remember` to store the flag when `rememberMe` is disabled
33 | 
34 | Plugins may also use cookies to store data. For example, the Two Factor Authentication plugin uses the `two_factor` cookie to store the two-factor authentication state.
35 | 
36 | ```ts title="auth.ts"
37 | import { betterAuth } from "better-auth"
38 | 
39 | export const auth = betterAuth({
40 |     advanced: {
41 |         cookies: {
42 |             session_token: {
43 |                 name: "custom_session_token",
44 |                 attributes: {
45 |                     // Set custom cookie attributes
46 |                 }
47 |             },
48 |         }
49 |     }
50 | })
51 | ```
52 | 
53 | ### Cross Subdomain Cookies
54 | 
55 | Sometimes you may need to share cookies across subdomains. 
56 | For example, if you authenticate on `auth.example.com`, you may also want to access the same session on `app.example.com`.
57 | 
58 | <Callout type="warn">
59 | The `domain` attribute controls which domains can access the cookie. Setting it to your root domain (e.g. `example.com`) makes the cookie accessible across all subdomains. For security, follow these guidelines:
60 | 
61 | 1. Only enable cross-subdomain cookies if it's necessary
62 | 2. Set the domain to the most specific scope needed (e.g. `app.example.com` instead of `.example.com`)
63 | 3. Be cautious of untrusted subdomains that could potentially access these cookies
64 | 4. Consider using separate domains for untrusted services (e.g. `status.company.com` vs `app.company.com`)
65 | </Callout>
66 | 
67 | ```ts title="auth.ts"
68 | import { betterAuth } from "better-auth"
69 | 
70 | export const auth = betterAuth({
71 |     advanced: {
72 |         crossSubDomainCookies: {
73 |             enabled: true,
74 |             domain: "app.example.com", // your domain
75 |         },
76 |     },
77 |     trustedOrigins: [
78 |         'https://example.com',
79 |         'https://app1.example.com',
80 |         'https://app2.example.com',
81 |     ],
82 | })
83 | ```
84 | ### Secure Cookies
85 | 
86 | By default, cookies are secure only when the server is running in production mode. You can force cookies to be always secure by setting `useSecureCookies` to `true` in the `advanced` object in the auth options.
87 | 
88 | ```ts title="auth.ts"
89 | import { betterAuth } from "better-auth"
90 | 
91 | export const auth = betterAuth({
92 |     advanced: {
93 |         useSecureCookies: true
94 |     }
95 | })
96 | ```
```
Page 10/69FirstPrevNextLast