amruthpillai/reactive-resume · error · APIError

BAD_REQUEST

BAD_REQUEST

Error message

redirect_uris entries must be strings

What it means

Better Auth before-middleware validates POST /oauth2/register (RFC 7591 dynamic client registration). Each entry of body.redirect_uris must be a string; if any entry is a number, object, array, etc., the middleware throws APIError('BAD_REQUEST') → HTTP 400 with this message.

Source

Thrown at packages/auth/src/config.ts:117

		telemetry: { enabled: false },
		trustedOrigins: TRUSTED_ORIGINS,
		rateLimit: {
			...rateLimitConfig.betterAuth.global,
			enabled: isRateLimitEnabled,
		},

		hooks: {
			// biome-ignore lint/suspicious/useAwait: Better Auth requires middleware callbacks to return a Promise.
			before: createAuthMiddleware(async (ctx) => {
				if (!ctx.path.includes("/oauth2/register")) return;

				const body = ctx.body as { redirect_uris?: unknown } | undefined;
				const redirectUris = Array.isArray(body?.redirect_uris) ? body.redirect_uris : [];

				for (const uri of redirectUris) {
					if (typeof uri !== "string") {
						throw new APIError("BAD_REQUEST", { message: "redirect_uris entries must be strings" });
					}
					if (
						!isAllowedOAuthRedirectUri(uri, TRUSTED_ORIGINS, {
							allowUnsafe: env.FLAG_ALLOW_UNSAFE_OAUTH_REDIRECT_URI,
						})
					) {
						throw new APIError("BAD_REQUEST", {
							message: "redirect_uri is not allowed for dynamic client registration",
						});
					}
				}
			}),
		},

		advanced: {
			database: { generateId },
			useSecureCookies: authBaseUrl.startsWith("https://"),
			ipAddress: { ipAddressHeaders: TRUSTED_IP_HEADERS },

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Ensure every element of redirect_uris is a string before posting: redirect_uris: ['https://app.example.com/callback'].
  2. If generating the payload programmatically, String(...) each entry and reject empties.
  3. Validate with a Zod schema z.array(z.string().url()) on the client side to catch shape errors before the request.

Example fix

// before
const body = { redirect_uris: [{ url: 'https://app.example.com/cb' }] };
// after
const body = { redirect_uris: ['https://app.example.com/cb'] };
Defensive patterns

Strategy: validation

Validate before calling

import { z } from 'zod';
const body = z.object({ redirect_uris: z.array(z.string().min(1)) }).parse(raw);

Type guard

function areStringUris(v: unknown): v is string[] {
  return Array.isArray(v) && v.every(x => typeof x === 'string' && x.length > 0);
}

Prevention

When it happens

Trigger: A dynamic-registration client posts a JSON body where redirect_uris contains a non-string element, e.g. [12345] or [{origin:'...'}] or [null]. The Array.isArray check passes but the per-entry typeof !== 'string' check fails.

Common situations: Third-party integration or test harness that builds the registration payload from typed objects instead of plain strings; a serialization bug that wraps URIs; a client confusing client_id (number) with a redirect URI.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/5b2f021118e1906c. Report an issue: GitHub.