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.
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.
Understand the failure class
Background: BAD_REQUEST error code: request rejected as invalid (HTTP 400) - causes and fixes across libraries — this error's family across 8 libraries.
Related errors
- provider did not return an email address. This is required…
- A compound selector can contain only one type selector.
- Application document cannot be empty.
- Application documents must be PDF files.
- Attribute is not available on .
AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12).
Data as JSON: /api/errors/5b2f021118e1906c.
Report an issue: GitHub.
Appendix: 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)