[GH-ISSUE #6299] Sign-in SSO gives TypeError: Invalid URL #10476

Closed
opened 2026-04-13 06:38:48 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @Atchferox on GitHub (Nov 25, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6299

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

After registering a sso provider i'm trying to sign up with that, whilst signin in im getting an 500 internal server error with Better Auth] [error]: TypeError TypeError: Invalid URL at ignore-listed frames { code: 'ERR_INVALID_URL', input: 'undefined' }

i dont know if this is an library issue or my setup, if anybody has any directions.

Current vs. Expected behavior

I expect the sign in request to work without giving TypeError

What version of Better Auth are you using?

1.4.1

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000",
    "release": "25.1.0",
    "cpuCount": 8,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.11 GB"
  },
  "node": {
    "version": "v20.17.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.1.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.0.0"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.7"
    }
  ],
  "betterAuth": {
    "version": "^1.4.1",
    "config": null
  }
}

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

Package, Other

Auth config (if applicable)

export const auth = betterAuth({
	logger: {
		disabled: false,
		level: "debug",
		log: (level, message, ...args) => {
			console.log(`[Better Auth] [${level}]: ${message}`, ...args);
		},
	},
	database: drizzleAdapter(db, {
		provider: "pg",
		schema,
	}),
	experimental: {
		joins: true,
	},
	user: {
		additionalFields: {
			role: {
				type: "string",
				input: false,
			},
		},
	},
	baseURL: "http://localhost:3000",
	basePath: "/api/auth",
	databaseHooks: {
		session: {
			create: {
				before: async session => {
					const activeOrganization = await db.query.organization.findFirst({
						where: inArray(
							organizationSchema.id,
							db
								.select({ id: memberSchema.organizationId })
								.from(memberSchema)
								.where(eq(memberSchema.userId, session.userId))
						),
					});
					return {
						data: {
							...session,
							activeOrganizationId: activeOrganization?.id,
						},
					};
				},
				after: async session => {
					await db
						.delete(schema.session)
						.where(
							and(
								eq(schema.session.userId, session.userId),
								ne(schema.session.id, session.id)
							)
						);
				},
			},
		},
	},
	emailAndPassword: {
		enabled: true,
		password: {
			hash: async password => {
				return new Promise((resolve, reject) => {
					scrypt(password, "salt", 64, (err, derivedKey) => {
						if (err) {
							reject(err);
						}
						resolve(derivedKey.toString());
					});
				});
			},
			verify: async data => {
				return new Promise((resolve, reject) => {
					scrypt(data.password, "salt", 64, (err, derivedKey) => {
						if (err) {
							reject(err);
						}
						resolve(derivedKey.toString() === data.hash);
					});
				});
			},
		},
		sendResetPassword: async ({ user, url }) => {
			const isEmailEnabled = process.env.NODE_ENV === "production";

			if (!isEmailEnabled) {
				console.log(
					"[Email disabled in development] Skipping reset password email",
					{
						to: user.email,
						url,
						nodeEnv: process.env.NODE_ENV,
					}
				);
				return;
			}

			const postmarkClient = new ServerClient(
				process.env.POSTMARK_API_KEY ?? ""
			);

			try {
				await postmarkClient.sendEmail({
					From: process.env.POSTMARK_FROM_EMAIL ?? "",
					To: user.email,
					Subject: "Reset wachtwoord",
					TextBody: `Klik op deze link om je wachtwoord te resetten: ${url}`,
				});
			} catch (error) {
				console.error(error);
				throw error;
			}
		},
	},
	plugins: [
		openAPI(),
		sso({
			organizationProvisioning: {
				disabled: false,
				defaultRole: "member",
			},
			trustEmailVerified: true,
		}),
		organization({
			ac,
			roles: { member, admin, owner, pending },
			organizationHooks: {
				beforeCreateInvitation: async ({ invitation }) => {
					const organization = await db.query.organization.findFirst({
						where: eq(organizationSchema.id, invitation.organizationId),

						with: {
							members: true,
							invitations: {
								where: eq(invitationSchema.status, "pending"),
							},
						},
					});
					if (!organization) {
						throw new Error("Organization not found");
					}

					if (
						organization.membershipLimit &&
						organization.membershipLimit + 1 <= // +1 because the super admin is already a member
							(organization.members?.length ?? 0) +
								(organization.invitations?.length ?? 0)
					) {
						console.log(organization.membershipLimit + 1);
						throw new Error("Organization membership limit reached");
					}

					//TODO: this is a stupid design decision from better auth not allowing to have a expiration date that is "infinite"
					const farFutureExpiration = new Date(
						Date.now() + 10 * 365 * 24 * 60 * 60 * 1000
					); // 10 years in the future
					const customExpiration = farFutureExpiration;

					return {
						data: {
							...invitation,
							expiresAt: customExpiration,
						},
					};
				},
			},
			organizationLimit: 100,
			membershipLimit: 200,
			schema: {
				organization: {
					additionalFields: {
						membershipLimit: {
							type: "number",
							input: false,
							defaultValue: 30,
						},
					},
				},
			},

			// Better auth doesn't have a generic that gets the extended user
			allowUserToCreateOrganization: async (betterAuthUser: User) => {
				const isSuperAdmin = await isUserSuperAdmin(betterAuthUser.id);
				return isSuperAdmin;
			},

			async sendInvitationEmail({ invitation }) {
				const isEmailEnabled = process.env.NODE_ENV === "production";

				if (!isEmailEnabled) {
					console.log(
						"[Email disabled in development] Skipping invitation email",
						{
							to: invitation.email,
							organizationId: invitation.organizationId,
							nodeEnv: process.env.NODE_ENV,
						}
					);
					return;
				}

				const postmarkClient = new ServerClient(
					process.env.POSTMARK_API_KEY ?? ""
				);

				const organization = await db.query.organization.findFirst({
					where: eq(organizationSchema.id, invitation.organizationId),
				});

				const url = `${process.env.NEXT_PUBLIC_BETTER_AUTH_URL}/accept-invitation?invitationId=${invitation.id}`;
				try {
					await postmarkClient.sendEmail({
						From: process.env.POSTMARK_FROM_EMAIL ?? "",
						To: invitation.email,
						Subject: `Je bent uitgenodigd voor de organizatie: ${organization?.name} als ${invitation.role}`,
						TextBody: `Klik op deze link om je account te activeren: ${url}`,
					});
				} catch (error) {
					console.error(error);
					await db
						.delete(invitationSchema)
						.where(eq(invitationSchema.id, invitation.id));
					throw error;
				}
			},
		}),
		nextCookies(),
	],
});

