SIWE user account not created (separate BA server and client) #2515

Closed
opened 2026-03-13 10:00:27 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @coopbri on GitHub (Dec 13, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Install Better Auth server with SIWE integration in an app:
Server
import { generateRandomString } from "better-auth/crypto";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { siwe } from "better-auth/plugins";
import { verifyMessage, createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

export const auth = betterAuth({
  baseURL: "https://localhost:4000",
  secret: "redacted",
  database: drizzleAdapter(dbClient, {
    provider: "pg",
  }),
  plugins: [
    siwe({
      domain: "localhost:3000",
      emailDomainName: "localhost",
      // tried both
      anonymous: true,
      // anonymous: false,
      getNonce: async () => generateRandomString(32),
      verifyMessage: async ({ message, signature, address }) => {
        try {
          const isValid = await verifyMessage({
            address: address as `0x${string}`,
            message,
            signature: signature as `0x${string}`,
          });
          
          return isValid;
        } catch (error) {
          console.error("SIWE verification failed:", error);
          
          return false;
        }
      },
      ensLookup: async ({ walletAddress }) => {
        try {
          const client = createPublicClient({
            chain: mainnet,
            transport: http(),
          });
          
          const ensName = await client.getEnsName({
            address: walletAddress as `0x${string}`,
          });
          
          const ensAvatar = ensName
            ? await client.getEnsAvatar({
                name: ensName,
              })
            : null;
          
          return {
            name: ensName || walletAddress,
            avatar: ensAvatar || "",
          };
        } catch {
          return {
            name: walletAddress,
            avatar: "",
          };
        }
      },
    }),
  ],
});
  1. Install Better Auth client with SIWE integration in a separate app:
Client
import { createAuthClient } from "better-auth/react";
import { siweClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  baseURL: "https://localhost:4000",
  plugins: [siweClient()],
});
  1. Connect Ethereum wallet (code redacted here for brevity, but can share on request), create nonce and perform SIWE signing verification:
SIWE nonce & sign
const nonce = await authClient.siwe.nonce({
  walletAddress: address,
  chainId: chainId ?? 1,
});

// prepare and sign `SiweMessage` here

const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId,
  // tested with email as well
  // email: ...,
});
  1. Notice SIWE authentication process succeeds but BA does not create a corresponding wallet address nor user account in the database

Current vs. Expected behavior

Current: SIWE succeeds and authenticates the user, but does not create a user account in the BA server.
Expected: SIWE succeeds and authenticates the user while also creating a backend user account in the BA server.

What version of Better Auth are you using?

1.4.6

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#1 SMP PREEMPT_DYNAMIC Mon, 24 Nov 2025 15:21:09 +0000",
    "release": "6.17.9-arch1-1",
    "cpuCount": 8,
    "cpuModel": "11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz",
    "totalMemory": "19.33 GB",
    "freeMemory": "3.34 GB"
  },
  "node": {
    "version": "v25.2.1",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.4"
  },
  "frameworks": null,
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.45.1"
    }
  ],
  "betterAuth": {
    "version": "^1.4.6",
    "config": {
      "baseURL": "https://localhost:4000",
      "secret": "[REDACTED]",
      "trustedOrigins": [
        "https://localhost:3000"
      ],
      "plugins": [
        {
          "name": "siwe",
          "config": {
            "id": "siwe",
            "schema": {
              "walletAddress": {
                "fields": {
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true,
                    "index": true
                  },
                  "address": {
                    "type": "string",
                    "required": true
                  },
                  "chainId": {
                    "type": "number",
                    "required": true
                  },
                  "isPrimary": {
                    "type": "boolean",
                    "defaultValue": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  }
                }
              }
            },
            "endpoints": {}
          }
        }
      ]
    }
  }
}

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

Backend

Auth config (if applicable)

Shared above in repro steps 1 and 2

Additional context

My server is an Elysia server with .mount(auth.handler) per the BA Elysia integration docs, client is TanStack Start.

Originally asked on Discord

