#
tokens: 43047/50000 4/1094 files (page 39/67)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 39 of 67. 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
│       │   │   │   │   └── 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-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
│   ├── 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

--------------------------------------------------------------------------------
/docs/app/sitemap.xml:
--------------------------------------------------------------------------------

```
  1 | <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
  2 | <url>
  3 | <loc>https://www.better-auth.com/</loc>
  4 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
  5 | <priority>1.00</priority>
  6 | </url>
  7 | <url>
  8 | <loc>https://www.better-auth.com/docs/introduction</loc>
  9 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 10 | <priority>0.80</priority>
 11 | </url>
 12 | <url>
 13 | <loc>https://www.better-auth.com/changelogs</loc>
 14 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 15 | <priority>0.80</priority>
 16 | </url>
 17 | <url>
 18 | <loc>https://www.better-auth.com/blog</loc>
 19 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 20 | <priority>0.80</priority>
 21 | </url>
 22 | <url>
 23 | <loc>https://www.better-auth.com/community</loc>
 24 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 25 | <priority>0.80</priority>
 26 | </url>
 27 | <url>
 28 | <loc>https://www.better-auth.com/docs/examples/next-js</loc>
 29 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 30 | <priority>0.80</priority>
 31 | </url>
 32 | <url>
 33 | <loc>https://www.better-auth.com/docs/comparison</loc>
 34 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 35 | <priority>0.64</priority>
 36 | </url>
 37 | <url>
 38 | <loc>https://www.better-auth.com/docs/installation</loc>
 39 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 40 | <priority>0.64</priority>
 41 | </url>
 42 | <url>
 43 | <loc>https://www.better-auth.com/docs/basic-usage</loc>
 44 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 45 | <priority>0.64</priority>
 46 | </url>
 47 | <url>
 48 | <loc>https://www.better-auth.com/blog/1-3</loc>
 49 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 50 | <priority>0.64</priority>
 51 | </url>
 52 | <url>
 53 | <loc>https://www.better-auth.com/blog/seed-round</loc>
 54 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 55 | <priority>0.64</priority>
 56 | </url>
 57 | <url>
 58 | <loc>https://www.better-auth.com/docs/adapters/other-relational-databases</loc>
 59 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 60 | <priority>0.51</priority>
 61 | </url>
 62 | <url>
 63 | <loc>https://www.better-auth.com/docs/concepts/cli</loc>
 64 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 65 | <priority>0.51</priority>
 66 | </url>
 67 | <url>
 68 | <loc>https://www.better-auth.com/docs/concepts/database</loc>
 69 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 70 | <priority>0.51</priority>
 71 | </url>
 72 | <url>
 73 | <loc>https://www.better-auth.com/docs/plugins/passkey</loc>
 74 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 75 | <priority>0.51</priority>
 76 | </url>
 77 | <url>
 78 | <loc>https://www.better-auth.com/docs/plugins/username</loc>
 79 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 80 | <priority>0.51</priority>
 81 | </url>
 82 | <url>
 83 | <loc>https://www.better-auth.com/docs/plugins/magic-link</loc>
 84 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 85 | <priority>0.51</priority>
 86 | </url>
 87 | <url>
 88 | <loc>https://www.better-auth.com/docs/plugins/email-otp</loc>
 89 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 90 | <priority>0.51</priority>
 91 | </url>
 92 | <url>
 93 | <loc>https://www.better-auth.com/docs/integrations/next</loc>
 94 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
 95 | <priority>0.51</priority>
 96 | </url>
 97 | <url>
 98 | <loc>https://www.better-auth.com/docs/concepts/session-management</loc>
 99 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
100 | <priority>0.51</priority>
101 | </url>
102 | <url>
103 | <loc>https://www.better-auth.com/docs/plugins/2fa</loc>
104 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
105 | <priority>0.51</priority>
106 | </url>
107 | <url>
108 | <loc>https://www.better-auth.com/docs/concepts/api</loc>
109 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
110 | <priority>0.51</priority>
111 | </url>
112 | <url>
113 | <loc>https://www.better-auth.com/docs/plugins/sso</loc>
114 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
115 | <priority>0.51</priority>
116 | </url>
117 | <url>
118 | <loc>https://www.better-auth.com/docs/plugins/oidc-provider</loc>
119 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
120 | <priority>0.51</priority>
121 | </url>
122 | <url>
123 | <loc>https://www.better-auth.com/docs/plugins/mcp</loc>
124 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
125 | <priority>0.51</priority>
126 | </url>
127 | <url>
128 | <loc>https://www.better-auth.com/docs/plugins/stripe</loc>
129 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
130 | <priority>0.51</priority>
131 | </url>
132 | <url>
133 | <loc>https://www.better-auth.com/docs/plugins/siwe</loc>
134 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
135 | <priority>0.51</priority>
136 | </url>
137 | <url>
138 | <loc>https://www.better-auth.com/docs/adapters/mysql</loc>
139 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
140 | <priority>0.41</priority>
141 | </url>
142 | <url>
143 | <loc>https://www.better-auth.com/docs/adapters/sqlite</loc>
144 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
145 | <priority>0.41</priority>
146 | </url>
147 | <url>
148 | <loc>https://www.better-auth.com/docs/adapters/postgresql</loc>
149 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
150 | <priority>0.41</priority>
151 | </url>
152 | <url>
153 | <loc>https://www.better-auth.com/docs/adapters/mssql</loc>
154 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
155 | <priority>0.41</priority>
156 | </url>
157 | <url>
158 | <loc>https://www.better-auth.com/docs/adapters/drizzle</loc>
159 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
160 | <priority>0.41</priority>
161 | </url>
162 | <url>
163 | <loc>https://www.better-auth.com/docs/concepts/client</loc>
164 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
165 | <priority>0.41</priority>
166 | </url>
167 | <url>
168 | <loc>https://www.better-auth.com/docs/concepts/typescript</loc>
169 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
170 | <priority>0.41</priority>
171 | </url>
172 | <url>
173 | <loc>https://www.better-auth.com/docs/concepts/hooks</loc>
174 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
175 | <priority>0.41</priority>
176 | </url>
177 | <url>
178 | <loc>https://www.better-auth.com/docs/concepts/cookies</loc>
179 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
180 | <priority>0.41</priority>
181 | </url>
182 | <url>
183 | <loc>https://www.better-auth.com/docs/concepts/email</loc>
184 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
185 | <priority>0.41</priority>
186 | </url>
187 | <url>
188 | <loc>https://www.better-auth.com/docs/plugins/generic-oauth</loc>
189 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
190 | <priority>0.41</priority>
191 | </url>
192 | <url>
193 | <loc>https://www.better-auth.com/docs/plugins/anonymous</loc>
194 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
195 | <priority>0.41</priority>
196 | </url>
197 | <url>
198 | <loc>https://www.better-auth.com/docs/plugins/phone-number</loc>
199 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
200 | <priority>0.41</priority>
201 | </url>
202 | <url>
203 | <loc>https://www.better-auth.com/docs/integrations/remix</loc>
204 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
205 | <priority>0.41</priority>
206 | </url>
207 | <url>
208 | <loc>https://www.better-auth.com/docs/integrations/nuxt</loc>
209 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
210 | <priority>0.41</priority>
211 | </url>
212 | <url>
213 | <loc>https://www.better-auth.com/docs/concepts/rate-limit</loc>
214 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
215 | <priority>0.41</priority>
216 | </url>
217 | <url>
218 | <loc>https://www.better-auth.com/docs/integrations/expo</loc>
219 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
220 | <priority>0.41</priority>
221 | </url>
222 | <url>
223 | <loc>https://www.better-auth.com/docs/plugins/bearer</loc>
224 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
225 | <priority>0.41</priority>
226 | </url>
227 | <url>
228 | <loc>https://www.better-auth.com/docs/plugins/organization</loc>
229 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
230 | <priority>0.41</priority>
231 | </url>
232 | <url>
233 | <loc>https://www.better-auth.com/docs/plugins/api-key</loc>
234 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
235 | <priority>0.41</priority>
236 | </url>
237 | <url>
238 | <loc>https://www.better-auth.com/docs/plugins/jwt</loc>
239 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
240 | <priority>0.41</priority>
241 | </url>
242 | <url>
243 | <loc>https://www.better-auth.com/docs/plugins/polar</loc>
244 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
245 | <priority>0.41</priority>
246 | </url>
247 | <url>
248 | <loc>https://www.better-auth.com/docs/plugins/one-tap</loc>
249 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
250 | <priority>0.41</priority>
251 | </url>
252 | <url>
253 | <loc>https://www.better-auth.com/docs/plugins/admin</loc>
254 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
255 | <priority>0.41</priority>
256 | </url>
257 | <url>
258 | <loc>https://www.better-auth.com/docs/guides/optimizing-for-performance</loc>
259 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
260 | <priority>0.33</priority>
261 | </url>
262 | <url>
263 | <loc>https://www.better-auth.com/docs/authentication/other-social-providers</loc>
264 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
265 | <priority>0.33</priority>
266 | </url>
267 | <url>
268 | <loc>https://www.better-auth.com/docs/adapters/prisma</loc>
269 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
270 | <priority>0.33</priority>
271 | </url>
272 | <url>
273 | <loc>https://www.better-auth.com/docs/concepts/users-accounts</loc>
274 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
275 | <priority>0.33</priority>
276 | </url>
277 | <url>
278 | <loc>https://www.better-auth.com/docs/concepts/plugins</loc>
279 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
280 | <priority>0.33</priority>
281 | </url>
282 | <url>
283 | <loc>https://www.better-auth.com/docs/authentication/email-password</loc>
284 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
285 | <priority>0.33</priority>
286 | </url>
287 | <url>
288 | <loc>https://www.better-auth.com/docs/integrations/astro</loc>
289 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
290 | <priority>0.33</priority>
291 | </url>
292 | <url>
293 | <loc>https://www.better-auth.com/docs/integrations/svelte-kit</loc>
294 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
295 | <priority>0.33</priority>
296 | </url>
297 | <url>
298 | <loc>https://www.better-auth.com/docs/concepts/oauth</loc>
299 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
300 | <priority>0.33</priority>
301 | </url>
302 | <url>
303 | <loc>https://www.better-auth.com/docs/integrations/nestjs</loc>
304 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
305 | <priority>0.33</priority>
306 | </url>
307 | <url>
308 | <loc>https://www.better-auth.com/docs/plugins/captcha</loc>
309 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
310 | <priority>0.33</priority>
311 | </url>
312 | <url>
313 | <loc>https://www.better-auth.com/docs/plugins/open-api</loc>
314 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
315 | <priority>0.33</priority>
316 | </url>
317 | <url>
318 | <loc>https://www.better-auth.com/docs/plugins/autumn</loc>
319 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
320 | <priority>0.33</priority>
321 | </url>
322 | <url>
323 | <loc>https://www.better-auth.com/docs/guides/browser-extension-guide</loc>
324 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
325 | <priority>0.26</priority>
326 | </url>
327 | <url>
328 | <loc>https://www.better-auth.com/docs/reference/options</loc>
329 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
330 | <priority>0.26</priority>
331 | </url>
332 | <url>
333 | <loc>https://www.better-auth.com/docs/authentication/zoom</loc>
334 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
335 | <priority>0.26</priority>
336 | </url>
337 | <url>
338 | <loc>https://www.better-auth.com/docs/adapters/mongo</loc>
339 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
340 | <priority>0.26</priority>
341 | </url>
342 | <url>
343 | <loc>https://www.better-auth.com/docs/authentication/apple</loc>
344 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
345 | <priority>0.26</priority>
346 | </url>
347 | <url>
348 | <loc>https://www.better-auth.com/docs/adapters/community-adapters</loc>
349 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
350 | <priority>0.26</priority>
351 | </url>
352 | <url>
353 | <loc>https://www.better-auth.com/docs/integrations/solid-start</loc>
354 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
355 | <priority>0.26</priority>
356 | </url>
357 | <url>
358 | <loc>https://www.better-auth.com/docs/integrations/nitro</loc>
359 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
360 | <priority>0.26</priority>
361 | </url>
362 | <url>
363 | <loc>https://www.better-auth.com/docs/plugins/have-i-been-pwned</loc>
364 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
365 | <priority>0.26</priority>
366 | </url>
367 | <url>
368 | <loc>https://www.better-auth.com/docs/plugins/one-time-token</loc>
369 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
370 | <priority>0.26</priority>
371 | </url>
372 | <url>
373 | <loc>https://www.better-auth.com/docs/plugins/dodopayments</loc>
374 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
375 | <priority>0.26</priority>
376 | </url>
377 | <url>
378 | <loc>https://www.better-auth.com/docs/integrations/hono</loc>
379 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
380 | <priority>0.21</priority>
381 | </url>
382 | <url>
383 | <loc>https://www.better-auth.com/docs/guides/create-a-db-adapter</loc>
384 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
385 | <priority>0.21</priority>
386 | </url>
387 | <url>
388 | <loc>https://www.better-auth.com/docs/reference/contributing</loc>
389 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
390 | <priority>0.21</priority>
391 | </url>
392 | <url>
393 | <loc>https://www.better-auth.com/docs/authentication/vk</loc>
394 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
395 | <priority>0.21</priority>
396 | </url>
397 | <url>
398 | <loc>https://www.better-auth.com/docs/authentication/discord</loc>
399 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
400 | <priority>0.21</priority>
401 | </url>
402 | <url>
403 | <loc>https://www.better-auth.com/docs/integrations/tanstack</loc>
404 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
405 | <priority>0.21</priority>
406 | </url>
407 | <url>
408 | <loc>https://www.better-auth.com/docs/integrations/elysia</loc>
409 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
410 | <priority>0.21</priority>
411 | </url>
412 | <url>
413 | <loc>https://www.better-auth.com/docs/plugins/multi-session</loc>
414 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
415 | <priority>0.21</priority>
416 | </url>
417 | <url>
418 | <loc>https://www.better-auth.com/docs/plugins/oauth-proxy</loc>
419 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
420 | <priority>0.21</priority>
421 | </url>
422 | <url>
423 | <loc>https://www.better-auth.com/docs/plugins/dub</loc>
424 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
425 | <priority>0.21</priority>
426 | </url>
427 | <url>
428 | <loc>https://www.better-auth.com/docs/integrations/fastify</loc>
429 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
430 | <priority>0.17</priority>
431 | </url>
432 | <url>
433 | <loc>https://www.better-auth.com/docs/guides/your-first-plugin</loc>
434 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
435 | <priority>0.17</priority>
436 | </url>
437 | <url>
438 | <loc>https://www.better-auth.com/docs/reference/resources</loc>
439 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
440 | <priority>0.17</priority>
441 | </url>
442 | <url>
443 | <loc>https://www.better-auth.com/docs/authentication/spotify</loc>
444 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
445 | <priority>0.17</priority>
446 | </url>
447 | <url>
448 | <loc>https://www.better-auth.com/docs/authentication/facebook</loc>
449 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
450 | <priority>0.17</priority>
451 | </url>
452 | <url>
453 | <loc>https://www.better-auth.com/docs/integrations/express</loc>
454 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
455 | <priority>0.17</priority>
456 | </url>
457 | <url>
458 | <loc>https://www.better-auth.com/docs/plugins/community-plugins</loc>
459 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
460 | <priority>0.17</priority>
461 | </url>
462 | <url>
463 | <loc>https://www.better-auth.com/docs/guides/clerk-migration-guide</loc>
464 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
465 | <priority>0.13</priority>
466 | </url>
467 | <url>
468 | <loc>https://www.better-auth.com/docs/reference/security</loc>
469 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
470 | <priority>0.13</priority>
471 | </url>
472 | <url>
473 | <loc>https://www.better-auth.com/docs/authentication/roblox</loc>
474 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
475 | <priority>0.13</priority>
476 | </url>
477 | <url>
478 | <loc>https://www.better-auth.com/docs/authentication/github</loc>
479 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
480 | <priority>0.13</priority>
481 | </url>
482 | <url>
483 | <loc>https://www.better-auth.com/docs/guides/next-auth-migration-guide</loc>
484 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
485 | <priority>0.13</priority>
486 | </url>
487 | <url>
488 | <loc>https://www.better-auth.com/docs/guides/supabase-migration-guide</loc>
489 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
490 | <priority>0.11</priority>
491 | </url>
492 | <url>
493 | <loc>https://www.better-auth.com/docs/reference/faq</loc>
494 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
495 | <priority>0.11</priority>
496 | </url>
497 | <url>
498 | <loc>https://www.better-auth.com/docs/authentication/reddit</loc>
499 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
500 | <priority>0.11</priority>
501 | </url>
502 | <url>
503 | <loc>https://www.better-auth.com/docs/authentication/google</loc>
504 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
505 | <priority>0.11</priority>
506 | </url>
507 | <url>
508 | <loc>https://www.better-auth.com/docs/authentication/gitlab</loc>
509 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
510 | <priority>0.09</priority>
511 | </url>
512 | <url>
513 | <loc>https://www.better-auth.com/docs/authentication/huggingface</loc>
514 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
515 | <priority>0.09</priority>
516 | </url>
517 | <url>
518 | <loc>https://www.better-auth.com/docs/authentication/linkedin</loc>
519 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
520 | <priority>0.07</priority>
521 | </url>
522 | <url>
523 | <loc>https://www.better-auth.com/docs/authentication/kick</loc>
524 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
525 | <priority>0.07</priority>
526 | </url>
527 | <url>
528 | <loc>https://www.better-auth.com/docs/authentication/linear</loc>
529 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
530 | <priority>0.05</priority>
531 | </url>
532 | <url>
533 | <loc>https://www.better-auth.com/docs/authentication/microsoft</loc>
534 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
535 | <priority>0.05</priority>
536 | </url>
537 | <url>
538 | <loc>https://www.better-auth.com/docs/authentication/dropbox</loc>
539 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
540 | <priority>0.04</priority>
541 | </url>
542 | <url>
543 | <loc>https://www.better-auth.com/docs/authentication/slack</loc>
544 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
545 | <priority>0.04</priority>
546 | </url>
547 | <url>
548 | <loc>https://www.better-auth.com/docs/authentication/twitter</loc>
549 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
550 | <priority>0.04</priority>
551 | </url>
552 | <url>
553 | <loc>https://www.better-auth.com/docs/authentication/notion</loc>
554 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
555 | <priority>0.04</priority>
556 | </url>
557 | <url>
558 | <loc>https://www.better-auth.com/docs/authentication/twitch</loc>
559 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
560 | <priority>0.03</priority>
561 | </url>
562 | <url>
563 | <loc>https://www.better-auth.com/docs/authentication/tiktok</loc>
564 | <lastmod>2025-07-31T12:33:38+00:00</lastmod>
565 | <priority>0.03</priority>
566 | </url>
567 | </urlset>
```

