Florian
Florian2w ago

http request blocked by cors

Any idea why my request from localhost:3000 is still blocked by CORS? I copied the headers from a Convex sample. I can call it from Postman but not React.
import { openai } from "@ai-sdk/openai";
import { getAuthUserId } from "@convex-dev/auth/server";
import { Message, streamText } from "ai";
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";

const http = httpRouter();

http.route({
path: "/api/chat",
method: "POST",
handler: httpAction(async (ctx, req) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

const { messages } = await req.json();

const lastMessages = messages.slice(-30) as Message[];

const result = streamText({
model: openai("gpt-4o"),
messages: lastMessages,
});

return result.toDataStreamResponse({
headers: new Headers({
"Access-Control-Allow-Origin": "*",
Vary: "origin",
}),
});
}),
});

http.route({
path: "/api/chat",
method: "OPTIONS",
handler: httpAction(async (_, request) => {
const headers = request.headers;
if (
headers.get("Origin") !== null &&
headers.get("Access-Control-Request-Method") !== null &&
headers.get("Access-Control-Request-Headers") !== null
) {
return new Response(null, {
headers: new Headers({
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Content-Type, Digest",
"Access-Control-Max-Age": "86400",
}),
});
} else {
return new Response();
}
}),
});


export default http;
import { openai } from "@ai-sdk/openai";
import { getAuthUserId } from "@convex-dev/auth/server";
import { Message, streamText } from "ai";
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";

const http = httpRouter();

http.route({
path: "/api/chat",
method: "POST",
handler: httpAction(async (ctx, req) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

const { messages } = await req.json();

const lastMessages = messages.slice(-30) as Message[];

const result = streamText({
model: openai("gpt-4o"),
messages: lastMessages,
});

return result.toDataStreamResponse({
headers: new Headers({
"Access-Control-Allow-Origin": "*",
Vary: "origin",
}),
});
}),
});

http.route({
path: "/api/chat",
method: "OPTIONS",
handler: httpAction(async (_, request) => {
const headers = request.headers;
if (
headers.get("Origin") !== null &&
headers.get("Access-Control-Request-Method") !== null &&
headers.get("Access-Control-Request-Headers") !== null
) {
return new Response(null, {
headers: new Headers({
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Content-Type, Digest",
"Access-Control-Max-Age": "86400",
}),
});
} else {
return new Response();
}
}),
});


export default http;
16 Replies
erquhart
erquhart2w ago
I run a fork of convex helpers with debugging for cors - opened a PR to add that to the library: https://github.com/get-convex/convex-helpers/pull/590 If you want to use right away, I published my fork as well, you can temporarily alias it to debug:
npm i convex-helpers@npm:@erquhart/convex-helpers
npm i convex-helpers@npm:@erquhart/convex-helpers
// convex/http.ts
const cors = corsRouter(http, { debug: true })
// convex/http.ts
const cors = corsRouter(http, { debug: true })
Florian
FlorianOP2w ago
Thank you. Is that npm install command broken?
erquhart
erquhart2w ago
It shouldn't be, what are you seeing? It's a command to install @erquhart/convex-helpers, but let it be named convex-helpers in your node_modules and package.json That way you don't have to update your code to temporarily use this fork
Florian
FlorianOP2w ago
I can't figure out the import statement
erquhart
erquhart2w ago
You won't change the import statement, that's the value of using an alias
Florian
FlorianOP2w ago
And my package.json entry looks like this: "convex-helpers": "npm:@erquhart/convex-helpers@^0.1.87",
erquhart
erquhart2w ago
oh shoot :facepalm: you're not using the cors router helper, I just read "cors" in your post and assumed you were
Florian
FlorianOP2w ago
It does look useful
erquhart
erquhart2w ago
sorry, let me re-read, but you probably just need to use the regular cors helper: https://github.com/get-convex/convex-helpers/tree/main/packages/convex-helpers#cors-support-for-httprouter
GitHub
convex-helpers/packages/convex-helpers at main · get-convex/convex...
A collection of useful code to complement the official packages. - get-convex/convex-helpers
Florian
FlorianOP2w ago
I'll give it a try, but my Convex auth randomly stopped working
erquhart
erquhart2w ago
Wonder if your versions changed when you did that install Can you run this and share the output:
npx envinfo --npmPackages
npx envinfo --npmPackages
Florian
FlorianOP2w ago
doesn't look like it AuthProviderDiscoveryFailed but there are 0 Google results @erquhart Ok, the auth error is solved. But I'm still getting CORS error after adding the corsRouter.
erquhart
erquhart2w ago
okay, so maybe debug will help then
npm i convex-helpers@npm:@erquhart/convex-helpers
npm i convex-helpers@npm:@erquhart/convex-helpers
// convex/http.ts
const cors = corsRouter(http, { debug: true })
// convex/http.ts
const cors = corsRouter(http, { debug: true })
Florian
FlorianOP2w ago
22.5.2025, 17:26:45 [CONVEX H(OPTIONS /api/chat)] [LOG] 'CORS request' {
path: 'https://clean-deer-999.convex.site/api/chat',
origin: 'http://localhost:3000',
headers: Headers { host: 'clean-deer-999.convex.site', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36', accept: '*/*', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7,en-DE;q=0.6,ru;q=0.5', 'access-control-request-headers': 'authorization,content-type', 'access-control-request-method': 'POST', origin: 'http://localhost:3000', priority: 'u=1, i', referer: 'http://localhost:3000/', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'cross-site', 'x-forwarded-for': '88.74.147.132', 'x-forwarded-host': 'clean-deer-999.convex.site', 'x-forwarded-proto': 'https', 'convex-request-id': '83cfc13510ffe6c4' },
method: 'OPTIONS',
body: null
}
22.5.2025, 17:26:45 [CONVEX H(OPTIONS /api/chat)] [LOG] 'CORS request' {
path: 'https://clean-deer-999.convex.site/api/chat',
origin: 'http://localhost:3000',
headers: Headers { host: 'clean-deer-999.convex.site', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36', accept: '*/*', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7,en-DE;q=0.6,ru;q=0.5', 'access-control-request-headers': 'authorization,content-type', 'access-control-request-method': 'POST', origin: 'http://localhost:3000', priority: 'u=1, i', referer: 'http://localhost:3000/', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'cross-site', 'x-forwarded-for': '88.74.147.132', 'x-forwarded-host': 'clean-deer-999.convex.site', 'x-forwarded-proto': 'https', 'convex-request-id': '83cfc13510ffe6c4' },
method: 'OPTIONS',
body: null
}
Can you see anything in here?
erquhart
erquhart2w ago
If there's no message after that saying it was blocked, the cors helper is allowing it through What cors error(s) are you getting specifically? Is it from options or get/post request?
Florian
FlorianOP2w ago
The options request gets through, but the post request doesn't Maybe it works different because the AI SDK streams the response For anyone running into the same problem, I had to add the "Authorization" header to the options endpoint like this:
"Access-Control-Allow-Headers": "Content-Type, Digest, Authorization",
"Access-Control-Allow-Headers": "Content-Type, Digest, Authorization",

Did you find this page helpful?