Additional context

No response

Originally created by @Atchferox on GitHub (Nov 25, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6299 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce After registering a sso provider i'm trying to sign up with that, whilst signin in im getting an 500 internal server error with `Better Auth] [error]: TypeError TypeError: Invalid URL at ignore-listed frames { code: 'ERR_INVALID_URL', input: 'undefined' }` i dont know if this is an library issue or my setup, if anybody has any directions. ### Current vs. Expected behavior I expect the sign in request to work without giving TypeError ### What version of Better Auth are you using? 1.4.1 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000", "release": "25.1.0", "cpuCount": 8, "cpuModel": "Apple M1 Pro", "totalMemory": "16.00 GB", "freeMemory": "0.11 GB" }, "node": { "version": "v20.17.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.1.0" }, "frameworks": [ { "name": "next", "version": "16.0.0" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.7" } ], "betterAuth": { "version": "^1.4.1", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Package, Other ### Auth config (if applicable) ```typescript export const auth = betterAuth({ logger: { disabled: false, level: "debug", log: (level, message, ...args) => { console.log(`[Better Auth] [${level}]: ${message}`, ...args); }, }, database: drizzleAdapter(db, { provider: "pg", schema, }), experimental: { joins: true, }, user: { additionalFields: { role: { type: "string", input: false, }, }, }, baseURL: "http://localhost:3000", basePath: "/api/auth", databaseHooks: { session: { create: { before: async session => { const activeOrganization = await db.query.organization.findFirst({ where: inArray( organizationSchema.id, db .select({ id: memberSchema.organizationId }) .from(memberSchema) .where(eq(memberSchema.userId, session.userId)) ), }); return { data: { ...session, activeOrganizationId: activeOrganization?.id, }, }; }, after: async session => { await db .delete(schema.session) .where( and( eq(schema.session.userId, session.userId), ne(schema.session.id, session.id) ) ); }, }, }, }, emailAndPassword: { enabled: true, password: { hash: async password => { return new Promise((resolve, reject) => { scrypt(password, "salt", 64, (err, derivedKey) => { if (err) { reject(err); } resolve(derivedKey.toString()); }); }); }, verify: async data => { return new Promise((resolve, reject) => { scrypt(data.password, "salt", 64, (err, derivedKey) => { if (err) { reject(err); } resolve(derivedKey.toString() === data.hash); }); }); }, }, sendResetPassword: async ({ user, url }) => { const isEmailEnabled = process.env.NODE_ENV === "production"; if (!isEmailEnabled) { console.log( "[Email disabled in development] Skipping reset password email", { to: user.email, url, nodeEnv: process.env.NODE_ENV, } ); return; } const postmarkClient = new ServerClient( process.env.POSTMARK_API_KEY ?? "" ); try { await postmarkClient.sendEmail({ From: process.env.POSTMARK_FROM_EMAIL ?? "", To: user.email, Subject: "Reset wachtwoord", TextBody: `Klik op deze link om je wachtwoord te resetten: ${url}`, }); } catch (error) { console.error(error); throw error; } }, }, plugins: [ openAPI(), sso({ organizationProvisioning: { disabled: false, defaultRole: "member", }, trustEmailVerified: true, }), organization({ ac, roles: { member, admin, owner, pending }, organizationHooks: { beforeCreateInvitation: async ({ invitation }) => { const organization = await db.query.organization.findFirst({ where: eq(organizationSchema.id, invitation.organizationId), with: { members: true, invitations: { where: eq(invitationSchema.status, "pending"), }, }, }); if (!organization) { throw new Error("Organization not found"); } if ( organization.membershipLimit && organization.membershipLimit + 1 <= // +1 because the super admin is already a member (organization.members?.length ?? 0) + (organization.invitations?.length ?? 0) ) { console.log(organization.membershipLimit + 1); throw new Error("Organization membership limit reached"); } //TODO: this is a stupid design decision from better auth not allowing to have a expiration date that is "infinite" const farFutureExpiration = new Date( Date.now() + 10 * 365 * 24 * 60 * 60 * 1000 ); // 10 years in the future const customExpiration = farFutureExpiration; return { data: { ...invitation, expiresAt: customExpiration, }, }; }, }, organizationLimit: 100, membershipLimit: 200, schema: { organization: { additionalFields: { membershipLimit: { type: "number", input: false, defaultValue: 30, }, }, }, }, // Better auth doesn't have a generic that gets the extended user allowUserToCreateOrganization: async (betterAuthUser: User) => { const isSuperAdmin = await isUserSuperAdmin(betterAuthUser.id); return isSuperAdmin; }, async sendInvitationEmail({ invitation }) { const isEmailEnabled = process.env.NODE_ENV === "production"; if (!isEmailEnabled) { console.log( "[Email disabled in development] Skipping invitation email", { to: invitation.email, organizationId: invitation.organizationId, nodeEnv: process.env.NODE_ENV, } ); return; } const postmarkClient = new ServerClient( process.env.POSTMARK_API_KEY ?? "" ); const organization = await db.query.organization.findFirst({ where: eq(organizationSchema.id, invitation.organizationId), }); const url = `${process.env.NEXT_PUBLIC_BETTER_AUTH_URL}/accept-invitation?invitationId=${invitation.id}`; try { await postmarkClient.sendEmail({ From: process.env.POSTMARK_FROM_EMAIL ?? "", To: invitation.email, Subject: `Je bent uitgenodigd voor de organizatie: ${organization?.name} als ${invitation.role}`, TextBody: `Klik op deze link om je account te activeren: ${url}`, }); } catch (error) { console.error(error); await db .delete(invitationSchema) .where(eq(invitationSchema.id, invitation.id)); throw error; } }, }), nextCookies(), ], }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 06:38:48 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 25, 2025):