--------------------------------------------------------------------------------
/docs/content/docs/reference/options.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Options
  3 | description: Better Auth configuration options reference.
  4 | ---
  5 | 
  6 | List of all the available options for configuring Better Auth. See [Better Auth Options](https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/types/options.ts#L13).
  7 | 
  8 | ## `appName`
  9 | 
 10 | The name of the application.
 11 | 
 12 | ```ts
 13 | import { betterAuth } from "better-auth";
 14 | export const auth = betterAuth({
 15 | 	appName: "My App",
 16 | })
 17 | ```
 18 | 
 19 | ## `baseURL`
 20 | 
 21 | Base URL for Better Auth. This is typically the root URL where your application server is hosted. Note: If you include a path in the baseURL, it will take precedence over the default path.
 22 | 
 23 | ```ts
 24 | import { betterAuth } from "better-auth";
 25 | export const auth = betterAuth({
 26 | 	baseURL: "https://example.com",
 27 | })
 28 | ```
 29 | 
 30 | If not explicitly set, the system will check for the environment variable `process.env.BETTER_AUTH_URL`
 31 | 
 32 | ## `basePath`
 33 | 
 34 | Base path for Better Auth. This is typically the path where the Better Auth routes are mounted. It will be overridden if there is a path component within `baseURL`.
 35 | 
 36 | ```ts
 37 | import { betterAuth } from "better-auth";
 38 | export const auth = betterAuth({
 39 | 	basePath: "/api/auth",
 40 | })
 41 | ```
 42 | 
 43 | Default: `/api/auth`
 44 | 
 45 | ## `trustedOrigins`
 46 | 
 47 | List of trusted origins. You can provide a static array of origins, a function that returns origins dynamically, or use wildcard patterns to match multiple domains.
 48 | 
 49 | ### Static Origins
 50 | 
 51 | You can provide a static array of origins:
 52 | 
 53 | ```ts
 54 | import { betterAuth } from "better-auth";
 55 | export const auth = betterAuth({
 56 | 	trustedOrigins: ["http://localhost:3000", "https://example.com"],
 57 | })
 58 | ```
 59 | 
 60 | ### Dynamic Origins
 61 | 
 62 | You can provide a function that returns origins dynamically:
 63 | 
 64 | ```ts
 65 | export const auth = betterAuth({
 66 | 	trustedOrigins: async (request: Request) => {
 67 | 		// Return an array of trusted origins based on the request
 68 | 		return ["https://dynamic-origin.com"];
 69 | 	}
 70 | })
 71 | ```
 72 | 
 73 | ### Wildcard Support
 74 | 
 75 | You can use wildcard patterns in trusted origins:
 76 | 
 77 | ```ts
 78 | export const auth = betterAuth({
 79 | 	trustedOrigins: [
 80 | 		"*.example.com",             // Trust all subdomains of example.com
 81 | 		"https://*.example.com",     // Trust only HTTPS subdomains
 82 | 		"http://*.dev.example.com"   // Trust HTTP subdomains of dev.example.com
 83 | 	]
 84 | })
 85 | ```
 86 | 
 87 | ## `secret`
 88 | 
 89 | The secret used for encryption, signing, and hashing.
 90 | 
 91 | ```ts
 92 | import { betterAuth } from "better-auth";
 93 | export const auth = betterAuth({
 94 | 	secret: "your-secret-key",
 95 | })
 96 | ```
 97 | 
 98 | By default, Better Auth will look for the following environment variables:
 99 | - `process.env.BETTER_AUTH_SECRET`
100 | - `process.env.AUTH_SECRET`
101 | 
102 | If none of these environment variables are set, it will default to `"better-auth-secret-123456789"`. In production, if it's not set, it will throw an error.
103 | 
104 | You can generate a good secret using the following command:
105 | 
106 | ```bash
107 | openssl rand -base64 32
108 | ```
109 | 
110 | ## `database`
111 | 
112 | Database configuration for Better Auth.
113 | 
114 | ```ts
115 | import { betterAuth } from "better-auth";
116 | export const auth = betterAuth({
117 | 	database: {
118 | 		dialect: "postgres",
119 | 		type: "postgres",
120 | 		casing: "camel"
121 | 	},
122 | })
123 | ```
124 | 
125 | Better Auth supports various database configurations including [PostgreSQL](/docs/adapters/postgresql), [MySQL](/docs/adapters/mysql), and [SQLite](/docs/adapters/sqlite).
126 | 
127 | Read more about databases [here](/docs/concepts/database).
128 | 
129 | ## `secondaryStorage`
130 | 
131 | Secondary storage configuration used to store session and rate limit data.
132 | 
133 | ```ts
134 | import { betterAuth } from "better-auth";
135 | 
136 | export const auth = betterAuth({
137 | 	// ... other options
138 |     secondaryStorage: {
139 |     	// Your implementation here
140 |     },
141 | })
142 | ```
143 | 
144 | Read more about secondary storage [here](/docs/concepts/database#secondary-storage).
145 | 
146 | ## `emailVerification`
147 | 
148 | Email verification configuration.
149 | 
150 | ```ts
151 | import { betterAuth } from "better-auth";
152 | export const auth = betterAuth({
153 | 	emailVerification: {
154 | 		sendVerificationEmail: async ({ user, url, token }) => {
155 | 			// Send verification email to user
156 | 		},
157 | 		sendOnSignUp: true,
158 | 		autoSignInAfterVerification: true,
159 | 		expiresIn: 3600 // 1 hour
160 | 	},
161 | })
162 | ```
163 | 
164 | - `sendVerificationEmail`: Function to send verification email
165 | - `sendOnSignUp`: Send verification email automatically after sign up (default: `false`)
166 | - `sendOnSignIn`: Send verification email automatically on sign in when the user's email is not verified (default: `false`)
167 | - `autoSignInAfterVerification`: Auto sign in the user after they verify their email
168 | - `expiresIn`: Number of seconds the verification token is valid for (default: `3600` seconds)
169 | 
170 | ## `emailAndPassword`
171 | 
172 | Email and password authentication configuration.
173 | 
174 | ```ts
175 | import { betterAuth } from "better-auth";
176 | export const auth = betterAuth({
177 | 	emailAndPassword: {
178 | 		enabled: true,
179 | 		disableSignUp: false,
180 | 		requireEmailVerification: true,
181 | 		minPasswordLength: 8,
182 | 		maxPasswordLength: 128,
183 | 		autoSignIn: true,
184 | 		sendResetPassword: async ({ user, url, token }) => {
185 | 			// Send reset password email
186 | 		},
187 | 		resetPasswordTokenExpiresIn: 3600, // 1 hour
188 | 		password: {
189 | 			hash: async (password) => {
190 | 				// Custom password hashing
191 | 				return hashedPassword;
192 | 			},
193 | 			verify: async ({ hash, password }) => {
194 | 				// Custom password verification
195 | 				return isValid;
196 | 			}
197 | 		}
198 | 	},
199 | })
200 | ```
201 | 
202 | - `enabled`: Enable email and password authentication (default: `false`)
203 | - `disableSignUp`: Disable email and password sign up (default: `false`)
204 | - `requireEmailVerification`: Require email verification before a session can be created
205 | - `minPasswordLength`: Minimum password length (default: `8`)
206 | - `maxPasswordLength`: Maximum password length (default: `128`)
207 | - `autoSignIn`: Automatically sign in the user after sign up
208 | - `sendResetPassword`: Function to send reset password email
209 | - `resetPasswordTokenExpiresIn`: Number of seconds the reset password token is valid for (default: `3600` seconds)
210 | - `password`: Custom password hashing and verification functions
211 | 
212 | ## `socialProviders`
213 | 
214 | Configure social login providers.
215 | 
216 | ```ts
217 | import { betterAuth } from "better-auth";
218 | export const auth = betterAuth({
219 | 	socialProviders: {
220 | 		google: {
221 | 			clientId: "your-client-id",
222 | 			clientSecret: "your-client-secret",
223 | 			redirectURI: "https://example.com/api/auth/callback/google"
224 | 		},
225 | 		github: {
226 | 			clientId: "your-client-id",
227 | 			clientSecret: "your-client-secret",
228 | 			redirectURI: "https://example.com/api/auth/callback/github"
229 | 		}
230 | 	},
231 | })
232 | ```
233 | 
234 | ## `plugins`
235 | 
236 | List of Better Auth plugins.
237 | 
238 | ```ts
239 | import { betterAuth } from "better-auth";
240 | import { emailOTP } from "better-auth/plugins";
241 | 
242 | export const auth = betterAuth({
243 | 	plugins: [
244 | 		emailOTP({
245 | 			sendVerificationOTP: async ({ email, otp, type }) => {
246 | 				// Send OTP to user's email
247 | 			}
248 | 		})
249 | 	],
250 | })
251 | ```
252 | 
253 | ## `user`
254 | 
255 | User configuration options.
256 | 
257 | ```ts
258 | import { betterAuth } from "better-auth";
259 | export const auth = betterAuth({
260 | 	user: {
261 | 		modelName: "users",
262 | 		fields: {
263 | 			email: "emailAddress",
264 | 			name: "fullName"
265 | 		},
266 | 		additionalFields: {
267 | 			customField: {
268 | 				type: "string",
269 | 			}
270 | 		},
271 | 		changeEmail: {
272 | 			enabled: true,
273 | 			sendChangeEmailVerification: async ({ user, newEmail, url, token }) => {
274 | 				// Send change email verification
275 | 			}
276 | 		},
277 | 		deleteUser: {
278 | 			enabled: true,
279 | 			sendDeleteAccountVerification: async ({ user, url, token }) => {
280 | 				// Send delete account verification
281 | 			},
282 | 			beforeDelete: async (user) => {
283 | 				// Perform actions before user deletion
284 | 			},
285 | 			afterDelete: async (user) => {
286 | 				// Perform cleanup after user deletion
287 | 			}
288 | 		}
289 | 	},
290 | })
291 | ```
292 | 
293 | - `modelName`: The model name for the user (default: `"user"`)
294 | - `fields`: Map fields to different column names
295 | - `additionalFields`: Additional fields for the user table
296 | - `changeEmail`: Configuration for changing email
297 | - `deleteUser`: Configuration for user deletion
298 | 
299 | ## `session`
300 | 
301 | Session configuration options.
302 | 
303 | ```ts
304 | import { betterAuth } from "better-auth";
305 | export const auth = betterAuth({
306 | 	session: {
307 | 		modelName: "sessions",
308 | 		fields: {
309 | 			userId: "user_id"
310 | 		},
311 | 		expiresIn: 604800, // 7 days
312 | 		updateAge: 86400, // 1 day
313 | 		disableSessionRefresh: true, // Disable session refresh so that the session is not updated regardless of the `updateAge` option. (default: `false`)
314 | 		additionalFields: { // Additional fields for the session table
315 | 			customField: {
316 | 				type: "string",
317 | 			}
318 | 		},
319 | 		storeSessionInDatabase: true, // Store session in database when secondary storage is provided (default: `false`)
320 | 		preserveSessionInDatabase: false, // Preserve session records in database when deleted from secondary storage (default: `false`)
321 | 		cookieCache: {
322 | 			enabled: true, // Enable caching session in cookie (default: `false`)	
323 | 			maxAge: 300 // 5 minutes
324 | 		}
325 | 	},
326 | })
327 | ```
328 | 
329 | - `modelName`: The model name for the session (default: `"session"`)
330 | - `fields`: Map fields to different column names
331 | - `expiresIn`: Expiration time for the session token in seconds (default: `604800` - 7 days)
332 | - `updateAge`: How often the session should be refreshed in seconds (default: `86400` - 1 day)
333 | - `additionalFields`: Additional fields for the session table
334 | - `storeSessionInDatabase`: Store session in database when secondary storage is provided (default: `false`)
335 | - `preserveSessionInDatabase`: Preserve session records in database when deleted from secondary storage (default: `false`)
336 | - `cookieCache`: Enable caching session in cookie
337 | 
338 | ## `account`
339 | 
340 | Account configuration options.
341 | 
342 | ```ts
343 | import { betterAuth } from "better-auth";
344 | export const auth = betterAuth({
345 | 	account: {
346 | 		modelName: "accounts",
347 | 		fields: {
348 | 			userId: "user_id"
349 | 		},
350 | 		encryptOAuthTokens: true, // Encrypt OAuth tokens before storing them in the database
351 | 		accountLinking: {
352 | 			enabled: true,
353 | 			trustedProviders: ["google", "github", "email-password"],
354 | 			allowDifferentEmails: false
355 | 		}
356 | 	},
357 | })
358 | ```
359 | 
360 | - `modelName`: The model name for the account
361 | - `fields`: Map fields to different column names
362 | 
363 | ### `encryptOAuthTokens`
364 | 
365 | Encrypt OAuth tokens before storing them in the database. Default: `false`.
366 | 
367 | ### `updateAccountOnSignIn`
368 | 
369 | If enabled (true), the user account data (accessToken, idToken, refreshToken, etc.)
370 | will be updated on sign in with the latest data from the provider.
371 | 
372 | ### `accountLinking`
373 | 
374 | Configuration for account linking.
375 | 
376 | - `enabled`: Enable account linking (default: `false`)
377 | - `trustedProviders`: List of trusted providers
378 | - `allowDifferentEmails`: Allow users to link accounts with different email addresses
379 | - `allowUnlinkingAll`: Allow users to unlink all accounts
380 | 
381 | ## `verification`
382 | 
383 | Verification configuration options.
384 | 
385 | ```ts
386 | import { betterAuth } from "better-auth";
387 | export const auth = betterAuth({
388 | 	verification: {
389 | 		modelName: "verifications",
390 | 		fields: {
391 | 			userId: "user_id"
392 | 		},
393 | 		disableCleanup: false
394 | 	},
395 | })
396 | ```
397 | 
398 | - `modelName`: The model name for the verification table
399 | - `fields`: Map fields to different column names
400 | - `disableCleanup`: Disable cleaning up expired values when a verification value is fetched
401 | 
402 | ## `rateLimit`
403 | 
404 | Rate limiting configuration.
405 | 
406 | ```ts
407 | import { betterAuth } from "better-auth";
408 | export const auth = betterAuth({
409 | 	rateLimit: {
410 | 		enabled: true,
411 | 		window: 10,
412 | 		max: 100,
413 | 		customRules: {
414 | 			"/example/path": {
415 | 				window: 10,
416 | 				max: 100
417 | 			}
418 | 		},
419 | 		storage: "memory",
420 | 		modelName: "rateLimit"
421 | 	}
422 | })
423 | ```
424 | 
425 | - `enabled`: Enable rate limiting (defaults: `true` in production, `false` in development)
426 | - `window`: Time window to use for rate limiting. The value should be in seconds. (default: `10`)
427 | - `max`: The default maximum number of requests allowed within the window. (default: `100`)
428 | - `customRules`: Custom rate limit rules to apply to specific paths.
429 | - `storage`: Storage configuration. If you passed a secondary storage, rate limiting will be stored in the secondary storage. (options: `"memory", "database", "secondary-storage"`, default: `"memory"`)
430 | - `modelName`: The name of the table to use for rate limiting if database is used as storage. (default: `"rateLimit"`)
431 | 
432 | 
433 | ## `advanced`
434 | 
435 | Advanced configuration options.
436 | 
437 | ```ts
438 | import { betterAuth } from "better-auth";
439 | export const auth = betterAuth({
440 | 	advanced: {
441 | 		ipAddress: {
442 | 			ipAddressHeaders: ["x-client-ip", "x-forwarded-for"],
443 | 			disableIpTracking: false
444 | 		},
445 | 		useSecureCookies: true,
446 | 		disableCSRFCheck: false,
447 | 		crossSubDomainCookies: {
448 | 			enabled: true,
449 | 			additionalCookies: ["custom_cookie"],
450 | 			domain: "example.com"
451 | 		},
452 | 		cookies: {
453 | 			session_token: {
454 | 				name: "custom_session_token",
455 | 				attributes: {
456 | 					httpOnly: true,
457 | 					secure: true
458 | 				}
459 | 			}
460 | 		},
461 | 		defaultCookieAttributes: {
462 | 			httpOnly: true,
463 | 			secure: true
464 | 		},
465 | 		cookiePrefix: "myapp",
466 | 		database: {
467 | 			// If your DB is using auto-incrementing IDs, set this to true.
468 | 			useNumberId: false,
469 | 			// Use your own custom ID generator, or disable generating IDs as a whole.
470 | 			generateId: (((options: {
471 | 				model: LiteralUnion<Models, string>;
472 | 				size?: number;
473 | 			}) => {
474 | 				return "my-super-unique-id";
475 | 			})) | false,
476 | 			defaultFindManyLimit: 100,
477 | 		}
478 | 	},
479 | })
480 | ```
481 | 
482 | - `ipAddress`: IP address configuration for rate limiting and session tracking
483 | - `useSecureCookies`: Use secure cookies (default: `false`)
484 | - `disableCSRFCheck`: Disable trusted origins check (⚠️ security risk)
485 | - `crossSubDomainCookies`: Configure cookies to be shared across subdomains
486 | - `cookies`: Customize cookie names and attributes
487 | - `defaultCookieAttributes`: Default attributes for all cookies
488 | - `cookiePrefix`: Prefix for cookies
489 | - `generateId`: Function to generate a unique ID for a model
490 | 
491 | ## `logger`
492 | 
493 | Logger configuration for Better Auth.
494 | 
495 | ```ts
496 | import { betterAuth } from "better-auth";
497 | export const auth = betterAuth({
498 | 	logger: {
499 | 		disabled: false,
500 | 		disableColors: false,
501 | 		level: "error",
502 | 		log: (level, message, ...args) => {
503 | 			// Custom logging implementation
504 | 			console.log(`[${level}] ${message}`, ...args);
505 | 		}
506 | 	}
507 | })
508 | ```
509 | 
510 | The logger configuration allows you to customize how Better Auth handles logging. It supports the following options:
511 | 
512 | - `disabled`: Disable all logging when set to `true` (default: `false`)
513 | - `disableColors`: Disable colors in the default logger implementation (default: determined by the terminal's color support)
514 | - `level`: Set the minimum log level to display. Available levels are:
515 |   - `"info"`: Show all logs
516 |   - `"warn"`: Show warnings and errors
517 |   - `"error"`: Show only errors
518 |   - `"debug"`: Show all logs including debug information
519 | - `log`: Custom logging function that receives:
520 |   - `level`: The log level (`"info"`, `"warn"`, `"error"`, or `"debug"`)
521 |   - `message`: The log message
522 |   - `...args`: Additional arguments passed to the logger
523 | 
524 | Example with custom logging:
525 | 
526 | ```ts
527 | import { betterAuth } from "better-auth";
528 | export const auth = betterAuth({
529 | 	logger: {
530 | 		level: "info",
531 | 		log: (level, message, ...args) => {
532 | 			// Send logs to a custom logging service
533 | 			myLoggingService.log({
534 | 				level,
535 | 				message,
536 | 				metadata: args,
537 | 				timestamp: new Date().toISOString()
538 | 			});
539 | 		}
540 | 	}
541 | })
542 | ```
543 | 
544 | ## `databaseHooks`
545 | 
546 | Database lifecycle hooks for core operations.
547 | 
548 | ```ts
549 | import { betterAuth } from "better-auth";
550 | export const auth = betterAuth({
551 | 	databaseHooks: {
552 | 		user: {
553 | 			create: {
554 | 				before: async (user) => {
555 | 					// Modify user data before creation
556 | 					return { data: { ...user, customField: "value" } };
557 | 				},
558 | 				after: async (user) => {
559 | 					// Perform actions after user creation
560 | 				}
561 | 			},
562 | 			update: {
563 | 				before: async (userData) => {
564 | 					// Modify user data before update
565 | 					return { data: { ...userData, updatedAt: new Date() } };
566 | 				},
567 | 				after: async (user) => {
568 | 					// Perform actions after user update
569 | 				}
570 | 			}
571 | 		},
572 | 		session: {
573 | 			// Session hooks
574 | 		},
575 | 		account: {
576 | 			// Account hooks
577 | 		},
578 | 		verification: {
579 | 			// Verification hooks
580 | 		}
581 | 	},
582 | })
583 | ```
584 | 
585 | ## `onAPIError`
586 | 
587 | API error handling configuration.
588 | 
589 | ```ts
590 | import { betterAuth } from "better-auth";
591 | export const auth = betterAuth({
592 | 	onAPIError: {
593 | 		throw: true,
594 | 		onError: (error, ctx) => {
595 | 			// Custom error handling
596 | 			console.error("Auth error:", error);
597 | 		},
598 | 		errorURL: "/auth/error"
599 | 	},
600 | })
601 | ```
602 | 
603 | - `throw`: Throw an error on API error (default: `false`)
604 | - `onError`: Custom error handler
605 | - `errorURL`: URL to redirect to on error (default: `/api/auth/error`)
606 | 
607 | ## `hooks`
608 | 
609 | Request lifecycle hooks.
610 | 
611 | ```ts
612 | import { betterAuth } from "better-auth";
613 | import { createAuthMiddleware } from "better-auth/api";
614 | 
615 | export const auth = betterAuth({
616 | 	hooks: {
617 | 		before: createAuthMiddleware(async (ctx) => {
618 | 			// Execute before processing the request
619 | 			console.log("Request path:", ctx.path);
620 | 		}),
621 | 		after: createAuthMiddleware(async (ctx) => {
622 | 			// Execute after processing the request
623 | 			console.log("Response:", ctx.context.returned);
624 | 		})
625 | 	},
626 | })
627 | ```
628 | 
629 | For more details and examples, see the [Hooks documentation](/docs/concepts/hooks).
630 | 
631 | ## `disabledPaths`
632 | 
633 | Disable specific auth paths.
634 | 
635 | ```ts
636 | import { betterAuth } from "better-auth";
637 | export const auth = betterAuth({
638 | 	disabledPaths: ["/sign-up/email", "/sign-in/email"],
639 | })
640 | ```
641 | 
642 | ## `telemetry`
643 | 
644 | Enable or disable Better Auth's telemetry collection. (default: `false`)
645 | 
646 | ```ts
647 | import { betterAuth } from "better-auth";
648 | export const auth = betterAuth({
649 |   telemetry: {
650 |     enabled: false,
651 |   }
652 | })
653 | ```
654 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/cookies/cookies.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it } from "vitest";
  2 | import { getTestInstance } from "../test-utils/test-instance";
  3 | import { getCookieCache, getCookies, getSessionCookie } from "../cookies";
  4 | import { parseSetCookieHeader } from "./cookie-utils";
  5 | import type { BetterAuthOptions } from "@better-auth/core";
  6 | 
  7 | describe("cookies", async () => {
  8 | 	const { client, testUser } = await getTestInstance();
  9 | 	it("should set cookies with default options", async () => {
 10 | 		await client.signIn.email(
 11 | 			{
 12 | 				email: testUser.email,
 13 | 				password: testUser.password,
 14 | 			},
 15 | 			{
 16 | 				onResponse(context) {
 17 | 					const setCookie = context.response.headers.get("set-cookie");
 18 | 					expect(setCookie).toBeDefined();
 19 | 					expect(setCookie).toContain("Path=/");
 20 | 					expect(setCookie).toContain("HttpOnly");
 21 | 					expect(setCookie).toContain("SameSite=Lax");
 22 | 					expect(setCookie).toContain("better-auth");
 23 | 				},
 24 | 			},
 25 | 		);
 26 | 	});
 27 | 
 28 | 	it("should set multiple cookies", async () => {
 29 | 		await client.signIn.social(
 30 | 			{
 31 | 				provider: "github",
 32 | 				callbackURL: "https://example.com",
 33 | 			},
 34 | 			{
 35 | 				onSuccess(context) {
 36 | 					const cookies = context.response.headers.get("Set-Cookie");
 37 | 					expect(cookies?.split(",").length).toBeGreaterThan(1);
 38 | 				},
 39 | 			},
 40 | 		);
 41 | 	});
 42 | 
 43 | 	it("should use secure cookies", async () => {
 44 | 		const { client, testUser } = await getTestInstance({
 45 | 			advanced: { useSecureCookies: true },
 46 | 		});
 47 | 		const res = await client.signIn.email(
 48 | 			{
 49 | 				email: testUser.email,
 50 | 				password: testUser.password,
 51 | 			},
 52 | 			{
 53 | 				onResponse(context) {
 54 | 					const setCookie = context.response.headers.get("set-cookie");
 55 | 					expect(setCookie).toContain("Secure");
 56 | 				},
 57 | 			},
 58 | 		);
 59 | 	});
 60 | 
 61 | 	it("should use secure cookies when the base url is https", async () => {
 62 | 		const { client, testUser } = await getTestInstance({
 63 | 			baseURL: "https://example.com",
 64 | 		});
 65 | 
 66 | 		await client.signIn.email(
 67 | 			{
 68 | 				email: testUser.email,
 69 | 				password: testUser.password,
 70 | 			},
 71 | 			{
 72 | 				onResponse(context) {
 73 | 					const setCookie = context.response.headers.get("set-cookie");
 74 | 					expect(setCookie).toContain("Secure");
 75 | 				},
 76 | 			},
 77 | 		);
 78 | 	});
 79 | });
 80 | 
 81 | describe("crossSubdomainCookies", () => {
 82 | 	it("should update cookies with custom domain", async () => {
 83 | 		const { client, testUser } = await getTestInstance({
 84 | 			advanced: {
 85 | 				crossSubDomainCookies: {
 86 | 					enabled: true,
 87 | 					domain: "example.com",
 88 | 				},
 89 | 			},
 90 | 		});
 91 | 
 92 | 		await client.signIn.email(
 93 | 			{
 94 | 				email: testUser.email,
 95 | 				password: testUser.password,
 96 | 			},
 97 | 			{
 98 | 				onResponse(context) {
 99 | 					const setCookie = context.response.headers.get("set-cookie");
100 | 					expect(setCookie).toContain("Domain=example.com");
101 | 					expect(setCookie).toContain("SameSite=Lax");
102 | 				},
103 | 			},
104 | 		);
105 | 	});
106 | 
107 | 	it("should use default domain from baseURL if not provided", async () => {
108 | 		const { testUser, client } = await getTestInstance({
109 | 			baseURL: "https://example.com",
110 | 			advanced: {
111 | 				crossSubDomainCookies: {
112 | 					enabled: true,
113 | 				},
114 | 			},
115 | 		});
116 | 
117 | 		await client.signIn.email(
118 | 			{
119 | 				email: testUser.email,
120 | 				password: testUser.password,
121 | 			},
122 | 			{
123 | 				onResponse(context) {
124 | 					const setCookie = context.response.headers.get("set-cookie");
125 | 					expect(setCookie).toContain("Domain=example.com");
126 | 				},
127 | 			},
128 | 		);
129 | 	});
130 | });
131 | 
132 | describe("cookie configuration", () => {
133 | 	it("should return correct cookie options based on configuration", async () => {
134 | 		const options = {
135 | 			baseURL: "https://example.com",
136 | 			database: {} as BetterAuthOptions["database"],
137 | 			advanced: {
138 | 				useSecureCookies: true,
139 | 				crossSubDomainCookies: {
140 | 					enabled: true,
141 | 					domain: "example.com",
142 | 				},
143 | 				cookiePrefix: "test-prefix",
144 | 			},
145 | 		} satisfies BetterAuthOptions;
146 | 
147 | 		const cookies = getCookies(options);
148 | 
149 | 		expect(cookies.sessionToken.options.secure).toBe(true);
150 | 		expect(cookies.sessionToken.name).toContain("test-prefix.session_token");
151 | 		expect(cookies.sessionData.options.sameSite).toBe("lax");
152 | 		expect(cookies.sessionData.options.domain).toBe("example.com");
153 | 	});
154 | });
155 | 
156 | describe("cookie-utils parseSetCookieHeader", () => {
157 | 	it("handles Expires with commas and multiple cookies", () => {
158 | 		const header =
159 | 			"a=1; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/, b=2; Expires=Thu, 22 Oct 2015 07:28:00 GMT; Path=/";
160 | 		const map = parseSetCookieHeader(header);
161 | 		expect(map.get("a")?.value).toBe("1");
162 | 		expect(map.get("b")?.value).toBe("2");
163 | 	});
164 | });
165 | 
166 | describe("getSessionCookie", async () => {
167 | 	it("should return the correct session cookie", async () => {
168 | 		const { client, testUser, signInWithTestUser } = await getTestInstance();
169 | 		const { headers } = await signInWithTestUser();
170 | 		const request = new Request("http://localhost:3000/api/auth/session", {
171 | 			headers,
172 | 		});
173 | 		const cookies = getSessionCookie(request);
174 | 		expect(cookies).not.toBeNull();
175 | 		expect(cookies).toBeDefined();
176 | 	});
177 | 
178 | 	it("should return the correct session cookie on production", async () => {
179 | 		const { client, testUser, cookieSetter } = await getTestInstance({
180 | 			baseURL: "https://example.com",
181 | 		});
182 | 		const headers = new Headers();
183 | 		await client.signIn.email(
184 | 			{
185 | 				email: testUser.email,
186 | 				password: testUser.password,
187 | 			},
188 | 			{
189 | 				onSuccess: cookieSetter(headers),
190 | 			},
191 | 		);
192 | 		const request = new Request("https://example.com/api/auth/session", {
193 | 			headers,
194 | 		});
195 | 		const cookies = getSessionCookie(request);
196 | 		expect(cookies).not.toBeNull();
197 | 		expect(cookies).toBeDefined();
198 | 	});
199 | 
200 | 	it("should allow override cookie prefix", async () => {
201 | 		const { client, testUser, cookieSetter } = await getTestInstance({
202 | 			advanced: {
203 | 				useSecureCookies: true,
204 | 				cookiePrefix: "test-prefix",
205 | 			},
206 | 		});
207 | 		const headers = new Headers();
208 | 		await client.signIn.email(
209 | 			{
210 | 				email: testUser.email,
211 | 				password: testUser.password,
212 | 			},
213 | 			{ onSuccess: cookieSetter(headers) },
214 | 		);
215 | 		const request = new Request("https://example.com/api/auth/session", {
216 | 			headers,
217 | 		});
218 | 		const cookies = getSessionCookie(request, {
219 | 			cookiePrefix: "test-prefix",
220 | 		});
221 | 		expect(cookies).not.toBeNull();
222 | 	});
223 | 
224 | 	it("should allow override cookie name", async () => {
225 | 		const { client, testUser, cookieSetter } = await getTestInstance({
226 | 			advanced: {
227 | 				useSecureCookies: true,
228 | 				cookiePrefix: "test",
229 | 				cookies: {
230 | 					session_token: {
231 | 						name: "test-session-token",
232 | 					},
233 | 				},
234 | 			},
235 | 		});
236 | 		const headers = new Headers();
237 | 		await client.signIn.email(
238 | 			{
239 | 				email: testUser.email,
240 | 				password: testUser.password,
241 | 			},
242 | 			{
243 | 				onSuccess: cookieSetter(headers),
244 | 			},
245 | 		);
246 | 		const request = new Request("https://example.com/api/auth/session", {
247 | 			headers,
248 | 		});
249 | 		const cookies = getSessionCookie(request, {
250 | 			cookieName: "session-token",
251 | 			cookiePrefix: "test",
252 | 		});
253 | 		expect(cookies).not.toBeNull();
254 | 	});
255 | 
256 | 	it("should return cookie cache", async () => {
257 | 		const { client, testUser, cookieSetter } = await getTestInstance({
258 | 			secret: "better-auth.secret",
259 | 			session: {
260 | 				cookieCache: {
261 | 					enabled: true,
262 | 				},
263 | 			},
264 | 		});
265 | 
266 | 		const headers = new Headers();
267 | 
268 | 		await client.signIn.email(
269 | 			{
270 | 				email: testUser.email,
271 | 				password: testUser.password,
272 | 			},
273 | 			{
274 | 				onSuccess: cookieSetter(headers),
275 | 			},
276 | 		);
277 | 
278 | 		const request = new Request("https://example.com/api/auth/session", {
279 | 			headers,
280 | 		});
281 | 
282 | 		const cache = await getCookieCache(request, {
283 | 			secret: "better-auth.secret",
284 | 		});
285 | 		expect(cache).not.toBeNull();
286 | 		expect(cache?.user?.email).toEqual(testUser.email);
287 | 		expect(cache?.session?.token).toEqual(expect.any(String));
288 | 	});
289 | 
290 | 	it("should respect dontRememberMe when storing session in cookie cache", async () => {
291 | 		const { client, testUser } = await getTestInstance({
292 | 			secret: "better-auth.secret",
293 | 			session: {
294 | 				cookieCache: {
295 | 					enabled: true,
296 | 				},
297 | 			},
298 | 		});
299 | 
300 | 		await client.signIn.email(
301 | 			{
302 | 				email: testUser.email,
303 | 				password: testUser.password,
304 | 				rememberMe: false,
305 | 			},
306 | 			{
307 | 				onSuccess(c) {
308 | 					const headers = c.response.headers;
309 | 					const setCookieHeader = headers.get("set-cookie");
310 | 					expect(setCookieHeader).toBeDefined();
311 | 
312 | 					const parsed = parseSetCookieHeader(setCookieHeader!);
313 | 
314 | 					const sessionTokenCookie = parsed.get("better-auth.session_token")!;
315 | 					expect(sessionTokenCookie).toBeDefined();
316 | 					expect(sessionTokenCookie["max-age"]).toBeUndefined();
317 | 
318 | 					const sessionDataCookie = parsed.get("better-auth.session_data")!;
319 | 					expect(sessionDataCookie).toBeDefined();
320 | 					expect(sessionDataCookie["max-age"]).toBeUndefined();
321 | 				},
322 | 			},
323 | 		);
324 | 	});
325 | 
326 | 	it("should return null if the cookie is invalid", async () => {
327 | 		const { client, testUser, cookieSetter } = await getTestInstance({
328 | 			session: {
329 | 				cookieCache: {
330 | 					enabled: true,
331 | 				},
332 | 			},
333 | 		});
334 | 		const headers = new Headers();
335 | 		await client.signIn.email({
336 | 			email: testUser.email,
337 | 			password: testUser.password,
338 | 		});
339 | 		const request = new Request("https://example.com/api/auth/session", {
340 | 			headers,
341 | 		});
342 | 		const cache = await getCookieCache(request, {
343 | 			secret: "wrong-secret",
344 | 		});
345 | 		expect(cache).toBeNull();
346 | 	});
347 | 
348 | 	it("should throw an error if the secret is not provided", async () => {
349 | 		const { client, testUser, cookieSetter } = await getTestInstance({
350 | 			session: {
351 | 				cookieCache: {
352 | 					enabled: true,
353 | 				},
354 | 			},
355 | 		});
356 | 		const headers = new Headers();
357 | 		await client.signIn.email(
358 | 			{
359 | 				email: testUser.email,
360 | 				password: testUser.password,
361 | 			},
362 | 			{
363 | 				onSuccess: cookieSetter(headers),
364 | 			},
365 | 		);
366 | 		const request = new Request("https://example.com/api/auth/session", {
367 | 			headers,
368 | 		});
369 | 		await expect(getCookieCache(request)).rejects.toThrow();
370 | 	});
371 | 
372 | 	it("should log error and skip setting cookie when data exceeds size limit", async () => {
373 | 		const loggerErrors: string[] = [];
374 | 		const mockLogger = {
375 | 			log: (level: string, message: string) => {
376 | 				if (level === "error") {
377 | 					loggerErrors.push(message);
378 | 				}
379 | 			},
380 | 		};
381 | 
382 | 		const { auth } = await getTestInstance({
383 | 			secret: "better-auth.secret",
384 | 			user: {
385 | 				additionalFields: {
386 | 					customField1: {
387 | 						type: "string",
388 | 						defaultValue: "",
389 | 					},
390 | 					customField2: {
391 | 						type: "string",
392 | 						defaultValue: "",
393 | 					},
394 | 					customField3: {
395 | 						type: "string",
396 | 						defaultValue: "",
397 | 					},
398 | 				},
399 | 			},
400 | 			session: {
401 | 				cookieCache: {
402 | 					enabled: true,
403 | 				},
404 | 			},
405 | 			logger: mockLogger,
406 | 		});
407 | 
408 | 		// Create a very large string that will exceed the cookie size limit when combined with session data
409 | 		// The limit is 4093 bytes, so we create data that will definitely exceed it
410 | 		const largeString = "x".repeat(2000);
411 | 
412 | 		// Sign up with large user data using the server API
413 | 		const result = await auth.api.signUpEmail({
414 | 			body: {
415 | 				name: "Test User",
416 | 				email: "[email protected]",
417 | 				password: "password123",
418 | 				customField1: largeString,
419 | 				customField2: largeString,
420 | 				customField3: largeString,
421 | 			},
422 | 		});
423 | 
424 | 		// Check that logger recorded an error about exceeding size limit
425 | 		const sizeError = loggerErrors.find((msg) =>
426 | 			msg.includes("Session data exceeds cookie size limit"),
427 | 		);
428 | 		expect(sizeError).toBeDefined();
429 | 		expect(sizeError).toContain("4093 bytes");
430 | 
431 | 		// The sign up should still succeed
432 | 		expect(result).toBeDefined();
433 | 		expect(result?.user).toBeDefined();
434 | 	});
435 | });
436 | 
437 | describe("Cookie Cache Field Filtering", () => {
438 | 	it("should exclude user fields with returned: false from cookie cache", async () => {
439 | 		const { client, testUser, cookieSetter } = await getTestInstance({
440 | 			secret: "better-auth.secret",
441 | 			user: {
442 | 				additionalFields: {
443 | 					internalNote: {
444 | 						type: "string",
445 | 						defaultValue: "",
446 | 						returned: false,
447 | 					},
448 | 				},
449 | 			},
450 | 			session: {
451 | 				cookieCache: {
452 | 					enabled: true,
453 | 				},
454 | 			},
455 | 		});
456 | 
457 | 		const headers = new Headers();
458 | 
459 | 		await client.signIn.email(
460 | 			{
461 | 				email: testUser.email,
462 | 				password: testUser.password,
463 | 			},
464 | 			{
465 | 				onSuccess: cookieSetter(headers),
466 | 			},
467 | 		);
468 | 
469 | 		const request = new Request("https://example.com/api/auth/session", {
470 | 			headers,
471 | 		});
472 | 
473 | 		const cache = await getCookieCache(request, {
474 | 			secret: "better-auth.secret",
475 | 		});
476 | 
477 | 		expect(cache).not.toBeNull();
478 | 		expect(cache?.user?.email).toEqual(testUser.email);
479 | 		expect(cache?.user?.internalNote).toBeUndefined();
480 | 	});
481 | 
482 | 	it("should correctly filter multiple user fields based on returned config", async () => {
483 | 		const { client, testUser, cookieSetter } = await getTestInstance({
484 | 			secret: "better-auth.secret",
485 | 			user: {
486 | 				additionalFields: {
487 | 					publicBio: {
488 | 						type: "string",
489 | 						defaultValue: "default-bio",
490 | 						returned: true,
491 | 					},
492 | 					internalNotes: {
493 | 						type: "string",
494 | 						defaultValue: "internal-notes",
495 | 						returned: false,
496 | 					},
497 | 					preferences: {
498 | 						type: "string",
499 | 						defaultValue: "default-prefs",
500 | 						returned: true,
501 | 					},
502 | 					adminFlags: {
503 | 						type: "string",
504 | 						defaultValue: "admin-flags",
505 | 						returned: false,
506 | 					},
507 | 				},
508 | 			},
509 | 			session: {
510 | 				cookieCache: {
511 | 					enabled: true,
512 | 				},
513 | 			},
514 | 		});
515 | 
516 | 		const headers = new Headers();
517 | 		await client.signIn.email(
518 | 			{
519 | 				email: testUser.email,
520 | 				password: testUser.password,
521 | 			},
522 | 			{
523 | 				onSuccess: cookieSetter(headers),
524 | 			},
525 | 		);
526 | 
527 | 		const request = new Request("https://example.com/api/auth/session", {
528 | 			headers,
529 | 		});
530 | 
531 | 		const cache = await getCookieCache(request, {
532 | 			secret: "better-auth.secret",
533 | 		});
534 | 
535 | 		expect(cache).not.toBeNull();
536 | 		// Fields with returned: true should be included
537 | 		expect(cache?.user?.publicBio).toBeDefined();
538 | 		expect(cache?.user?.preferences).toBeDefined();
539 | 		// Fields with returned: false should be excluded
540 | 		expect(cache?.user?.internalNotes).toBeUndefined();
541 | 		expect(cache?.user?.adminFlags).toBeUndefined();
542 | 	});
543 | 
544 | 	it("should reduce cookie size when large fields are excluded", async () => {
545 | 		const largeString = "x".repeat(2000);
546 | 		const { client, testUser, cookieSetter } = await getTestInstance({
547 | 			secret: "better-auth.secret",
548 | 			user: {
549 | 				additionalFields: {
550 | 					largeBio: {
551 | 						type: "string",
552 | 						defaultValue: largeString,
553 | 						returned: false,
554 | 					},
555 | 					smallField: {
556 | 						type: "string",
557 | 						defaultValue: "small-value",
558 | 						returned: true,
559 | 					},
560 | 				},
561 | 			},
562 | 			session: {
563 | 				cookieCache: {
564 | 					enabled: true,
565 | 				},
566 | 			},
567 | 		});
568 | 
569 | 		const headers = new Headers();
570 | 
571 | 		// Sign in with testUser
572 | 		await client.signIn.email(
573 | 			{
574 | 				email: testUser.email,
575 | 				password: testUser.password,
576 | 			},
577 | 			{
578 | 				onSuccess: cookieSetter(headers),
579 | 			},
580 | 		);
581 | 
582 | 		const request = new Request("https://example.com/api/auth/session", {
583 | 			headers,
584 | 		});
585 | 
586 | 		const cache = await getCookieCache(request, {
587 | 			secret: "better-auth.secret",
588 | 		});
589 | 
590 | 		// Cookie cache should exist (not exceed size limit)
591 | 		expect(cache).not.toBeNull();
592 | 		// Large field should be excluded
593 | 		expect(cache?.user?.largeBio).toBeUndefined();
594 | 		// Small field should be included
595 | 		expect(cache?.user?.smallField).toBeDefined();
596 | 	});
597 | 
598 | 	it("should maintain session field filtering (regression check)", async () => {
599 | 		const { client, testUser, cookieSetter } = await getTestInstance({
600 | 			secret: "better-auth.secret",
601 | 			session: {
602 | 				additionalFields: {
603 | 					internalSessionData: {
604 | 						type: "string",
605 | 						defaultValue: "internal-data",
606 | 						returned: false,
607 | 					},
608 | 					publicSessionData: {
609 | 						type: "string",
610 | 						defaultValue: "public-data",
611 | 						returned: true,
612 | 					},
613 | 				},
614 | 				cookieCache: {
615 | 					enabled: true,
616 | 				},
617 | 			},
618 | 		});
619 | 
620 | 		const headers = new Headers();
621 | 
622 | 		// Sign in with testUser
623 | 		await client.signIn.email(
624 | 			{
625 | 				email: testUser.email,
626 | 				password: testUser.password,
627 | 			},
628 | 			{
629 | 				onSuccess: cookieSetter(headers),
630 | 			},
631 | 		);
632 | 
633 | 		const request = new Request("https://example.com/api/auth/session", {
634 | 			headers,
635 | 		});
636 | 
637 | 		const cache = await getCookieCache(request, {
638 | 			secret: "better-auth.secret",
639 | 		});
640 | 
641 | 		expect(cache).not.toBeNull();
642 | 		// Verify session field filtering still works correctly
643 | 		// Session should have token
644 | 		expect(cache?.session?.token).toEqual(expect.any(String));
645 | 		// Session field with returned: false should be excluded
646 | 		expect(cache?.session?.internalSessionData).toBeUndefined();
647 | 	});
648 | 
649 | 	it("should include unknown user fields for backward compatibility", async () => {
650 | 		const { client, testUser, cookieSetter } = await getTestInstance({
651 | 			secret: "better-auth.secret",
652 | 			user: {
653 | 				additionalFields: {
654 | 					knownField: {
655 | 						type: "string",
656 | 						defaultValue: "known-value",
657 | 						returned: false,
658 | 					},
659 | 				},
660 | 			},
661 | 			session: {
662 | 				cookieCache: {
663 | 					enabled: true,
664 | 				},
665 | 			},
666 | 		});
667 | 
668 | 		const headers = new Headers();
669 | 
670 | 		await client.signIn.email(
671 | 			{
672 | 				email: testUser.email,
673 | 				password: testUser.password,
674 | 			},
675 | 			{
676 | 				onSuccess: cookieSetter(headers),
677 | 			},
678 | 		);
679 | 
680 | 		const request = new Request("https://example.com/api/auth/session", {
681 | 			headers,
682 | 		});
683 | 
684 | 		const cache = await getCookieCache(request, {
685 | 			secret: "better-auth.secret",
686 | 		});
687 | 
688 | 		expect(cache).not.toBeNull();
689 | 		// Known field with returned: false should be excluded
690 | 		expect(cache?.user?.knownField).toBeUndefined();
691 | 		// Standard fields like email, name should be included (backward compatibility)
692 | 		expect(cache?.user?.email).toEqual(testUser.email);
693 | 		expect(cache?.user?.name).toBeDefined();
694 | 	});
695 | });
696 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/db/internal-adapter.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { beforeAll, expect, it, describe, vi, afterEach } from "vitest";
  2 | import type {
  3 | 	BetterAuthOptions,
  4 | 	BetterAuthPlugin,
  5 | 	Session,
  6 | 	User,
  7 | } from "../types";
  8 | import Database from "better-sqlite3";
  9 | import { init } from "../init";
 10 | import { betterAuth } from "../auth";
 11 | import { getMigrations } from "./get-migration";
 12 | import { Kysely, SqliteDialect } from "kysely";
 13 | import { getTestInstance } from "../test-utils/test-instance";
 14 | import { safeJSONParse } from "../utils/json";
 15 | import { runWithEndpointContext } from "@better-auth/core/context";
 16 | import type { GenericEndpointContext } from "@better-auth/core";
 17 | 
 18 | describe("adapter test", async () => {
 19 | 	const sqliteDialect = new SqliteDialect({
 20 | 		database: new Database(":memory:"),
 21 | 	});
 22 | 	const map = new Map();
 23 | 	const expirationMap = new Map();
 24 | 	let id = 1;
 25 | 	const hookUserCreateBefore = vi.fn();
 26 | 	const hookUserCreateAfter = vi.fn();
 27 | 	const pluginHookUserCreateBefore = vi.fn();
 28 | 	const pluginHookUserCreateAfter = vi.fn();
 29 | 	const opts = {
 30 | 		database: {
 31 | 			dialect: sqliteDialect,
 32 | 			type: "sqlite",
 33 | 		},
 34 | 		user: {
 35 | 			fields: {
 36 | 				email: "email_address",
 37 | 				emailVerified: "email_verified",
 38 | 			},
 39 | 		},
 40 | 		secondaryStorage: {
 41 | 			set(key, value, ttl) {
 42 | 				map.set(key, value);
 43 | 				expirationMap.set(key, ttl);
 44 | 			},
 45 | 			get(key) {
 46 | 				return map.get(key);
 47 | 			},
 48 | 			delete(key) {
 49 | 				map.delete(key);
 50 | 				expirationMap.delete(key);
 51 | 			},
 52 | 		},
 53 | 		advanced: {
 54 | 			database: {
 55 | 				generateId() {
 56 | 					return (id++).toString();
 57 | 				},
 58 | 			},
 59 | 		},
 60 | 		databaseHooks: {
 61 | 			user: {
 62 | 				create: {
 63 | 					async before(user, context) {
 64 | 						hookUserCreateBefore(user, context);
 65 | 						return { data: user };
 66 | 					},
 67 | 					async after(user, context) {
 68 | 						hookUserCreateAfter(user, context);
 69 | 						return;
 70 | 					},
 71 | 				},
 72 | 			},
 73 | 		},
 74 | 		plugins: [
 75 | 			{
 76 | 				id: "test-plugin",
 77 | 				init(ctx) {
 78 | 					return {
 79 | 						options: {
 80 | 							databaseHooks: {
 81 | 								user: {
 82 | 									create: {
 83 | 										async before(user, context) {
 84 | 											pluginHookUserCreateBefore(user, context);
 85 | 											return { data: user };
 86 | 										},
 87 | 										async after(user, context) {
 88 | 											pluginHookUserCreateAfter(user, context);
 89 | 										},
 90 | 									},
 91 | 								},
 92 | 								session: {
 93 | 									create: {
 94 | 										before: async (session) => {
 95 | 											return {
 96 | 												data: {
 97 | 													...session,
 98 | 													activeOrganizationId: "1",
 99 | 												},
100 | 											};
101 | 										},
102 | 									},
103 | 								},
104 | 							},
105 | 						},
106 | 					};
107 | 				},
108 | 			} satisfies BetterAuthPlugin,
109 | 		],
110 | 	} satisfies BetterAuthOptions;
111 | 	beforeAll(async () => {
112 | 		(await getMigrations(opts)).runMigrations();
113 | 	});
114 | 	afterEach(async () => {
115 | 		vi.clearAllMocks();
116 | 		map.clear();
117 | 	});
118 | 	const authContext = await init(opts);
119 | 	const ctx = {
120 | 		context: authContext,
121 | 	} as GenericEndpointContext;
122 | 	const internalAdapter = authContext.internalAdapter;
123 | 
124 | 	it("should create oauth user with custom generate id", async () => {
125 | 		const user = await runWithEndpointContext(ctx, () =>
126 | 			internalAdapter.createOAuthUser(
127 | 				{
128 | 					email: "[email protected]",
129 | 					name: "name",
130 | 					emailVerified: false,
131 | 				},
132 | 				{
133 | 					providerId: "provider",
134 | 					accountId: "account",
135 | 					accessTokenExpiresAt: new Date(),
136 | 					refreshTokenExpiresAt: new Date(),
137 | 					createdAt: new Date(),
138 | 					updatedAt: new Date(),
139 | 				},
140 | 			),
141 | 		);
142 | 		expect(user).toMatchObject({
143 | 			user: {
144 | 				id: "1",
145 | 				name: "name",
146 | 				email: "[email protected]",
147 | 				emailVerified: false,
148 | 				image: null,
149 | 				createdAt: expect.any(Date),
150 | 				updatedAt: expect.any(Date),
151 | 			},
152 | 			account: {
153 | 				id: "2",
154 | 				userId: expect.any(String),
155 | 				providerId: "provider",
156 | 				accountId: "account",
157 | 				accessToken: null,
158 | 				refreshToken: null,
159 | 				refreshTokenExpiresAt: expect.any(Date),
160 | 				accessTokenExpiresAt: expect.any(Date),
161 | 			},
162 | 		});
163 | 		expect(user?.user.id).toBe(user?.account.userId);
164 | 		expect(pluginHookUserCreateAfter).toHaveBeenCalledOnce();
165 | 		expect(pluginHookUserCreateBefore).toHaveBeenCalledOnce();
166 | 		expect(hookUserCreateAfter).toHaveBeenCalledOnce();
167 | 		expect(hookUserCreateBefore).toHaveBeenCalledOnce();
168 | 	});
169 | 	it("should find session with custom userId", async () => {
170 | 		const { client, signInWithTestUser } = await getTestInstance({
171 | 			session: {
172 | 				fields: {
173 | 					userId: "user_id",
174 | 				},
175 | 			},
176 | 		});
177 | 		const { headers } = await signInWithTestUser();
178 | 		const session = await client.getSession({
179 | 			fetchOptions: {
180 | 				headers,
181 | 			},
182 | 		});
183 | 		expect(session.data?.session).toBeDefined();
184 | 	});
185 | 
186 | 	it("should delete expired verification values on find", async () => {
187 | 		await runWithEndpointContext(ctx, () =>
188 | 			internalAdapter.createVerificationValue({
189 | 				identifier: `test-id-1`,
190 | 				value: "test-id-1",
191 | 				expiresAt: new Date(Date.now() - 1000),
192 | 			}),
193 | 		);
194 | 		const value = await internalAdapter.findVerificationValue("test-id-1");
195 | 		expect(value).toMatchObject({
196 | 			identifier: "test-id-1",
197 | 		});
198 | 		const value2 = await internalAdapter.findVerificationValue("test-id-1");
199 | 		expect(value2).toBe(undefined);
200 | 		await runWithEndpointContext(ctx, () =>
201 | 			internalAdapter.createVerificationValue({
202 | 				identifier: `test-id-1`,
203 | 				value: "test-id-1",
204 | 				expiresAt: new Date(Date.now() + 1000),
205 | 			}),
206 | 		);
207 | 		const value3 = await internalAdapter.findVerificationValue("test-id-1");
208 | 		expect(value3).toMatchObject({
209 | 			identifier: "test-id-1",
210 | 		});
211 | 		const value4 = await internalAdapter.findVerificationValue("test-id-1");
212 | 		expect(value4).toMatchObject({
213 | 			identifier: "test-id-1",
214 | 		});
215 | 	});
216 | 
217 | 	it("runs the after hook after adding user to db", async () => {
218 | 		const sampleUser = {
219 | 			name: "sample",
220 | 			email: "[email protected]",
221 | 			password: "sampliiiiiing",
222 | 		};
223 | 		const hookUserCreateAfter = vi.fn();
224 | 
225 | 		const dialect = new SqliteDialect({
226 | 			database: new Database(":memory:"),
227 | 		});
228 | 
229 | 		const db = new Kysely<any>({
230 | 			dialect,
231 | 		});
232 | 
233 | 		const opts: BetterAuthOptions = {
234 | 			database: {
235 | 				dialect,
236 | 				type: "sqlite",
237 | 			},
238 | 			databaseHooks: {
239 | 				user: {
240 | 					create: {
241 | 						async after(user, context) {
242 | 							hookUserCreateAfter(user, context);
243 | 
244 | 							const userFromDb: any = await db
245 | 								.selectFrom("user")
246 | 								.selectAll()
247 | 								.where("id", "=", user.id)
248 | 								.executeTakeFirst();
249 | 
250 | 							expect(user.id).toBe(userFromDb.id);
251 | 							expect(user.name).toBe(userFromDb.name);
252 | 							expect(user.email).toBe(userFromDb.email);
253 | 							expect(user.image).toBe(userFromDb.image);
254 | 							expect(user.emailVerified).toBe(
255 | 								Boolean(userFromDb.emailVerified),
256 | 							);
257 | 							expect(user.createdAt).toStrictEqual(
258 | 								new Date(userFromDb.createdAt),
259 | 							);
260 | 							expect(user.updatedAt).toStrictEqual(
261 | 								new Date(userFromDb.updatedAt),
262 | 							);
263 | 						},
264 | 					},
265 | 				},
266 | 			},
267 | 			emailAndPassword: { enabled: true },
268 | 		} satisfies BetterAuthOptions;
269 | 
270 | 		const migrations = await getMigrations(opts);
271 | 		await migrations.runMigrations();
272 | 
273 | 		const auth = betterAuth(opts);
274 | 
275 | 		await auth.api.signUpEmail({
276 | 			body: {
277 | 				name: sampleUser.name,
278 | 				email: sampleUser.email,
279 | 				password: sampleUser.password,
280 | 			},
281 | 		});
282 | 
283 | 		expect(hookUserCreateAfter).toHaveBeenCalledOnce();
284 | 	});
285 | 
286 | 	it("should calculate TTL correctly with Math.floor for secondary storage", async () => {
287 | 		const mockStorage = new Map<string, { value: string; ttl?: number }>();
288 | 		const capturedTTLs: number[] = [];
289 | 
290 | 		const testOpts = {
291 | 			database: {
292 | 				dialect: new SqliteDialect({
293 | 					database: new Database(":memory:"),
294 | 				}),
295 | 				type: "sqlite",
296 | 			},
297 | 			secondaryStorage: {
298 | 				set(key: string, value: string, ttl?: number) {
299 | 					if (ttl !== undefined) {
300 | 						capturedTTLs.push(ttl);
301 | 						mockStorage.set(key, { value, ttl });
302 | 					} else {
303 | 						mockStorage.set(key, { value });
304 | 					}
305 | 				},
306 | 				get(key: string) {
307 | 					const item = mockStorage.get(key);
308 | 					return item?.value || null;
309 | 				},
310 | 				delete(key: string) {
311 | 					mockStorage.delete(key);
312 | 				},
313 | 			},
314 | 		} satisfies BetterAuthOptions;
315 | 
316 | 		// Run migrations for the new database
317 | 		(await getMigrations(testOpts)).runMigrations();
318 | 
319 | 		// Test the actual refreshUserSessions functionality from internal adapter
320 | 		const testCtx = await init(testOpts);
321 | 		const ctx = {
322 | 			context: testCtx,
323 | 		} as GenericEndpointContext;
324 | 
325 | 		const testUser = {
326 | 			id: "test-user-id",
327 | 			name: "Test User",
328 | 			email: "[email protected]",
329 | 			emailVerified: true,
330 | 			image: null,
331 | 			createdAt: new Date(),
332 | 			updatedAt: new Date(),
333 | 		};
334 | 
335 | 		// Create a user in the database first
336 | 		await runWithEndpointContext(ctx, () =>
337 | 			ctx.context.internalAdapter.createUser(testUser),
338 | 		);
339 | 
340 | 		// Test case 1: Session with fractional seconds in TTL
341 | 		const expiresAt = new Date(Date.now() + 3599500); // 59 minutes and 59.5 seconds from now
342 | 		const expectedTTL = Math.floor(3599500 / 1000); // Should be 3599 seconds (rounded down)
343 | 
344 | 		const session = {
345 | 			id: "test-session-id",
346 | 			userId: testUser.id,
347 | 			token: "test-token",
348 | 			expiresAt,
349 | 			ipAddress: "127.0.0.1",
350 | 			userAgent: "test-agent",
351 | 		};
352 | 
353 | 		// Set up active sessions and session data for refresh test
354 | 		const activeSessions = [
355 | 			{ token: session.token, expiresAt: expiresAt.getTime() },
356 | 		];
357 | 
358 | 		await testCtx.options.secondaryStorage?.set(
359 | 			`active-sessions-${testUser.id}`,
360 | 			JSON.stringify(activeSessions),
361 | 		);
362 | 
363 | 		await testCtx.options.secondaryStorage?.set(
364 | 			session.token,
365 | 			JSON.stringify({ session, user: testUser }),
366 | 		);
367 | 
368 | 		// Trigger refreshUserSessions by updating the user
369 | 		await runWithEndpointContext(ctx, () =>
370 | 			ctx.context.internalAdapter.updateUser(testUser.id, {
371 | 				name: "Updated Name",
372 | 			}),
373 | 		);
374 | 
375 | 		// The TTL should be properly rounded down
376 | 		const lastTTL = capturedTTLs[capturedTTLs.length - 1];
377 | 		expect(lastTTL).toBeLessThanOrEqual(expectedTTL);
378 | 		expect(lastTTL).toBeGreaterThanOrEqual(expectedTTL - 1); // Allow for 1 second of test execution time
379 | 
380 | 		// Test case 2: Very small TTL (less than 1 second should round to 0)
381 | 		capturedTTLs.length = 0; // Clear array
382 | 		const almostExpiredSession = {
383 | 			...session,
384 | 			token: "almost-expired-token",
385 | 			expiresAt: new Date(Date.now() + 500), // 0.5 seconds from now
386 | 		};
387 | 
388 | 		await testCtx.options.secondaryStorage?.set(
389 | 			`active-sessions-${testUser.id}`,
390 | 			JSON.stringify([
391 | 				{
392 | 					token: almostExpiredSession.token,
393 | 					expiresAt: almostExpiredSession.expiresAt.getTime(),
394 | 				},
395 | 			]),
396 | 		);
397 | 
398 | 		await testCtx.options.secondaryStorage?.set(
399 | 			almostExpiredSession.token,
400 | 			JSON.stringify({ session: almostExpiredSession, user: testUser }),
401 | 		);
402 | 
403 | 		await runWithEndpointContext(ctx, () =>
404 | 			ctx.context.internalAdapter.updateUser(testUser.id, {
405 | 				name: "Updated Again",
406 | 			}),
407 | 		);
408 | 
409 | 		// Should be rounded down to 0
410 | 		expect(capturedTTLs.at(-1)).toBe(0);
411 | 
412 | 		// Test case 3: Large TTL with fractional component
413 | 		capturedTTLs.length = 0; // Clear array
414 | 		const longSession = {
415 | 			...session,
416 | 			token: "long-token",
417 | 			expiresAt: new Date(Date.now() + 7199999), // ~2 hours from now (7199.999 seconds)
418 | 		};
419 | 
420 | 		await testCtx.options.secondaryStorage?.set(
421 | 			`active-sessions-${testUser.id}`,
422 | 			JSON.stringify([
423 | 				{
424 | 					token: longSession.token,
425 | 					expiresAt: longSession.expiresAt.getTime(),
426 | 				},
427 | 			]),
428 | 		);
429 | 
430 | 		await testCtx.options.secondaryStorage?.set(
431 | 			longSession.token,
432 | 			JSON.stringify({ session: longSession, user: testUser }),
433 | 		);
434 | 
435 | 		await runWithEndpointContext(ctx, () =>
436 | 			ctx.context.internalAdapter.updateUser(testUser.id, {
437 | 				name: "Final Update",
438 | 			}),
439 | 		);
440 | 
441 | 		// Should be rounded down to 7199
442 | 		const finalTTL = capturedTTLs.at(-1);
443 | 		expect(finalTTL).toBeLessThanOrEqual(7199);
444 | 		expect(finalTTL).toBeGreaterThanOrEqual(7198); // Allow for test execution time
445 | 	});
446 | 
447 | 	it("should create on secondary storage", async () => {
448 | 		// Create session
449 | 		const now = Date.now();
450 | 		const expiresAt = new Date(now + 60 * 60 * 24 * 7 * 1000);
451 | 		const user = await runWithEndpointContext(ctx, () =>
452 | 			internalAdapter.createUser({
453 | 				name: "test-user",
454 | 				email: "[email protected]",
455 | 			}),
456 | 		);
457 | 		const session = await runWithEndpointContext(ctx, () =>
458 | 			internalAdapter.createSession(user.id),
459 | 		);
460 | 		const storedSessions: { token: string; expiresAt: number }[] = JSON.parse(
461 | 			map.get(`active-sessions-${user.id}`),
462 | 		);
463 | 		const token = session.token;
464 | 		// Check stored sessions
465 | 		expect(storedSessions.length).toBe(1);
466 | 		expect(storedSessions.at(0)?.token).toBe(session.token);
467 | 		// Check expiration time set is the last expiration set
468 | 		const lastExpiration = storedSessions.reduce((prev, curr) =>
469 | 			prev.expiresAt >= curr.expiresAt ? prev : curr,
470 | 		);
471 | 		const actualExp = expirationMap.get(`active-sessions-${user.id}`);
472 | 		const expectedExp = Math.floor(
473 | 			(lastExpiration.expiresAt - Date.now()) / 1000,
474 | 		);
475 | 		// max 1s clock drift between check and set
476 | 		expect(actualExp - expectedExp).toBeLessThanOrEqual(1);
477 | 		expect(actualExp - expectedExp).toBeGreaterThanOrEqual(0);
478 | 
479 | 		const storedSession = safeJSONParse<{
480 | 			session: Session;
481 | 			user: User;
482 | 		}>(map.get(token));
483 | 		expect(storedSession?.user).toMatchObject(user);
484 | 		expect(storedSession?.session).toMatchObject({
485 | 			...session,
486 | 			activeOrganizationId: "1",
487 | 		});
488 | 		const actualTokenExp = expirationMap.get(token);
489 | 		const expectedTokenExp = Math.floor(
490 | 			(expiresAt.getTime() - Date.now()) / 1000,
491 | 		);
492 | 		// max 1s clock drift between check and set
493 | 		expect(actualTokenExp - expectedTokenExp).toBeLessThanOrEqual(1);
494 | 		expect(actualTokenExp - expectedTokenExp).toBeGreaterThanOrEqual(0);
495 | 	});
496 | 
497 | 	it("should delete on secondary storage", async () => {
498 | 		// Create multiple sessions in past and future
499 | 		const now = Date.now();
500 | 		const userId = "test-user";
501 | 		// 10 consecutive days (5 in past, 1 now, 4 in future)
502 | 		for (let i = -5; i < 5; i++) {
503 | 			const expiresIn = i * 60 * 60 * 24 * 1000;
504 | 			const expiresAt = new Date(now + expiresIn);
505 | 			await runWithEndpointContext(ctx, () =>
506 | 				internalAdapter.createSession(
507 | 					userId,
508 | 					undefined,
509 | 					{
510 | 						expiresAt,
511 | 					},
512 | 					true,
513 | 				),
514 | 			);
515 | 			if (i > 0) {
516 | 				const actualExp = expirationMap.get(`active-sessions-${userId}`);
517 | 				const expectedExp = Math.floor(
518 | 					(expiresAt.getTime() - Date.now()) / 1000,
519 | 				);
520 | 				expect(actualExp - expectedExp).toBeLessThanOrEqual(1); // max 1s clock drift between check and set
521 | 				expect(actualExp - expectedExp).toBeGreaterThanOrEqual(0); // max 1s clock drift between check and set
522 | 			} else {
523 | 				expect(expirationMap.get(`active-sessions-${userId}`)).toBeUndefined();
524 | 			}
525 | 		}
526 | 		const storedSessions: { token: string; expiresAt: number }[] = JSON.parse(
527 | 			map.get(`active-sessions-${userId}`),
528 | 		);
529 | 		expect(storedSessions.length).toBe(4);
530 | 		const token = storedSessions.at(-1)?.token;
531 | 		const tokenStored = map.get(token);
532 | 		expect(tokenStored).toBeDefined();
533 | 
534 | 		// Delete session should clean expiresAt and token
535 | 		await runWithEndpointContext(ctx, () =>
536 | 			internalAdapter.deleteSession(token!),
537 | 		);
538 | 		const afterDeleted: { token: string; expiresAt: number }[] = JSON.parse(
539 | 			map.get(`active-sessions-${userId}`),
540 | 		);
541 | 		expect(afterDeleted.length).toBe(3);
542 | 		const removedToken = map.get(token);
543 | 		expect(removedToken).toBeUndefined();
544 | 		// Check expiration time set is the last expiration set
545 | 		const lastExpiration = afterDeleted.reduce((prev, curr) =>
546 | 			prev.expiresAt >= curr.expiresAt ? prev : curr,
547 | 		);
548 | 		const actualExp = expirationMap.get(`active-sessions-${userId}`);
549 | 		const expectedExp = Math.floor(
550 | 			(lastExpiration.expiresAt - Date.now()) / 1000,
551 | 		);
552 | 		expect(actualExp - expectedExp).toBeLessThanOrEqual(1); // max 1s clock drift between check and set
553 | 		expect(actualExp - expectedExp).toBeGreaterThanOrEqual(0); // max 1s clock drift between check and set
554 | 	});
555 | 
556 | 	it("should delete a single account", async () => {
557 | 		const user = await runWithEndpointContext(ctx, () =>
558 | 			internalAdapter.createUser({
559 | 				name: "Account Delete User",
560 | 				email: "[email protected]",
561 | 			}),
562 | 		);
563 | 
564 | 		const account = await runWithEndpointContext(ctx, () =>
565 | 			internalAdapter.createAccount({
566 | 				userId: user.id,
567 | 				providerId: "test-provider",
568 | 				accountId: "test-account-id-1",
569 | 			}),
570 | 		);
571 | 
572 | 		let foundAccount = await internalAdapter.findAccount(account.accountId);
573 | 		expect(foundAccount).toBeDefined();
574 | 
575 | 		await runWithEndpointContext(ctx, () =>
576 | 			internalAdapter.deleteAccount(account.id),
577 | 		);
578 | 
579 | 		foundAccount = await internalAdapter.findAccount(account.accountId);
580 | 		expect(foundAccount).toBeNull();
581 | 	});
582 | 
583 | 	it("should delete multiple accounts for a user", async () => {
584 | 		const user = await runWithEndpointContext(ctx, () =>
585 | 			internalAdapter.createUser({
586 | 				name: "Accounts Delete User",
587 | 				email: "[email protected]",
588 | 			}),
589 | 		);
590 | 
591 | 		await runWithEndpointContext(ctx, () =>
592 | 			internalAdapter.createAccount({
593 | 				userId: user.id,
594 | 				providerId: "test-provider-1",
595 | 				accountId: "test-account-id-2",
596 | 			}),
597 | 		);
598 | 
599 | 		await runWithEndpointContext(ctx, () =>
600 | 			internalAdapter.createAccount({
601 | 				userId: user.id,
602 | 				providerId: "test-provider-2",
603 | 				accountId: "test-account-id-3",
604 | 			}),
605 | 		);
606 | 
607 | 		let accounts = await internalAdapter.findAccounts(user.id);
608 | 		expect(accounts.length).toBe(2);
609 | 
610 | 		await runWithEndpointContext(ctx, () =>
611 | 			internalAdapter.deleteAccounts(user.id),
612 | 		);
613 | 
614 | 		accounts = await internalAdapter.findAccounts(user.id);
615 | 		expect(accounts.length).toBe(0);
616 | 	});
617 | });
618 | 
```
Page 39/67FirstPrevNextLast