[GH-ISSUE #4945] Organization creation serverside after user creation #18747

Closed
opened 2026-04-15 17:22:26 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @enisze on GitHub (Sep 27, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4945

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

This is basically my code:

const authResult = await auth.api.signUpEmail({
					body: {
						email,
						password,
					},
					headers: await headers(),
				});

				const signedIn = await auth.api.signInEmail({
					headers: await headers(),
					body: { email, password },
				});


				const session = await auth.api.getSession({
					headers: await headers(),
				});

-> The session is undefined here though, which is probably why I cannot create the organization and get a 401 UNAUTHORIZED, but I am unsure
				logger.info(JSON.stringify(session?.user));

				const organization = await auth.api
					.createOrganization({
						body: {
							name: companyName,
							businessAddressId: address.id,
							numberOfProperties,
							numberOfUnits,
							userId: authResult.user.id,
							slug: companyName.toLowerCase().replace(/ /g, "-"),
						},
						headers: await headers(),
					})

Current vs. Expected behavior

I would expect to be able to create an organization

What version of Better Auth are you using?

1.3.7

System info

did not create anything here

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin, organization, phoneNumber } from "better-auth/plugins";
import { db, schema } from "@/lib/data";
import { logger } from "./axiom/server";
import type { AddressSelect } from "./data/schema";

export const auth = betterAuth({
	database: drizzleAdapter(db, {
		provider: "pg",
		schema: {
			...schema,
			user: schema.UserTable,
			organization: schema.OrganizationTable,
			member: schema.MemberTable,
			invitation: schema.InvitationTable,
		},
	}),
	emailAndPassword: {
		enabled: true,
	},
	plugins: [
		admin(),
		phoneNumber({
			sendOTP: async ({ phoneNumber, code }, _request) => {
				logger.info("Verification code", { phoneNumber, code });
				// In production, send SMS here
				// For development, the code is logged above
			},
		}),
		organization({
			schema: {
				organization: {
					additionalFields: {
						businessAddressId: {
							type: "string",
							required: true,
							input: true,
						},
						numberOfProperties: {
							type: "number",
							required: true,
							input: true,
						},
						numberOfUnits: {
							type: "number",
							required: true,
							input: true,
						},
					},
				},
			},
		}),
	],
	user: {
		additionalFields: {
			userType: {
				type: "string",
				input: true,
			},
		},
	},
});

});

Additional context

No response

