Florian
Florian4mo 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
erquhart4mo 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
FlorianOP4mo ago
Thank you. Is that npm install command broken?
erquhart
erquhart4mo 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
FlorianOP4mo ago
I can't figure out the import statement
erquhart
erquhart4mo ago
You won't change the import statement, that's the value of using an alias
Florian
FlorianOP4mo ago
And my package.json entry looks like this: "convex-helpers": "npm:@erquhart/convex-helpers@^0.1.87",
erquhart
erquhart4mo ago
oh shoot :facepalm: you're not using the cors router helper, I just read "cors" in your post and assumed you were
Florian
FlorianOP4mo ago
It does look useful
erquhart
erquhart4mo 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
FlorianOP4mo ago
I'll give it a try, but my Convex auth randomly stopped working
erquhart
erquhart4mo 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
FlorianOP4mo 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
erquhart4mo 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
FlorianOP4mo 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
erquhart4mo 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
FlorianOP3mo 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?