401 error in createUser after upgrading to v1.2.3 #822

Closed
opened 2026-03-13 08:05:45 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @awbx on GitHub (Mar 11, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

APIError[401] when creating users via seed function in v1.2.3

Description

When trying to seed users via the API endpoint, the auth.api.createUser function throws an APIError[401] (Unauthorized) in version 1.2.3. This functionality was working correctly in version 1.1.21.

Environment

  • Current version: 1.2.3
  • Previously working version: 1.1.21
  • Using Hono

Steps to Reproduce

  1. Make a POST request to the /seed endpoint
  2. The endpoint calls the userRepo.seed function
  3. Inside this function, it attempts to create users with auth.api.createUser
  4. The request includes headers passed from the original request

Code

// User seeding function
async function seed(ctx: RepoContext, headers: Headers) {
  for (const user of users) {
    const existingUser = await ctx.db
      .selectFrom("users as u")
      .where("email", "=", user.email)
      .executeTakeFirst();
    if (existingUser) continue;
    await auth.api.createUser({
      body: {
        email: user.email,
        name: `${user.firstName} ${user.lastName}`,
        role: user.role,
        password: user.password,
        data: { service: [] },
      },
      headers,
    });
  }
  return { success: true };
}

// Route definition
userRouter.post(
  "/seed",
  describeRoute({
    summary: "Seed users",
  }),
  protectedRoute(),
  async (c) => {
    return c.json(await userRepo.seed(repoContext(c, true), c.req.raw.headers));
  },
);

Current vs. Expected behavior

Expected Behavior

Users should be created successfully via the seed function when authenticated.

Actual Behavior

Receiving an APIError[401] (Unauthorized) when attempting to create users.

What version of Better Auth are you using?

1.2.3

Provide environment information

## Environment
- Version: v1.2.3 (previously working on v1.1.21)
- Framework: Hono
- OS: MacOS, M1
- Browser: Firefox
- Authentication: Using session-based auth

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

Backend

Auth config (if applicable)

export const auth = betterAuth({
  appName: "ABS all in one Platform",
  baseURL: env.BETTER_AUTH_URL,
  secret: env.BETTER_AUTH_SECRET,
  plugins: [
    openAPI(),
    adminPlugin({ defaultRole: "REQUESTER", adminRole: ["ADMIN"] }),
  ],
  database: {
    db,
    type: "postgres",
    casing: "camel",
  },
  secondaryStorage: {
    async set(key, value, ttl) {
      if (typeof ttl === "undefined") return await redisClient.set(key, value);
      return await redisClient.set(key, value, "EX", ttl);
    },
    async get(key) {
      return await redisClient.get(key);
    },
    async delete(key) {
      await redisClient.del(key);
    },
  },
  rateLimit: {
    enabled: true,
    storage: "secondary-storage",
    max: 2,
    window: 60,
    modelName: "rate-limits",
  },
  advanced: {
    useSecureCookies: isProduction(),
  },

  emailVerification: {
    sendOnSignUp: true,
    async sendVerificationEmail(data) {
      console.log("sendVerificationEmail user", data.user);
      console.log("sendVerificationEmail token", data.token);
      console.log("sendVerificationEmail url", data.url);
    },
  },
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    async sendResetPassword(data) {
      console.log("sendResetPassword user", data.user);
      console.log("sendResetPassword token", data.token);
      console.log("sendResetPassword url", data.url);
    },

    // sendResetPassword(data, request) {},
  },
  user: {
    modelName: "users",
    additionalFields: {
      role: {
        type: "string",
        validator: {
          input: z.nativeEnum(UserRole),
        },
        defaultValue: "CLIENT",
        input: false,
      },
      services: {
        type: "string[]",
        validator: {
          input: z.enum(serviceTypes),
        },
        required: true,
        input: false,
        defaultValue: [],
      },
    },
  },
  account: {
    modelName: "accounts",
    accountLinking: {
      enabled: true,
      trustedProviders: ["microsoft"],
    },
  },
  session: {
    modelName: "sessions",
  },
  verification: {
    modelName: "verifications",
  },
  hooks: {
    before: createAuthMiddleware(async (ctx) => {
      if (ctx.path !== "/sign-up/email") {
        return;
      }
      const users = await db
        .selectFrom("users")
        .where("banned", "=", false)
        .where("role", "=", "ADMIN")
        .select((eb) => [
          eb.fn.coalesce(eb.fn.countAll(), eb.val(0)).as("count"),
        ])
        .executeTakeFirstOrThrow();
      if (users.count != 0) {
        throw new APIError(403, {
          message: "Sign up is disabled",
          code: "EMAIL_AND_PASSWORD_SIGN_UP_IS_NOT_ENABLED",
        });
      }
    }),
  },
  databaseHooks: {
    user: {
      create: {
        before: async (user) => {
          const users = await db
            .selectFrom("users")
            .select((eb) => [
              eb.fn.coalesce(eb.fn.countAll(), eb.val(0)).as("count"),
            ])
            .executeTakeFirstOrThrow();

          return {
            data: {
              ...user,
              role: users.count == 0 ? "ADMIN" : extractRole(user),
            },
          };
        },
      },
    },
  },
});

