[GH-ISSUE #6735] Send 2fa (two factor) otp not working on tanstack start #19236

Closed
opened 2026-04-15 18:05:14 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @devnomic on GitHub (Dec 13, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6735

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Send 2FA OTP not working on tanstack start, but other auth functions works fine. I suspect the auth.handler(request) has issue.

My better auth config.

export const auth = betterAuth({
  experimental: { joins: true },
  database: dialect,
  advanced: {
    database: {
      generateId: 'serial', // "serial" for auto-incrementing numeric IDs
    },
  },
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async ({ user, url }) => {
      void sendEmail({
        to: user.email,
        subject: 'Reset your password',
        react: getResetPasswordEmailTemplate({ user, url }),
      })
    },
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      sendEmail({
        to: user.email,
        subject: 'Verify your email address',
        react: getVerificationEmailTemplate({ user, url }),
      })
    },
    autoSignInAfterVerification: true,
    sendOnSignUp: true,
  },
  user: {
    modelName: 'users',
  },
  session: {
    modelName: 'sessions',
  },
  account: {
    modelName: 'accounts',
  },
  verification: {
    modelName: 'verifications',
  },
  plugins: [
    organization({
      schema: {
        organization: {
          modelName: 'organizations',
        },
        member: {
          modelName: 'members',
        },
        invitation: {
          modelName: 'invitations',
        },
      },
      async sendInvitationEmail(data: any) {
        // TODO: Implement email sending
        console.log('Invitation email:', data)
      },
    }),
    twoFactor({
      otpOptions: {
        async sendOTP({ user, otp }) {
          void sendEmail({
            to: user.email,
            subject: 'Your One Time Password',
            react: getOtpEmailTemplate({ user, otp }),
          })
        },
      },
      schema: {
        twoFactor: {
          modelName: 'twoFactors',
        },
        user: {
          modelName: 'users',
        },
      },
    }),
    tanstackStartCookies(),
  ],
})

API routes set

import { auth } from '@/lib/auth'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/auth/$')({
  server: {
    handlers: {
      GET: async ({ request }: { request: Request }) => {
        return await auth.handler(request)
      },
      POST: async ({ request }: { request: Request }) => {
        return await auth.handler(request)
      },
    },
  },
})

Current vs. Expected behavior

Route not found when sending OTP.

Image

What version of Better Auth are you using?

