[GH-ISSUE #2774] Adding userId to organization.create will not make that user the owner #9344

Closed
opened 2026-04-13 04:46:47 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @j13n on GitHub (May 24, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2774

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

First create a new user you want to make owner of an organization:

const { data: userData, error: userError } = await authClient.admin.createUser({
  name,
  email,
  password: "something-random",
  role: "user",
})

Now create an organization and make this new user the organization owner:

const { error: organizationError } = await authClient.organization.create({
  name: organizationName,
  slug,
  userId: userData.user.id, // This should be the owner right now
})

Current vs. Expected behavior

Now check the newly created member of the organization and see that the owner is the currently active admin instead of the user of the supplied userId to organization.create.

What version of Better Auth are you using?

1.2.8

Provide environment information

- OS: Mac
- Browser: Chrome
- Node: 22.16.0

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

Backend, Client, Package

Auth config (if applicable)

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

Additional context

I'll create a PR with a fix and test.

Originally created by @j13n on GitHub (May 24, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2774 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce First create a new user you want to make owner of an organization: ```ts const { data: userData, error: userError } = await authClient.admin.createUser({ name, email, password: "something-random", role: "user", }) ``` Now create an organization and make this new user the organization owner: ```ts const { error: organizationError } = await authClient.organization.create({ name: organizationName, slug, userId: userData.user.id, // This should be the owner right now }) ``` ### Current vs. Expected behavior Now check the newly created member of the organization and see that the owner is the currently active admin instead of the user of the supplied userId to organization.create. ### What version of Better Auth are you using? 1.2.8 ### Provide environment information ```bash - OS: Mac - Browser: Chrome - Node: 22.16.0 ``` ### Which area(s) are affected? (Select all that apply) Backend, Client, Package ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context I'll create a PR with a fix and test.
GiteaMirror added the locked label 2026-04-13 04:46:47 -05:00
Author
Owner

@j13n commented on GitHub (May 24, 2025):

Added PR: https://github.com/better-auth/better-auth/pull/2775

<!-- gh-comment-id:2906814006 --> @j13n commented on GitHub (May 24, 2025): Added PR: https://github.com/better-auth/better-auth/pull/2775
Author
Owner

@Kinfe123 commented on GitHub (May 28, 2025):

This should only be used by admins or when called by the server. thats why we are checking for the user.

<!-- gh-comment-id:2914649785 --> @Kinfe123 commented on GitHub (May 28, 2025): This should only be used by admins or when called by the server. thats why we are checking for the user.
Author
Owner

@j13n commented on GitHub (May 29, 2025):

Ok. So I took another look at this and found the following. I noticed the backend instance also allows you to create an organization with userId:

const result = await auth.api.createOrganization({
  body: {
    name,
    slug,
    userId,
  },
})

And this way it does make the userId the owner of the organization. I'd say either remove the userId option in the userClient when possible (with TS) or perhaps document this somewhere since the option doesn't make sense. You always need to be authenticated when calling authClient.organization.create else it'll send you an UNAUTHORIZED message. PR should fix that.

Anyway, hopefully this helps someone out there.

<!-- gh-comment-id:2918249799 --> @j13n commented on GitHub (May 29, 2025): Ok. So I took another look at this and found the following. I noticed the backend instance also allows you to create an organization with `userId`: ```ts const result = await auth.api.createOrganization({ body: { name, slug, userId, }, }) ``` And this way it **does make the userId the owner of the organization**. I'd say either remove the userId option in the userClient when possible (with TS) or perhaps document this somewhere since the option doesn't make sense. You always need to be authenticated when calling `authClient.organization.create` else it'll send you an UNAUTHORIZED message. PR should fix that. Anyway, hopefully this helps someone out there.
Author
Owner

@conficiusa commented on GitHub (Jul 31, 2025):

This should only be used by admins or when called by the server. thats why we are checking for the user.

This is the problematic code in the endpoint

 const session = await getSessionFromCtx(ctx);

if (!session && (ctx.request || ctx.headers)) {
  throw new APIError("UNAUTHORIZED");
}

let user = session?.user || null;

if (!user) {
  if (!ctx.body.userId) {
    throw new APIError("UNAUTHORIZED");
  }

  user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
}

https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/organization/routes/crud-org.ts

the session always contains the user and so when there is a session there is a user when there is no session an unauthorized error is thrown

is there is no scenario where

if (!user) {
  if (!ctx.body.userId) {
    throw new APIError("UNAUTHORIZED");
  }

  user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
}

ever runs

<!-- gh-comment-id:3141321607 --> @conficiusa commented on GitHub (Jul 31, 2025): > This should only be used by admins or when called by the server. thats why we are checking for the user. This is the problematic code in the endpoint ``` const session = await getSessionFromCtx(ctx); if (!session && (ctx.request || ctx.headers)) { throw new APIError("UNAUTHORIZED"); } let user = session?.user || null; if (!user) { if (!ctx.body.userId) { throw new APIError("UNAUTHORIZED"); } user = await ctx.context.internalAdapter.findUserById(ctx.body.userId); } ``` [https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/organization/routes/crud-org.ts](https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/organization/routes/crud-org.ts ) the session always contains the user and so when there is a session there is a user when there is no session an unauthorized error is thrown is there is no scenario where ``` if (!user) { if (!ctx.body.userId) { throw new APIError("UNAUTHORIZED"); } user = await ctx.context.internalAdapter.findUserById(ctx.body.userId); } ``` ever runs
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9344