Additional context

Additional Information

  • I've checked the better-auth.session- header, which appears to be present
  • The exact same code works correctly in version 1.1.21
  • This appears to be a regression in how authentication headers are handled in v1.2.3
Originally created by @awbx on GitHub (Mar 11, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce # APIError[401] when creating users via seed function in v1.2.3 ## Description When trying to seed users via the API endpoint, the `auth.api.createUser` function throws an `APIError[401]` (Unauthorized) in version 1.2.3. This functionality was working correctly in version 1.1.21. ## Environment - Current version: 1.2.3 - Previously working version: 1.1.21 - Using Hono ## Steps to Reproduce 1. Make a POST request to the `/seed` endpoint 2. The endpoint calls the `userRepo.seed` function 3. Inside this function, it attempts to create users with `auth.api.createUser` 4. The request includes headers passed from the original request ## Code ```typescript // User seeding function async function seed(ctx: RepoContext, headers: Headers) { for (const user of users) { const existingUser = await ctx.db .selectFrom("users as u") .where("email", "=", user.email) .executeTakeFirst(); if (existingUser) continue; await auth.api.createUser({ body: { email: user.email, name: `${user.firstName} ${user.lastName}`, role: user.role, password: user.password, data: { service: [] }, }, headers, }); } return { success: true }; } // Route definition userRouter.post( "/seed", describeRoute({ summary: "Seed users", }), protectedRoute(), async (c) => { return c.json(await userRepo.seed(repoContext(c, true), c.req.raw.headers)); }, ); ``` ### Current vs. Expected behavior ## Expected Behavior Users should be created successfully via the seed function when authenticated. ## Actual Behavior Receiving an `APIError[401]` (Unauthorized) when attempting to create users. ### What version of Better Auth are you using? 1.2.3 ### Provide environment information ```bash ## Environment - Version: v1.2.3 (previously working on v1.1.21) - Framework: Hono - OS: MacOS, M1 - Browser: Firefox - Authentication: Using session-based auth ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript export const auth = betterAuth({ appName: "ABS all in one Platform", baseURL: env.BETTER_AUTH_URL, secret: env.BETTER_AUTH_SECRET, plugins: [ openAPI(), adminPlugin({ defaultRole: "REQUESTER", adminRole: ["ADMIN"] }), ], database: { db, type: "postgres", casing: "camel", }, secondaryStorage: { async set(key, value, ttl) { if (typeof ttl === "undefined") return await redisClient.set(key, value); return await redisClient.set(key, value, "EX", ttl); }, async get(key) { return await redisClient.get(key); }, async delete(key) { await redisClient.del(key); }, }, rateLimit: { enabled: true, storage: "secondary-storage", max: 2, window: 60, modelName: "rate-limits", }, advanced: { useSecureCookies: isProduction(), }, emailVerification: { sendOnSignUp: true, async sendVerificationEmail(data) { console.log("sendVerificationEmail user", data.user); console.log("sendVerificationEmail token", data.token); console.log("sendVerificationEmail url", data.url); }, }, emailAndPassword: { enabled: true, requireEmailVerification: true, async sendResetPassword(data) { console.log("sendResetPassword user", data.user); console.log("sendResetPassword token", data.token); console.log("sendResetPassword url", data.url); }, // sendResetPassword(data, request) {}, }, user: { modelName: "users", additionalFields: { role: { type: "string", validator: { input: z.nativeEnum(UserRole), }, defaultValue: "CLIENT", input: false, }, services: { type: "string[]", validator: { input: z.enum(serviceTypes), }, required: true, input: false, defaultValue: [], }, }, }, account: { modelName: "accounts", accountLinking: { enabled: true, trustedProviders: ["microsoft"], }, }, session: { modelName: "sessions", }, verification: { modelName: "verifications", }, hooks: { before: createAuthMiddleware(async (ctx) => { if (ctx.path !== "/sign-up/email") { return; } const users = await db .selectFrom("users") .where("banned", "=", false) .where("role", "=", "ADMIN") .select((eb) => [ eb.fn.coalesce(eb.fn.countAll(), eb.val(0)).as("count"), ]) .executeTakeFirstOrThrow(); if (users.count != 0) { throw new APIError(403, { message: "Sign up is disabled", code: "EMAIL_AND_PASSWORD_SIGN_UP_IS_NOT_ENABLED", }); } }), }, databaseHooks: { user: { create: { before: async (user) => { const users = await db .selectFrom("users") .select((eb) => [ eb.fn.coalesce(eb.fn.countAll(), eb.val(0)).as("count"), ]) .executeTakeFirstOrThrow(); return { data: { ...user, role: users.count == 0 ? "ADMIN" : extractRole(user), }, }; }, }, }, }, }); ``` ### Additional context ## Additional Information - I've checked the `better-auth.session-` header, which appears to be present - The exact same code works correctly in version 1.1.21 - This appears to be a regression in how authentication headers are handled in v1.2.3
GiteaMirror added the bug label 2026-03-13 08:05:45 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#822