1.4.6

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:55 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6031",
    "release": "24.6.0",
    "cpuCount": 14,
    "cpuModel": "Apple M3 Max",
    "totalMemory": "36.00 GB",
    "freeMemory": "1.25 GB"
  },
  "node": {
    "version": "v24.5.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.5.2"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "^19.2.3"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "kysely",
      "version": "^0.28.8"
    }
  ],
  "betterAuth": {
    "version": "^1.4.6",
    "config": {
      "experimental": {
        "joins": true
      },
      "database": {},
      "advanced": {
        "database": {
          "generateId": "serial"
        }
      },
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": true
      },
      "emailVerification": {
        "autoSignInAfterVerification": true,
        "sendOnSignUp": true
      },
      "user": {
        "modelName": "users"
      },
      "session": {
        "modelName": "sessions"
      },
      "account": {
        "modelName": "accounts"
      },
      "verification": {
        "modelName": "verifications"
      },
      "plugins": [
        {
          "name": "organization",
          "config": {
            "id": "organization",
            "endpoints": {},
            "schema": {
              "organization": {
                "modelName": "organizations",
                "fields": {
                  "name": {
                    "type": "string",
                    "required": true,
                    "sortable": true
                  },
                  "slug": {
                    "type": "string",
                    "required": true,
                    "unique": true,
                    "sortable": true
                  },
                  "logo": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  }
                }
              },
              "member": {
                "modelName": "members",
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "index": true
                  },
                  "role": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "member"
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  }
                }
              },
              "invitation": {
                "modelName": "invitations",
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    },
                    "index": true
                  },
                  "email": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "index": true
                  },
                  "role": {
                    "type": "string",
                    "required": false,
                    "sortable": true
                  },
                  "status": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "pending"
                  },
                  "expiresAt": {
                    "type": "date",
                    "required": true
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "inviterId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true
                  }
                }
              },
              "session": {
                "fields": {
                  "activeOrganizationId": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$Infer": {
              "Organization": {},
              "Invitation": {},
              "Member": {},
              "Team": {},
              "TeamMember": {},
              "ActiveOrganization": {}
            },
            "$ERROR_CODES": {
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": "You are not allowed to create a new organization",
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": "You have reached the maximum number of organizations",
              "ORGANIZATION_ALREADY_EXISTS": "Organization already exists",
              "ORGANIZATION_SLUG_ALREADY_TAKEN": "Organization slug already taken",
              "ORGANIZATION_NOT_FOUND": "Organization not found",
              "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "User is not a member of the organization",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": "You are not allowed to update this organization",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": "You are not allowed to delete this organization",
              "NO_ACTIVE_ORGANIZATION": "No active organization",
              "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": "User is already a member of this organization",
              "MEMBER_NOT_FOUND": "Member not found",
              "ROLE_NOT_FOUND": "Role not found",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": "You are not allowed to create a new team",
              "TEAM_ALREADY_EXISTS": "Team already exists",
              "TEAM_NOT_FOUND": "Team not found",
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": "You cannot leave the organization as the only owner",
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": "You cannot leave the organization without an owner",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": "You are not allowed to delete this member",
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": "You are not allowed to invite users to this organization",
              "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "User is already invited to this organization",
              "INVITATION_NOT_FOUND": "Invitation not found",
              "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": "You are not the recipient of the invitation",
              "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": "Email verification required before accepting or rejecting invitation",
              "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": "You are not allowed to cancel this invitation",
              "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": "Inviter is no longer a member of the organization",
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": "You are not allowed to invite a user with this role",
              "FAILED_TO_RETRIEVE_INVITATION": "Failed to retrieve invitation",
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": "You have reached the maximum number of teams",
              "UNABLE_TO_REMOVE_LAST_TEAM": "Unable to remove last team",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": "You are not allowed to update this member",
              "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": "Organization membership limit reached",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to create teams in this organization",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to delete teams in this organization",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": "You are not allowed to update this team",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": "You are not allowed to delete this team",
              "INVITATION_LIMIT_REACHED": "Invitation limit reached",
              "TEAM_MEMBER_LIMIT_REACHED": "Team member limit reached",
              "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": "User is not a member of the team",
              "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": "You are not allowed to list the members of this team",
              "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": "You do not have an active team",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": "You are not allowed to create a new member",
              "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": "You are not allowed to remove a team member",
              "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": "You are not allowed to access this organization as an owner",
              "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": "You are not a member of this organization",
              "MISSING_AC_INSTANCE": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information",
              "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": "You must be in an organization to create a role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": "You are not allowed to create a role",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": "You are not allowed to update a role",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": "You are not allowed to delete a role",
              "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": "You are not allowed to read a role",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": "You are not allowed to list a role",
              "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": "You are not allowed to get a role",
              "TOO_MANY_ROLES": "This organization has too many roles",
              "INVALID_RESOURCE": "The provided permission includes an invalid resource",
              "ROLE_NAME_IS_ALREADY_TAKEN": "That role name is already taken",
              "CANNOT_DELETE_A_PRE_DEFINED_ROLE": "Cannot delete a pre-defined role"
            },
            "options": {
              "schema": {
                "organization": {
                  "modelName": "organizations"
                },
                "member": {
                  "modelName": "members"
                },
                "invitation": {
                  "modelName": "invitations"
                }
              }
            }
          }
        },
        {
          "name": "two-factor",
          "config": {
            "id": "two-factor",
            "endpoints": {},
            "options": {
              "otpOptions": {},
              "schema": {
                "twoFactor": {
                  "modelName": "twoFactors"
                },
                "user": {
                  "modelName": "users"
                }
              }
            },
            "hooks": {
              "after": [
                {}
              ]
            },
            "schema": {
              "user": {
                "fields": {
                  "twoFactorEnabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false,
                    "input": false
                  }
                },
                "modelName": "users"
              },
              "twoFactor": {
                "fields": {
                  "secret": {
                    "type": "string",
                    "required": true,
                    "returned": false,
                    "index": true
                  },
                  "backupCodes": {
                    "type": "string",
                    "required": true,
                    "returned": false
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "returned": false,
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "index": true
                  }
                },
                "modelName": "twoFactors"
              }
            },
            "rateLimit": [
              {
                "window": 10,
                "max": 3
              }
            ],
            "$ERROR_CODES": {
              "OTP_NOT_ENABLED": "OTP not enabled",
              "OTP_HAS_EXPIRED": "OTP has expired",
              "TOTP_NOT_ENABLED": "TOTP not enabled",
              "TWO_FACTOR_NOT_ENABLED": "Two factor isn't enabled",
              "BACKUP_CODES_NOT_ENABLED": "Backup codes aren't enabled",
              "INVALID_BACKUP_CODE": "Invalid backup code",
              "INVALID_CODE": "Invalid code",
              "TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE": "Too many attempts. Please request a new code.",
              "INVALID_TWO_FACTOR_COOKIE": "Invalid two factor cookie"
            }
          }
        },
        {
          "name": "tanstack-start-cookies",
          "config": {
            "id": "tanstack-start-cookies",
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        }
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

No response

Originally created by @devnomic on GitHub (Dec 13, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6735 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Send 2FA OTP not working on tanstack start, but other auth functions works fine. I suspect the `auth.handler(request)` has issue. My better auth config. ``` export const auth = betterAuth({ experimental: { joins: true }, database: dialect, advanced: { database: { generateId: 'serial', // "serial" for auto-incrementing numeric IDs }, }, emailAndPassword: { enabled: true, requireEmailVerification: true, sendResetPassword: async ({ user, url }) => { void sendEmail({ to: user.email, subject: 'Reset your password', react: getResetPasswordEmailTemplate({ user, url }), }) }, }, emailVerification: { sendVerificationEmail: async ({ user, url }) => { sendEmail({ to: user.email, subject: 'Verify your email address', react: getVerificationEmailTemplate({ user, url }), }) }, autoSignInAfterVerification: true, sendOnSignUp: true, }, user: { modelName: 'users', }, session: { modelName: 'sessions', }, account: { modelName: 'accounts', }, verification: { modelName: 'verifications', }, plugins: [ organization({ schema: { organization: { modelName: 'organizations', }, member: { modelName: 'members', }, invitation: { modelName: 'invitations', }, }, async sendInvitationEmail(data: any) { // TODO: Implement email sending console.log('Invitation email:', data) }, }), twoFactor({ otpOptions: { async sendOTP({ user, otp }) { void sendEmail({ to: user.email, subject: 'Your One Time Password', react: getOtpEmailTemplate({ user, otp }), }) }, }, schema: { twoFactor: { modelName: 'twoFactors', }, user: { modelName: 'users', }, }, }), tanstackStartCookies(), ], }) ``` API routes set ``` import { auth } from '@/lib/auth' import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/api/auth/$')({ server: { handlers: { GET: async ({ request }: { request: Request }) => { return await auth.handler(request) }, POST: async ({ request }: { request: Request }) => { return await auth.handler(request) }, }, }, }) ``` ### Current vs. Expected behavior Route not found when sending OTP. <img width="788" height="220" alt="Image" src="https://github.com/user-attachments/assets/27cb2e14-a8a1-4b31-920d-0589b4dc32b2" /> ### What version of Better Auth are you using? 1.4.6 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:55 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6031", "release": "24.6.0", "cpuCount": 14, "cpuModel": "Apple M3 Max", "totalMemory": "36.00 GB", "freeMemory": "1.25 GB" }, "node": { "version": "v24.5.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.5.2" }, "frameworks": [ { "name": "react", "version": "^19.2.3" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "kysely", "version": "^0.28.8" } ], "betterAuth": { "version": "^1.4.6", "config": { "experimental": { "joins": true }, "database": {}, "advanced": { "database": { "generateId": "serial" } }, "emailAndPassword": { "enabled": true, "requireEmailVerification": true }, "emailVerification": { "autoSignInAfterVerification": true, "sendOnSignUp": true }, "user": { "modelName": "users" }, "session": { "modelName": "sessions" }, "account": { "modelName": "accounts" }, "verification": { "modelName": "verifications" }, "plugins": [ { "name": "organization", "config": { "id": "organization", "endpoints": {}, "schema": { "organization": { "modelName": "organizations", "fields": { "name": { "type": "string", "required": true, "sortable": true }, "slug": { "type": "string", "required": true, "unique": true, "sortable": true }, "logo": { "type": "string", "required": false }, "createdAt": { "type": "date", "required": true }, "metadata": { "type": "string", "required": false } } }, "member": { "modelName": "members", "fields": { "organizationId": { "type": "string", "required": true, "references": { "model": "organization", "field": "id" }, "index": true }, "userId": { "type": "string", "required": true, "references": { "model": "user", "field": "id" }, "index": true }, "role": { "type": "string", "required": true, "sortable": true, "defaultValue": "member" }, "createdAt": { "type": "date", "required": true } } }, "invitation": { "modelName": "invitations", "fields": { "organizationId": { "type": "string", "required": true, "references": { "model": "organization", "field": "id" }, "index": true }, "email": { "type": "string", "required": true, "sortable": true, "index": true }, "role": { "type": "string", "required": false, "sortable": true }, "status": { "type": "string", "required": true, "sortable": true, "defaultValue": "pending" }, "expiresAt": { "type": "date", "required": true }, "createdAt": { "type": "date", "required": true }, "inviterId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true } } }, "session": { "fields": { "activeOrganizationId": { "type": "string", "required": false } } } }, "$Infer": { "Organization": {}, "Invitation": {}, "Member": {}, "Team": {}, "TeamMember": {}, "ActiveOrganization": {} }, "$ERROR_CODES": { "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": "You are not allowed to create a new organization", "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": "You have reached the maximum number of organizations", "ORGANIZATION_ALREADY_EXISTS": "Organization already exists", "ORGANIZATION_SLUG_ALREADY_TAKEN": "Organization slug already taken", "ORGANIZATION_NOT_FOUND": "Organization not found", "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "User is not a member of the organization", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": "You are not allowed to update this organization", "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": "You are not allowed to delete this organization", "NO_ACTIVE_ORGANIZATION": "No active organization", "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": "User is already a member of this organization", "MEMBER_NOT_FOUND": "Member not found", "ROLE_NOT_FOUND": "Role not found", "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": "You are not allowed to create a new team", "TEAM_ALREADY_EXISTS": "Team already exists", "TEAM_NOT_FOUND": "Team not found", "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": "You cannot leave the organization as the only owner", "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": "You cannot leave the organization without an owner", "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": "You are not allowed to delete this member", "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": "You are not allowed to invite users to this organization", "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "User is already invited to this organization", "INVITATION_NOT_FOUND": "Invitation not found", "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": "You are not the recipient of the invitation", "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": "Email verification required before accepting or rejecting invitation", "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": "You are not allowed to cancel this invitation", "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": "Inviter is no longer a member of the organization", "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": "You are not allowed to invite a user with this role", "FAILED_TO_RETRIEVE_INVITATION": "Failed to retrieve invitation", "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": "You have reached the maximum number of teams", "UNABLE_TO_REMOVE_LAST_TEAM": "Unable to remove last team", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": "You are not allowed to update this member", "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": "Organization membership limit reached", "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to create teams in this organization", "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to delete teams in this organization", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": "You are not allowed to update this team", "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": "You are not allowed to delete this team", "INVITATION_LIMIT_REACHED": "Invitation limit reached", "TEAM_MEMBER_LIMIT_REACHED": "Team member limit reached", "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": "User is not a member of the team", "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": "You are not allowed to list the members of this team", "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": "You do not have an active team", "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": "You are not allowed to create a new member", "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": "You are not allowed to remove a team member", "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": "You are not allowed to access this organization as an owner", "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": "You are not a member of this organization", "MISSING_AC_INSTANCE": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information", "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": "You must be in an organization to create a role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": "You are not allowed to create a role", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": "You are not allowed to update a role", "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": "You are not allowed to delete a role", "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": "You are not allowed to read a role", "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": "You are not allowed to list a role", "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": "You are not allowed to get a role", "TOO_MANY_ROLES": "This organization has too many roles", "INVALID_RESOURCE": "The provided permission includes an invalid resource", "ROLE_NAME_IS_ALREADY_TAKEN": "That role name is already taken", "CANNOT_DELETE_A_PRE_DEFINED_ROLE": "Cannot delete a pre-defined role" }, "options": { "schema": { "organization": { "modelName": "organizations" }, "member": { "modelName": "members" }, "invitation": { "modelName": "invitations" } } } } }, { "name": "two-factor", "config": { "id": "two-factor", "endpoints": {}, "options": { "otpOptions": {}, "schema": { "twoFactor": { "modelName": "twoFactors" }, "user": { "modelName": "users" } } }, "hooks": { "after": [ {} ] }, "schema": { "user": { "fields": { "twoFactorEnabled": { "type": "boolean", "required": false, "defaultValue": false, "input": false } }, "modelName": "users" }, "twoFactor": { "fields": { "secret": { "type": "string", "required": true, "returned": false, "index": true }, "backupCodes": { "type": "string", "required": true, "returned": false }, "userId": { "type": "string", "required": true, "returned": false, "references": { "model": "user", "field": "id" }, "index": true } }, "modelName": "twoFactors" } }, "rateLimit": [ { "window": 10, "max": 3 } ], "$ERROR_CODES": { "OTP_NOT_ENABLED": "OTP not enabled", "OTP_HAS_EXPIRED": "OTP has expired", "TOTP_NOT_ENABLED": "TOTP not enabled", "TWO_FACTOR_NOT_ENABLED": "Two factor isn't enabled", "BACKUP_CODES_NOT_ENABLED": "Backup codes aren't enabled", "INVALID_BACKUP_CODE": "Invalid backup code", "INVALID_CODE": "Invalid code", "TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE": "Too many attempts. Please request a new code.", "INVALID_TWO_FACTOR_COOKIE": "Invalid two factor cookie" } } }, { "name": "tanstack-start-cookies", "config": { "id": "tanstack-start-cookies", "hooks": { "after": [ {} ] } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-15 18:05:15 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Dec 13, 2025):

Hi @devnomic! I'm Dosu and I’m helping the better-auth team.

The /api/auth/two-factor/send-otp endpoint only supports POST requests, not GET. That's why you're seeing a "Cannot GET" error when trying to send the OTP with a GET request. Your TanStack Start route config is correct—just make sure to trigger the OTP by sending a POST request to /api/auth/two-factor/send-otp.

For example, using curl:

curl -X POST https://your-domain.com/api/auth/two-factor/send-otp \
  -H "Content-Type: application/json" \
  --cookie "your-session-cookie"

Or with fetch in JavaScript:

await fetch('/api/auth/two-factor/send-otp', { method: 'POST', credentials: 'include' });

If you switch to POST and still have issues, double-check your sendOTP config and session state. The endpoint is registered by default and doesn't require extra setup beyond what you've done.
Reference: Better Auth codebase

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3649667749 --> @dosubot[bot] commented on GitHub (Dec 13, 2025): <!-- Greeting --> Hi @devnomic! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The /api/auth/two-factor/send-otp endpoint only supports POST requests, not GET. That's why you're seeing a "Cannot GET" error when trying to send the OTP with a GET request. Your TanStack Start route config is correct—just make sure to trigger the OTP by sending a POST request to /api/auth/two-factor/send-otp. For example, using curl: ```bash curl -X POST https://your-domain.com/api/auth/two-factor/send-otp \ -H "Content-Type: application/json" \ --cookie "your-session-cookie" ``` Or with fetch in JavaScript: ```js await fetch('/api/auth/two-factor/send-otp', { method: 'POST', credentials: 'include' }); ``` If you switch to POST and still have issues, double-check your sendOTP config and session state. The endpoint is registered by default and doesn't require extra setup beyond what you've done. [Reference: Better Auth codebase](https://github.com/better-auth/better-auth/blob/3d849d8dbb7fb2673b50b3f9e12755d7ffbefa23/packages/better-auth/src/plugins/two-factor/otp/index.ts#L158-L406) <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/d9725f66-2046-4a82-b46b-b898ee7baef0?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20/api/auth/two-factor/send-otp%20endpoint%20only%20supports%20POST%20requests%2C%20not%20GET.%20That%27s%20why%20you%27re%20seeing%20a%20%22Cannot%20GET%22%20error%20when%20trying%20to%20send%20the%20OTP%20with%20a%20GET%20request.%20Your%20TanStack%20Start%20route%20config%20is%20correct%E2%80%94just%20make%20sure%20to%20trigger%20the%20OTP%20by%20sending%20a%20POST%20request%20to%20/api/auth/two-factor/send-otp.%0A%0AFor%20example%2C%20using%20curl%3A%0A%0A%60%60%60bash%0Acurl%20-X%20POST%20https%3A//your-domain.com/api/auth/two-factor/send-otp%20%5C%0A%20%20-H%20%22Content-Type%3A%20application/json%22%20%5C%0A%20%20--cookie%20%22your-session-cookie%22%0A%60%60%60%0A%0AOr%20with%20fetch%20in%20JavaScript%3A%0A%0A%60%60%60js%0Aawait%20fetch%28%27/api/auth/two-factor/send-otp%27%2C%20%7B%20method%3A%20%27POST%27%2C%20credentials%3A%20%27include%27%20%7D%29%3B%0A%60%60%60%0A%0AIf%20you%20switch%20to%20POST%20and%20still%20have%20issues%2C%20double-check%20your%20sendOTP%20config%20and%20session%20state.%20The%20endpoint%20is%20registered%20by%20default%20and%20doesn%27t%20require%20extra%20setup%20beyond%20what%20you%27ve%20done.%20%20%0A%5BReference%3A%20Better%20Auth%20codebase%5D%28https%3A//github.com/better-auth/better-auth/blob/3d849d8dbb7fb2673b50b3f9e12755d7ffbefa23/packages/better-auth/src/plugins/two-factor/otp/index.ts%23L158-L406%29)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6735)
Author
Owner

@devnomic commented on GitHub (Dec 13, 2025):

Sorry it was my mistake. Working as expected.

<!-- gh-comment-id:3649670592 --> @devnomic commented on GitHub (Dec 13, 2025): Sorry it was my mistake. Working as expected.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19236