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
- Ensure every element of redirect_uris is a string before posting: redirect_uris: ['https://app.example.com/callback'].
- If generating the payload programmatically, String(...) each entry and reject empties.
- 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
- Always send redirect_uris as an array of literal strings.
- Validate the registration payload with Zod before posting.
- String() each entry if generated from typed objects.
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
- ${providerName} provider did not return an email address. Th
- AuthError
- BAD_REQUEST
- BAD_REQUEST
- BAD_REQUEST
AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12).
Data as JSON: /api/errors/5b2f021118e1906c.
Report an issue: GitHub.