Thanks for reporting this issue! The error “Invalid URL” suggests that somewhere in the SSO flow a URL is being constructed using an undefined value—most likely a missing or misconfigured callback URL or base URL. Please double-check your SSO provider settings and ensure that all URL-related fields (such as your baseURL and any callbackURL values) are correctly set and not undefined. It may also help to verify your environment variables (for example, any NEXT_PUBLIC_BETTER_AUTH_URL if used) so they are properly passed into your configuration. Are there any adjustments you made to the default SSO settings that might affect these URL values? tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3575401861 --> @better-auth-agent[bot] commented on GitHub (Nov 25, 2025): Thanks for reporting this issue! The error “Invalid URL” suggests that somewhere in the SSO flow a URL is being constructed using an undefined value—most likely a missing or misconfigured callback URL or base URL. Please double-check your SSO provider settings and ensure that all URL-related fields (such as your baseURL and any callbackURL values) are correctly set and not undefined. It may also help to verify your environment variables (for example, any NEXT_PUBLIC_BETTER_AUTH_URL if used) so they are properly passed into your configuration. Are there any adjustments you made to the default SSO settings that might affect these URL values? tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@Atchferox commented on GitHub (Nov 25, 2025):

@better-auth-agent

<!-- gh-comment-id:3575403480 --> @Atchferox commented on GitHub (Nov 25, 2025): @better-auth-agent
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 25, 2025):