Originally created by @enisze on GitHub (Sep 27, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4945 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce This is basically my code: ```ts const authResult = await auth.api.signUpEmail({ body: { email, password, }, headers: await headers(), }); const signedIn = await auth.api.signInEmail({ headers: await headers(), body: { email, password }, }); const session = await auth.api.getSession({ headers: await headers(), }); -> The session is undefined here though, which is probably why I cannot create the organization and get a 401 UNAUTHORIZED, but I am unsure logger.info(JSON.stringify(session?.user)); const organization = await auth.api .createOrganization({ body: { name: companyName, businessAddressId: address.id, numberOfProperties, numberOfUnits, userId: authResult.user.id, slug: companyName.toLowerCase().replace(/ /g, "-"), }, headers: await headers(), }) ``` ### Current vs. Expected behavior I would expect to be able to create an organization ### What version of Better Auth are you using? 1.3.7 ### System info ```bash did not create anything here ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { admin, organization, phoneNumber } from "better-auth/plugins"; import { db, schema } from "@/lib/data"; import { logger } from "./axiom/server"; import type { AddressSelect } from "./data/schema"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: { ...schema, user: schema.UserTable, organization: schema.OrganizationTable, member: schema.MemberTable, invitation: schema.InvitationTable, }, }), emailAndPassword: { enabled: true, }, plugins: [ admin(), phoneNumber({ sendOTP: async ({ phoneNumber, code }, _request) => { logger.info("Verification code", { phoneNumber, code }); // In production, send SMS here // For development, the code is logged above }, }), organization({ schema: { organization: { additionalFields: { businessAddressId: { type: "string", required: true, input: true, }, numberOfProperties: { type: "number", required: true, input: true, }, numberOfUnits: { type: "number", required: true, input: true, }, }, }, }, }), ], user: { additionalFields: { userType: { type: "string", input: true, }, }, }, }); }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-15 17:22:26 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 27, 2025):

The session is undefined and you're getting a 401 because the session cookies are not being set or propagated between your sign-in and subsequent requests. When using Better Auth server-side, you need to call auth.api.signInEmail with asResponse: true to get the response headers containing the session cookies, then pass those cookies to your next requests. Without this, getSession will return undefined and any auth-gated action like organization creation will fail with 401 UNAUTHORIZED [source].

Here's how you can fix it:

const signInResponse = await auth.api.signInEmail({
  body: { email, password },
  asResponse: true,
});
const setCookie = signInResponse.headers.get('set-cookie');

// Pass the set-cookie header to subsequent requests
const sessionResponse = await auth.api.getSession({
  headers: { cookie: setCookie },
  asResponse: true,
});
const session = await sessionResponse.json();

const orgResponse = await auth.api.createOrganization({
  body: { /* ... */ },
  headers: { cookie: setCookie },
});

If you need to perform privileged actions immediately after signup (before the session is fully established), consider using the API Key plugin and authenticating with an API key in the headers instead of relying on the user's session [source].

Alternatively, defer organization creation until after the user is fully committed to the database (for example, on first dashboard access), as there are transaction timing issues that can make the user invisible to internal queries right after signup [source].

Let me know if you need a more detailed code example for cookie handling!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3341873637 --> @dosubot[bot] commented on GitHub (Sep 27, 2025): <!-- Answer --> The session is undefined and you're getting a 401 because the session cookies are not being set or propagated between your sign-in and subsequent requests. When using Better Auth server-side, you need to call `auth.api.signInEmail` with `asResponse: true` to get the response headers containing the session cookies, then pass those cookies to your next requests. Without this, `getSession` will return undefined and any auth-gated action like organization creation will fail with 401 UNAUTHORIZED [[source](https://github.com/better-auth/better-auth/issues/4517)]. Here's how you can fix it: ```ts const signInResponse = await auth.api.signInEmail({ body: { email, password }, asResponse: true, }); const setCookie = signInResponse.headers.get('set-cookie'); // Pass the set-cookie header to subsequent requests const sessionResponse = await auth.api.getSession({ headers: { cookie: setCookie }, asResponse: true, }); const session = await sessionResponse.json(); const orgResponse = await auth.api.createOrganization({ body: { /* ... */ }, headers: { cookie: setCookie }, }); ``` If you need to perform privileged actions immediately after signup (before the session is fully established), consider using the API Key plugin and authenticating with an API key in the headers instead of relying on the user's session [[source](https://github.com/better-auth/better-auth/issues/4861)]. Alternatively, defer organization creation until after the user is fully committed to the database (for example, on first dashboard access), as there are transaction timing issues that can make the user invisible to internal queries right after signup [[source](https://github.com/better-auth/better-auth/issues/4718)]. Let me know if you need a more detailed code example for cookie handling! <!-- 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/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/45e06c5f-8cee-4f84-96d0-83973ce9fd7d?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/4945)
Author
Owner

@enisze commented on GitHub (Sep 27, 2025):

How could I sync the session of the server to the user afterwards @dosu @dosubot?

<!-- gh-comment-id:3341912623 --> @enisze commented on GitHub (Sep 27, 2025): How could I sync the session of the server to the user afterwards @dosu @dosubot?
Author
Owner

@dosubot[bot] commented on GitHub (Sep 27, 2025):

To sync the server-side session to the user's browser after privileged actions, you need to forward the Set-Cookie headers from Better Auth's response to the user's HTTP response. This is typically done by calling your auth API (like signInEmail) with asResponse: true, extracting the Set-Cookie header from the response, and then setting it on the outgoing response to the client. This ensures the browser receives and stores the session cookies, syncing the session state.

For example, in a Next.js API route, you might do:

const signInResponse = await auth.api.signInEmail({
  body: { email, password },
  asResponse: true,
});
const setCookie = signInResponse.headers.get('set-cookie');
if (setCookie) {
  // Forward the Set-Cookie header to the user's browser
  response.headers.set('set-cookie', setCookie);
}

This pattern is used in Better Auth's Next.js integration via toNextJsHandler, which manages session cookies and authentication state propagation between server and client. The key is to proxy the Set-Cookie header from Better Auth's internal response to your user's outgoing HTTP response, so the browser can store the session cookie and the user stays authenticated on the client side [source].

If you're using a different backend framework, the same principle applies: extract the Set-Cookie header from the Better Auth response and set it on your outgoing response to the client.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3341913626 --> @dosubot[bot] commented on GitHub (Sep 27, 2025): <!-- Answer --> To sync the server-side session to the user's browser after privileged actions, you need to forward the Set-Cookie headers from Better Auth's response to the user's HTTP response. This is typically done by calling your auth API (like signInEmail) with `asResponse: true`, extracting the Set-Cookie header from the response, and then setting it on the outgoing response to the client. This ensures the browser receives and stores the session cookies, syncing the session state. For example, in a Next.js API route, you might do: ```ts const signInResponse = await auth.api.signInEmail({ body: { email, password }, asResponse: true, }); const setCookie = signInResponse.headers.get('set-cookie'); if (setCookie) { // Forward the Set-Cookie header to the user's browser response.headers.set('set-cookie', setCookie); } ``` This pattern is used in Better Auth's Next.js integration via `toNextJsHandler`, which manages session cookies and authentication state propagation between server and client. The key is to proxy the Set-Cookie header from Better Auth's internal response to your user's outgoing HTTP response, so the browser can store the session cookie and the user stays authenticated on the client side [[source]](https://github.com/better-auth/better-auth/blob/f6cbdcc84ee5d2971fdcc8b23ff7c174f88cf45b/demo/nextjs/app/api/auth/[...all]/route.ts). If you're using a different backend framework, the same principle applies: extract the Set-Cookie header from the Better Auth response and set it on your outgoing response to the client. <!-- 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/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/1229bdd2-4bb0-47e3-95fa-880d4120dc5f?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/4945)
Author
Owner