Originally created by @coopbri on GitHub (Dec 13, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Install Better Auth server with SIWE integration in an app: <details> <summary>Server</summary> ```ts import { generateRandomString } from "better-auth/crypto"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { siwe } from "better-auth/plugins"; import { verifyMessage, createPublicClient, http } from "viem"; import { mainnet } from "viem/chains"; export const auth = betterAuth({ baseURL: "https://localhost:4000", secret: "redacted", database: drizzleAdapter(dbClient, { provider: "pg", }), plugins: [ siwe({ domain: "localhost:3000", emailDomainName: "localhost", // tried both anonymous: true, // anonymous: false, getNonce: async () => generateRandomString(32), verifyMessage: async ({ message, signature, address }) => { try { const isValid = await verifyMessage({ address: address as `0x${string}`, message, signature: signature as `0x${string}`, }); return isValid; } catch (error) { console.error("SIWE verification failed:", error); return false; } }, ensLookup: async ({ walletAddress }) => { try { const client = createPublicClient({ chain: mainnet, transport: http(), }); const ensName = await client.getEnsName({ address: walletAddress as `0x${string}`, }); const ensAvatar = ensName ? await client.getEnsAvatar({ name: ensName, }) : null; return { name: ensName || walletAddress, avatar: ensAvatar || "", }; } catch { return { name: walletAddress, avatar: "", }; } }, }), ], }); ``` </details> 2. Install Better Auth client with SIWE integration in a separate app: <details> <summary>Client</summary> ```ts import { createAuthClient } from "better-auth/react"; import { siweClient } from "better-auth/client/plugins"; export const authClient = createAuthClient({ baseURL: "https://localhost:4000", plugins: [siweClient()], }); ``` </details> 3. Connect Ethereum wallet (code redacted here for brevity, but can share on request), create nonce and perform SIWE signing verification: <details> <summary>SIWE nonce & sign</summary> ```ts const nonce = await authClient.siwe.nonce({ walletAddress: address, chainId: chainId ?? 1, }); // prepare and sign `SiweMessage` here const { data, error } = await authClient.siwe.verify({ message, signature, walletAddress, chainId, // tested with email as well // email: ..., }); ``` </details> 4. Notice SIWE authentication process succeeds but BA does not create a corresponding wallet address nor user account in the database ### Current vs. Expected behavior **Current:** SIWE succeeds and authenticates the user, but does not create a user account in the BA server. **Expected:** SIWE succeeds and authenticates the user while also creating a backend user account in the BA server. ### What version of Better Auth are you using? 1.4.6 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#1 SMP PREEMPT_DYNAMIC Mon, 24 Nov 2025 15:21:09 +0000", "release": "6.17.9-arch1-1", "cpuCount": 8, "cpuModel": "11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz", "totalMemory": "19.33 GB", "freeMemory": "3.34 GB" }, "node": { "version": "v25.2.1", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.4" }, "frameworks": null, "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.45.1" } ], "betterAuth": { "version": "^1.4.6", "config": { "baseURL": "https://localhost:4000", "secret": "[REDACTED]", "trustedOrigins": [ "https://localhost:3000" ], "plugins": [ { "name": "siwe", "config": { "id": "siwe", "schema": { "walletAddress": { "fields": { "userId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true, "index": true }, "address": { "type": "string", "required": true }, "chainId": { "type": "number", "required": true }, "isPrimary": { "type": "boolean", "defaultValue": false }, "createdAt": { "type": "date", "required": true } } } }, "endpoints": {} } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) Shared above in repro steps 1 and 2 ### Additional context My server is an Elysia server with `.mount(auth.handler)` per the [BA Elysia integration docs](https://www.better-auth.com/docs/integrations/elysia), client is TanStack Start. Originally asked on [Discord](https://discord.com/channels/1288403910284935179/1448897881531744436/1448897881531744436)
GiteaMirror added the elysia label 2026-03-13 10:00:27 -05:00
Author
Owner

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

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

The SIWE plugin in Better Auth is designed to create a user account and wallet address in your database when a new wallet signs in, but this only works if your Drizzle schema and adapter configuration are correct. If SIWE authentication succeeds but no user or wallet is created, it's almost always a schema or adapter integration issue.

Here's what you should check:

  1. Schema completeness: Your Drizzle schema must include all required tables (user, walletAddress, account, session, verification) and their relations. The SIWE plugin adds a walletAddress model—your schema and migrations must include this table and its fields, or user creation will silently fail. If you haven't already, regenerate your schema using npx @better-auth/cli@latest generate and apply the migration to your database. source

  2. Pass the merged schema: Pass the merged schema (including SIWE additions and all relations) to both your Drizzle instance and the drizzleAdapter. For example:

    import * as schema from './schema'
    const db = drizzle(connection, { schema })
    export const auth = betterAuth({
      database: drizzleAdapter(db, {
        provider: 'pg',
        schema,
      }),
      // ...
    })
    

    source

  3. Model names: If you use custom table names, set the modelName for each entity in your Better Auth config to match your actual table names. Otherwise, the adapter may not find the tables. source

  4. Timestamps: All timestamp fields (like createdAt, updatedAt, emailVerified) in your schema must default to real JavaScript Date objects. Use .$defaultFn(() => new Date()) for these fields. If these are null, strings, or booleans, user creation will fail with errors like toISOString is not a function. source

  5. Debugging: Enable debugLogs: true in your drizzleAdapter config and check your server logs for errors during SIWE verification and user creation. The adapter throws explicit errors if a model or field is missing from the schema, but these may only appear in logs. source

  6. Relations: If using Drizzle's experimental joins, ensure you pass both tables and relations to the Drizzle instance and the adapter. Missing relations can cause silent failures. source

If you follow these steps and regenerate your schema/migrations, SIWE user creation should work as expected. If you still have issues, try logging the payloads and errors in your adapter's create/find methods to catch any silent failures.

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

@dosubot[bot] commented on GitHub (Dec 13, 2025): <!-- Greeting --> Hi @coopbri! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The SIWE plugin in Better Auth is designed to create a user account and wallet address in your database when a new wallet signs in, but this only works if your Drizzle schema and adapter configuration are correct. If SIWE authentication succeeds but no user or wallet is created, it's almost always a schema or adapter integration issue. Here's what you should check: 1. **Schema completeness:** Your Drizzle schema must include all required tables (user, walletAddress, account, session, verification) and their relations. The SIWE plugin adds a `walletAddress` model—your schema and migrations must include this table and its fields, or user creation will silently fail. If you haven't already, regenerate your schema using `npx @better-auth/cli@latest generate` and apply the migration to your database. [source](https://github.com/better-auth/better-auth/pull/6117) 2. **Pass the merged schema:** Pass the merged schema (including SIWE additions and all relations) to both your Drizzle instance and the `drizzleAdapter`. For example: ```ts import * as schema from './schema' const db = drizzle(connection, { schema }) export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', schema, }), // ... }) ``` [source](https://github.com/better-auth/better-auth/issues/6200) 3. **Model names:** If you use custom table names, set the `modelName` for each entity in your Better Auth config to match your actual table names. Otherwise, the adapter may not find the tables. [source](https://github.com/better-auth/better-auth/issues/6200) 4. **Timestamps:** All timestamp fields (like `createdAt`, `updatedAt`, `emailVerified`) in your schema must default to real JavaScript `Date` objects. Use `.$defaultFn(() => new Date())` for these fields. If these are `null`, strings, or booleans, user creation will fail with errors like `toISOString is not a function`. [source](https://github.com/better-auth/better-auth/issues/4434#issuecomment-3259022893) 5. **Debugging:** Enable `debugLogs: true` in your drizzleAdapter config and check your server logs for errors during SIWE verification and user creation. The adapter throws explicit errors if a model or field is missing from the schema, but these may only appear in logs. [source](https://github.com/better-auth/better-auth/pull/6168) 6. **Relations:** If using Drizzle's experimental joins, ensure you pass both tables and relations to the Drizzle instance and the adapter. Missing relations can cause silent failures. [source](https://github.com/better-auth/better-auth/issues/6200) If you follow these steps and regenerate your schema/migrations, SIWE user creation should work as expected. If you still have issues, try logging the payloads and errors in your adapter's create/find methods to catch any silent failures. <!-- 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/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/13a0a9ae-2ce4-417d-bbc3-8ecc3e5a64e4?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%20SIWE%20plugin%20in%20Better%20Auth%20is%20designed%20to%20create%20a%20user%20account%20and%20wallet%20address%20in%20your%20database%20when%20a%20new%20wallet%20signs%20in%2C%20but%20this%20only%20works%20if%20your%20Drizzle%20schema%20and%20adapter%20configuration%20are%20correct.%20If%20SIWE%20authentication%20succeeds%20but%20no%20user%20or%20wallet%20is%20created%2C%20it%27s%20almost%20always%20a%20schema%20or%20adapter%20integration%20issue.%0A%0AHere%27s%20what%20you%20should%20check%3A%0A%0A1.%20%2A%2ASchema%20completeness%3A%2A%2A%20Your%20Drizzle%20schema%20must%20include%20all%20required%20tables%20%28user%2C%20walletAddress%2C%20account%2C%20session%2C%20verification%29%20and%20their%20relations.%20The%20SIWE%20plugin%20adds%20a%20%60walletAddress%60%20model%E2%80%94your%20schema%20and%20migrations%20must%20include%20this%20table%20and%20its%20fields%2C%20or%20user%20creation%20will%20silently%20fail.%20If%20you%20haven%27t%20already%2C%20regenerate%20your%20schema%20using%20%60npx%20%40better-auth/cli%40latest%20generate%60%20and%20apply%20the%20migration%20to%20your%20database.%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/6117%29%0A%0A2.%20%2A%2APass%20the%20merged%20schema%3A%2A%2A%20Pass%20the%20merged%20schema%20%28including%20SIWE%20additions%20and%20all%20relations%29%20to%20both%20your%20Drizzle%20instance%20and%20the%20%60drizzleAdapter%60.%20For%20example%3A%0A%20%20%20%60%60%60ts%0A%20%20%20import%20%2A%20as%20schema%20from%20%27./schema%27%0A%20%20%20const%20db%20%3D%20drizzle%28connection%2C%20%7B%20schema%20%7D%29%0A%20%20%20export%20const%20auth%20%3D%20betterAuth%28%7B%0A%20%20%20%20%20database%3A%20drizzleAdapter%28db%2C%20%7B%0A%20%20%20%20%20%20%20provider%3A%20%27pg%27%2C%0A%20%20%20%20%20%20%20schema%2C%0A%20%20%20%20%20%7D%29%2C%0A%20%20%20%20%20//%20...%0A%20%20%20%7D%29%0A%20%20%20%60%60%60%0A%20%20%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/6200%29%0A%0A3.%20%2A%2AModel%20names%3A%2A%2A%20If%20you%20use%20custom%20table%20names%2C%20set%20the%20%60modelName%60%20for%20each%20entity%20in%20your%20Better%20Auth%20config%20to%20match%20your%20actual%20table%20names.%20Otherwise%2C%20the%20adapter%20may%20not%20find%20the%20tables.%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/6200%29%0A%0A4.%20%2A%2ATimestamps%3A%2A%2A%20All%20timestamp%20fields%20%28like%20%60createdAt%60%2C%20%60updatedAt%60%2C%20%60emailVerified%60%29%20in%20your%20schema%20must%20default%20to%20real%20JavaScript%20%60Date%60%20objects.%20Use%20%60.%24defaultFn%28%28%29%20%3D%3E%20new%20Date%28%29%29%60%20for%20these%20fields.%20If%20these%20are%20%60null%60%2C%20strings%2C%20or%20booleans%2C%20user%20creation%20will%20fail%20with%20errors%20like%20%60toISOString%20is%20not%20a%20function%60.%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/4434%23issuecomment-3259022893%29%0A%0A5.%20%2A%2ADebugging%3A%2A%2A%20Enable%20%60debugLogs%3A%20true%60%20in%20your%20drizzleAdapter%20config%20and%20check%20your%20server%20logs%20for%20errors%20during%20SIWE%20verification%20and%20user%20creation.%20The%20adapter%20throws%20explicit%20errors%20if%20a%20model%20or%20field%20is%20missing%20from%20the%20schema%2C%20but%20these%20may%20only%20appear%20in%20logs.%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/6168%29%0A%0A6.%20%2A%2ARelations%3A%2A%2A%20If%20using%20Drizzle%27s%20experimental%20joins%2C%20ensure%20you%20pass%20both%20tables%20and%20relations%20to%20the%20Drizzle%20instance%20and%20the%20adapter.%20Missing%20relations%20can%20cause%20silent%20failures.%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/6200%29%0A%0AIf%20you%20follow%20these%20steps%20and%20regenerate%20your%20schema/migrations%2C%20SIWE%20user%20creation%20should%20work%20as%20expected.%20If%20you%20still%20have%20issues%2C%20try%20logging%20the%20payloads%20and%20errors%20in%20your%20adapter%27s%20create/find%20methods%20to%20catch%20any%20silent%20failures.)&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/6725)
Author
Owner

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

Easy fix! Red herring, I just had to use a database pool client.

@coopbri commented on GitHub (Dec 13, 2025): Easy fix! Red herring, I just had to use a database pool client.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2515