Hi there! It looks like the sign-in process is failing because the callback URL is coming through as undefined, which suggests that there might be a misconfiguration in your SSO setup. Here are a few troubleshooting steps you can try:

• Verify that your baseURL is correctly defined in your auth configuration and that it’s not being overwritten by an undefined environment variable. A missing or incorrect base URL can lead to an invalid callback URL.

• Double-check the settings on your SSO provider side—ensure that the callback/redirect URL exactly matches what your Better Auth backend expects.

• If you are using environment variables to set the SSO or base URL, be sure they are set properly in your development environment. Sometimes an undefined or empty environment variable may lead to this error.

• Finally, you may want to add logging right before constructing the URL in your SSO flow to confirm that all variables (like the callback URL) are populated correctly at runtime.

Could you check these points and let me know if they resolve the issue? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3575404743 --> @better-auth-agent[bot] commented on GitHub (Nov 25, 2025): Hi there! It looks like the sign-in process is failing because the callback URL is coming through as undefined, which suggests that there might be a misconfiguration in your SSO setup. Here are a few troubleshooting steps you can try: • Verify that your baseURL is correctly defined in your auth configuration and that it’s not being overwritten by an undefined environment variable. A missing or incorrect base URL can lead to an invalid callback URL. • Double-check the settings on your SSO provider side—ensure that the callback/redirect URL exactly matches what your Better Auth backend expects. • If you are using environment variables to set the SSO or base URL, be sure they are set properly in your development environment. Sometimes an undefined or empty environment variable may lead to this error. • Finally, you may want to add logging right before constructing the URL in your SSO flow to confirm that all variables (like the callback URL) are populated correctly at runtime. Could you check these points and let me know if they resolve the issue? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@Atchferox commented on GitHub (Nov 25, 2025):