@tkjaergaard commented on GitHub (Jan 15, 2026):

I managed to create an organization after user creation using:

export const auth = betterAuth({
 // ...
  databaseHooks: {
    user: {
      create: {
        after: async (user, _ctx) => {
          // Create an organization for the new user
          await auth.api.createOrganization({
            request: undefined,
            headers: undefined,
            body: {
              name: '<personal>',
              userId: user.id,
              slug: user.name.replace(/\s+/g, '-').toLowerCase(),
            },
          })
        },
      },
    },
  },
})
<!-- gh-comment-id:3753983866 --> @tkjaergaard commented on GitHub (Jan 15, 2026): I managed to create an organization after user creation using: ```ts export const auth = betterAuth({ // ... databaseHooks: { user: { create: { after: async (user, _ctx) => { // Create an organization for the new user await auth.api.createOrganization({ request: undefined, headers: undefined, body: { name: '<personal>', userId: user.id, slug: user.name.replace(/\s+/g, '-').toLowerCase(), }, }) }, }, }, }, }) ```
Author
Owner

@nhuray commented on GitHub (Jan 22, 2026):

@tkjaergaard How did you achieve it ? Which version are your running ?

I'm trying to do the same but face that issue: https://github.com/better-auth/better-auth/issues/4718#issuecomment-3302288221

<!-- gh-comment-id:3783970961 --> @nhuray commented on GitHub (Jan 22, 2026): @tkjaergaard How did you achieve it ? Which version are your running ? I'm trying to do the same but face that issue: https://github.com/better-auth/better-auth/issues/4718#issuecomment-3302288221
Author
Owner

@tkjaergaard commented on GitHub (Jan 22, 2026):

@tkjaergaard How did you achieve it ? Which version are your running ?

I'm trying to do the same but face that issue: #4718 (comment)

 "better-auth": "^1.4.10"
<!-- gh-comment-id:3783979509 --> @tkjaergaard commented on GitHub (Jan 22, 2026): > [@tkjaergaard](https://github.com/tkjaergaard) How did you achieve it ? Which version are your running ? > > I'm trying to do the same but face that issue: [#4718 (comment)](https://github.com/better-auth/better-auth/issues/4718#issuecomment-3302288221) ``` "better-auth": "^1.4.10" ```
Author
Owner

@github-actions[bot] commented on GitHub (Apr 1, 2026):

This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.

<!-- gh-comment-id:4166563635 --> @github-actions[bot] commented on GitHub (Apr 1, 2026): This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#18747