Setting `corsHandler.origin` issues
Description
I encountered a similar issue as described in Issue 505 and Issue 497. However, I believe my case might be slightly different, so Iām opening a new issue.
When attempting to configure the corsHandler.origin to a specific value, such as https://hthompson.dev, the access-control-allow-origin header remains set to * instead of reflecting the specified origin. I have tried various configurations, but the issue persists. In Issue 505, the author mentioned the direct use of the access-control-allow-origin header, which I may have misunderstood, but applying that suggestion did not resolve the problem.
Ultimately, my goal is to configure the origin to something like ["https://hthompson.dev", "https://*.hthompson.dev"] or ["https://hthompson.dev", "https://analytics.hthompson.dev"]. But right now, I'm stumped.
Version
nuxt-security:2.0.0-rc.9nuxt:3.12.4
Reproduction Link
Steps to Reproduce
- Clone the repository locally.
- Switch to the
nuxt-securitybranch:git checkout nuxt-security. - Install the dependencies and build the site:
pnpm install && pnpm run build. - Start the server:
node .output/server/index.mjs. - Perform a cURL request to retrieve the headers:
curl -I http://localhost:3000. - Observe that the
access-control-allow-originheader remains set to*.
Expected Behavior
The access-control-allow-origin header should be set according to the configuration specified in the nuxt.config.ts file.
Actual Behavior
The header defaults to access-control-allow-origin: *, regardless of the specified configuration.
Hi @StrangeRanger I am the author of #505.
Have you already tried the following? I was able to temporarily solve the problem by turning corsHandler off.
# nuxt.config.ts
# It worked.
export default {
routeRules: {
security: {
+ corsHandler: false // This temporarily solved the problem.
},
headers: {
'Access-Control-Allow-Origin': ["https://hthompson.dev", "https://*.hthompson.dev"]
}
}
},
}
However, it did not solve the root of the problem and I am still investigating the cause. š¤
Sorry for not getting back to you sooner, @myaaaapon; I have not tried that. Though, with disabling the corsHandler, will that not affect the other security settings set by nuxt-security?
As another note, I don't have routeRules in my nuxt.config.ts file. Would I just do the following:
export default defineNuxtConfig({
........................,
routeRules: {
headers: {
'Access-Control-Allow-Origin': ["https://hthompson.dev/", "https://*.hthompson.dev"]
}
},
security: {
corsHandler: false,
...........
},
............
});
Sorry for not getting back to you sooner, @myaaaapon; I have not tried that. Though, with disabling the
corsHandler, will that not affect the other security settings set bynuxt-security?As another note, I don't have
routeRulesin mynuxt.config.tsfile. Would I just do the following:export default defineNuxtConfig({ ........................, routeRules: { headers: { 'Access-Control-Allow-Origin': ["https://hthompson.dev/", "https://*.hthompson.dev"] } }, security: { corsHandler: false, ........... }, ............ });
Thank you StrangeRanger to open this issue, actually I have facing with this few days ago and struck on this. After try setting the cors in routeRules -> it worked on windows browser and android. But I wondering have you tried on ios browser?
I always get 403 once request api except change the 'Access-Control-Allow-Origin' to '*'. Need something help.
Having the same issue here, would be nice if this can get fixed.
@Morgbn would you be able to help here? :)
Hello, don't know how I can help,
the reproduction link is down.
But note that https://*.hthompson.dev must be a regex.
Also, I have a similar setup that works, maybe it can help :
routeRules: {
'/api/contact/': {
csurf: false,
security: {
enabled: true,
corsHandler: {
origin: process.env.CONTACT_ORIGIN, // for example https://hthompson.dev
methods: ['POST', 'OPTIONS']
}
}
}
}
if I do:
curl 'http://localhost:3300/api/contact' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'Origin: https://hthompson.dev' \
--data-raw '{}' \
--verbose
I have < access-control-allow-origin: https://hthompson.dev
Useful links: h3 handleCors function calling appendCorsHeaders calling createOriginHeaders calling isCorsOriginAllowed
Thanks for the help @Morgbn . You are a š
@whitersun Thank you for your reply. Your workaround works for me only if I specify the route (i.e., '/*'), which I realized from @Morgbn's example. I at least have a temporary solution, thanks to you both; I appreciate it.
@Morgbn Unfortunately, your method didn't work for me. As for your curl command, the output displays < access-control-allow-origin: https://hthompson.dev even if I don't apply your suggested configurations. I found this resulted from specifying -H 'Origin: https://hthompson.dev' \, and while that's great, it doesn't accurately reflect what happens when accessing my website from a browser. Though when I applied the workaround provided by @whitersun and execute the curl command, with and without the -H 'Origin: https://hthompson.dev' \ option, I now get < access-control-allow-origin: https://hthompson.dev, https://*.hthompson.dev.
@Baroshem, I just wanted to ping you in this response in case it provided any helpful information. I've also provided my current nuxt.config.ts file below.
nuxt.config.ts:
// https://nuxt.com/docs/api/configuration/nuxt-config
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
export default defineNuxtConfig({
plugins: [
process.env.NODE_ENV !== "development"
? "plugins/production/vue-matomo.client.js"
: "",
process.env.NODE_ENV !== "development"
? "plugins/production/cloudflare.client.js"
: "",
].filter(Boolean),
devtools: { enabled: true },
build: {
transpile: ["vuetify"],
},
modules: [
"@nuxt/eslint",
"nuxt-security",
"@nuxt/devtools",
(_options, nuxt) => {
nuxt.hooks.hook("vite:extendConfig", (config) => {
config.plugins.push(vuetify({ autoImport: true }));
});
},
],
routeRules: {
"/*": {
headers: {
"Access-Control-Allow-Origin": [
"https://hthompson.dev",
"https://*.hthompson.dev",
],
},
},
},
security: {
enabled: true,
strict: true,
nonce: true,
corsHandler: false,
//corsHandler: {
// origin: ["https://hthompson.dev", "https://*.hthompson.dev"],
//},
allowedMethodsRestricter: {
methods: ["GET", "HEAD", "OPTIONS"],
},
headers: {
crossOriginEmbedderPolicy:
process.env.NODE_ENV === "development" ? "unsafe-none" : "require-corp",
contentSecurityPolicy: {
"default-src": ["'self'"],
"img-src": ["'self'", "blob:"],
"style-src": ["'self'", "https:", "'unsafe-inline'"],
"connect-src": ["'self'", "https://analytics.hthompson.dev"],
"script-src": [
"'self'",
"https:",
"'unsafe-inline'",
"'strict-dynamic'",
"'nonce-{{nonce}}'",
"https://analytics.hthompson.dev",
"https://files.hthompson.dev/scripts/tracking.js",
"https://static.cloudflareinsights.com",
],
},
referrerPolicy: "same-origin",
strictTransportSecurity: {
maxAge: 31536000,
includeSubdomains: true,
preload: true,
},
xContentTypeOptions: "nosniff",
xFrameOptions: "SAMEORIGIN",
xXSSProtection: "1; mode=block",
},
hidePoweredBy: true,
},
vite: {
vue: {
template: {
transformAssetUrls,
},
},
},
css: ["~/assets/css/main.css"],
telemetry: false,
compatibilityDate: "2024-10-19",
});
Here is a link to easily display the headers of the current version of my website to see it in action: https://securityheaders.com/?q=hthompson.dev&followRedirects=on
Adding to @StrangeRanger answer, /* adds access-control-allow-origin to all base level routes. In case you have nested routes, you need to add /*/*... depending on level of nesting you have in your directory.
Same here this looks like a bug
Could it be an upstream issue with H3?
I don't know, but the fact is there strange behavior where access-control-allow-origin not respects what you have in config.
Expectation is to set nuxt-security and get CORS support in all endpoints including Nuxt server side /api.
If /api endpoints not supported - then it should be clearly mentioned here https://nuxt-security.vercel.app/middleware/cors-handler
The workaround with corsHandler disabling not an option.
Not sure why maintainers ignore this issues, it's pretty critical bug.
Hey @Hronom
Please remember that this is an Open Source project so contributions are always welcome. If you have an idea how to solve it, feel free to create a Pull Request and I will be happy to review it and merge it to unblock you :)
@Baroshem ok, so I created simple project to check issue https://github.com/Hronom/nuxt-security-playground/tree/main CSRF disabled.
When I send:
curl --location --request POST 'http://localhost:3000/api/test' \
--header 'Origin: http://superfish:3000' \
--header 'Cookie: csrf=3e4e9237-a674-4b2a-9a02-1620863d83f6'
Response:
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /api/test HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.5.0
> Accept: */*
> Origin: http://superfish:3000
> Cookie: csrf=3e4e9237-a674-4b2a-9a02-1620863d83f6
>
< HTTP/1.1 200 OK
< referrer-policy: no-referrer
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< x-download-options: noopen
< x-frame-options: DENY
< x-permitted-cross-domain-policies: none
< x-xss-protection: 0
< access-control-allow-origin: http://superfish:3000
< vary: origin
< access-control-allow-credentials: true
< content-type: application/json
< date: Wed, 17 Sep 2025 00:50:47 GMT
< connection: close
< content-length: 30
<
{
"status": "ok from post"
* Closing connection
}
Looks like ok.
But when I use cURL:
curl --location --request POST 'http://localhost:3000/api/test' \
--header 'Cookie: csrf=3e4e9237-a674-4b2a-9a02-1620863d83f6' -v
Response:
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /api/test HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< referrer-policy: no-referrer
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< x-download-options: noopen
< x-frame-options: DENY
< x-permitted-cross-domain-policies: none
< x-xss-protection: 0
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-type: application/json
< date: Wed, 17 Sep 2025 00:49:27 GMT
< connection: close
< content-length: 30
<
{
"status": "ok from post"
* Closing connection
}
My expectation was to not have access-control-allow-origin: *(more clean implementation) for when origin is not added(e.g. not a CORS request).
Like for example in this cURL:
curl --location --request POST 'http://localhost:3000/api/test' \
--header 'Origin: http://localhost:3000' \
--header 'Cookie: csrf=3e4e9237-a674-4b2a-9a02-1620863d83f6'
Response
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /api/test HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.5.0
> Accept: */*
> Origin: http://localhost:3000
> Cookie: csrf=3e4e9237-a674-4b2a-9a02-1620863d83f6
>
< HTTP/1.1 200 OK
< referrer-policy: no-referrer
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< x-download-options: noopen
< x-frame-options: DENY
< x-permitted-cross-domain-policies: none
< x-xss-protection: 0
< access-control-allow-credentials: true
< content-type: application/json
< date: Wed, 17 Sep 2025 01:04:23 GMT
< connection: close
< content-length: 30
<
{
"status": "ok from post"
* Closing connection
}
No access-control-allow-origin header at all.
I guess it's fine in terms of security, but maybe confusing as people expect to see access-control-allow-origin: http://superfish:3000 e.g. what was set in corsHandler.origin