@better-auth-agent

i looked at all the relevant points you said, I have even hardcoded the baseUrl in both the auth and authclient setup.
even after doing that it still gives the same error.

<!-- gh-comment-id:3575420554 --> @Atchferox commented on GitHub (Nov 25, 2025): @better-auth-agent i looked at all the relevant points you said, I have even hardcoded the baseUrl in both the auth and authclient setup. even after doing that it still gives the same error.
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 25, 2025):

Hi there! It looks like your error indicates that one of the URLs—likely the callback or redirect URL—is coming through as undefined, which is causing the Invalid URL error. Given that you’ve already hardcoded the base URL in both your auth and auth client setups, here are a few troubleshooting steps you can try:

• Double-check that every URL used by your SSO provider (including any callback URLs you might be passing implicitly) is valid and fully qualified (include the http/https protocol). Sometimes a missing protocol can lead to an “undefined” result.

• Verify that all required environment variables (e.g., the one for NEXT_PUBLIC_BETTER_AUTH_URL) are correctly populated in your environment. If that variable is not defined correctly, your constructed URL (as seen in your invitation link generation) could inadvertently turn out undefined [1].

• Add some debugging logs right before the URL is passed to the SSO flow to help you pinpoint which value is undefined. This will help determine if it’s an SSO plugin configuration issue or something on the provider’s end.

• Ensure that your SSO provider’s setup (like the redirect or callback URL registered on their dashboard) matches the URL you expect to be used. An inconsistency there can lead to an invalid or missing URL.

Have you been able to trace which specific URL is coming through as undefined? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3575422004 --> @better-auth-agent[bot] commented on GitHub (Nov 25, 2025): Hi there! It looks like your error indicates that one of the URLs—likely the callback or redirect URL—is coming through as undefined, which is causing the Invalid URL error. Given that you’ve already hardcoded the base URL in both your auth and auth client setups, here are a few troubleshooting steps you can try: • Double-check that every URL used by your SSO provider (including any callback URLs you might be passing implicitly) is valid and fully qualified (include the http/https protocol). Sometimes a missing protocol can lead to an “undefined” result. • Verify that all required environment variables (e.g., the one for NEXT_PUBLIC_BETTER_AUTH_URL) are correctly populated in your environment. If that variable is not defined correctly, your constructed URL (as seen in your invitation link generation) could inadvertently turn out undefined [[1]](https://www.better-auth.com/). • Add some debugging logs right before the URL is passed to the SSO flow to help you pinpoint which value is undefined. This will help determine if it’s an SSO plugin configuration issue or something on the provider’s end. • Ensure that your SSO provider’s setup (like the redirect or callback URL registered on their dashboard) matches the URL you expect to be used. An inconsistency there can lead to an invalid or missing URL. Have you been able to trace which specific URL is coming through as undefined? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@jonathansamines commented on GitHub (Nov 25, 2025):

Hey @Atchferox Can you help me verify a few things:

  • When you register the SSO provider, are you using OIDC or SAML2?
  • If you are using OIDC, are you providing either an authorization endpoint (via oidcConfig.authorizationEndpoint) or a discovery endpoint (via oidcConfig.discoveryEndpoint)?
  • If you are providing a discovery endpoint, is the authorization endpoint returned by it?
<!-- gh-comment-id:3576282144 --> @jonathansamines commented on GitHub (Nov 25, 2025): Hey @Atchferox Can you help me verify a few things: - When you register the SSO provider, are you using OIDC or SAML2? - If you are using OIDC, are you providing either an authorization endpoint (via `oidcConfig.authorizationEndpoint`) or a discovery endpoint (via `oidcConfig.discoveryEndpoint`)? - If you are providing a discovery endpoint, is the authorization endpoint returned by it?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10476