vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.4k stars 26.9k forks source link

What is the deal with Access-Control-Allow-Origin header for Edge functions? #48224

Open ctjlewis opened 1 year ago

ctjlewis commented 1 year ago

Verify canary release

Provide environment information

[blank]

Which area(s) of Next.js are affected? (leave empty if unsure)

Middleware / Edge (API routes, runtime)

Link to the code that reproduces this issue

[blank]

To Reproduce

Try to set Access-Control-Allow-Origin: * on an Edge function request. I ended up taking the initiative of publishing the CORS Edge example as a library only to find that also didn't work.

Describe the Bug

No amount of begging and pleading will set the correct headers: { "Access-Control-Allow-Origin": "*" } header I am asking to be set on the response.

This is not an optional thing. You should not step on our headers. We don't provide headers and expect Vercel to retain veto power over them.

Expected Behavior

The headers should be set as the developer specifies them.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

ctjlewis commented 1 year ago
return cors(
    req,
    new Response(
      stream, 
      {
        status: 200,
        headers: {
          "Access-Control-Allow-Origin": "*",
        }
      }
    )
  );

Like, there's zero excuse for this to not work...

ctjlewis commented 1 year ago

Workaround

This took hours to get stable for CORS with request headers.

The cors snippet (now at nextjs-edge-cors) needs to be reviewed and I don't understand why it's not in the core library either (though preferably, stable).

export const CORS_HEADERS = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-auth0-id-token",
};

export default async function edge(
  req: NextRequest
) {
  // The `cors` snippet says the preflight OPTIONS is handled, but it's not stable!
  if (req.method === "OPTIONS") {
    return new Response(
      null, 
      {
        headers: CORS_HEADERS,
      }
    );
  }

  for (...) {
    try {
      // ...
    } catch (err) {
      return new Response(
        "Error message", 
        { 
          status: 401,
          headers: CORS_HEADERS,
        }
      );
    }
  }

  async function* stream() {
    yield ENCODER.encode("hello ");
    yield ENCODER.encode("world");
  }

  return cors(
    req,
    new Response(
      generateStream(stream), 
      {
        status: 200,
      }
    ),
  );
}
ctjlewis commented 1 year ago

This is still unstable even with this workaround. CORS is not exactly a luxury, it is a pretty important thing to have available - if we are saying Edge Functions are stable, they should be stable.

That CORS snippet needs to be fixed (and added to next/server, there is no need for a redundant external library...).

hieunc229 commented 1 year ago

Can we even enable cors? I spent half a day trying lots of things

ctjlewis commented 1 year ago

Can we even enable cors? I spent half a day trying lots of things

The solution for me was to ensure the headers were set and manually catch the OPTIONS request.

I'll try to ping @leerob about it.

ocodista commented 1 year ago

I'm having the same issue, the example at https://vercel.com/guides/how-to-enable-cors doesn't work.

Any ideas on how to solve it on Edge functions? @ctjlewis @leerob

ctjlewis commented 1 year ago

@ocodista You must manually catch the preflight OPTIONS request, the code in the docs example doesn't work beyond the hello world case.

I will fix it for you and ping you, give me an hour. cc @leerob

ctjlewis commented 1 year ago

Solution

See withCors() and the docs rewrites() config:

https://github.com/SpellcraftAI/nextjs-edge-cors

The easiest way to enable CORS is in your Next config, per the docs:

// next.config.mjs

export const headers = () => {
  return [
    {
      source: "/api/cors/:path*",
      headers: [
        { key: "Access-Control-Allow-Credentials", value: "true" },
        { key: "Access-Control-Allow-Origin", value: "*" },
        { key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
        { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
      ]
    }
  ];
};

Alternatively, you can use the withCors() Middleware wrapper:

// src/middleware.ts
import { withCors } from "nextjs-edge-cors"

export const middleware = withCors();

export const config = {
  matcher: ["/api/cors/:path*"]
};

Advanced

You can wrap existing Middleware or pass CORS options to withCors():

import { withCors } from "nextjs-edge-cors"

export const middleware = withCors(
  otherMiddleware,
  {
    /**
     * Only allow CORS requests from Google.com. Comma separate for multiple
     * values.
     */
    origin: "https://www.google.com"
  }
);

export const config = {
  matcher: ["/api/cors/:path*"]
};
tomelliot commented 11 months ago

You must manually catch the preflight OPTIONS request, the code in the docs example doesn't work beyond the hello world case.

@ctjlewis according to this documentation for the App router

// If OPTIONS is not defined, Next.js will automatically implement OPTIONS and set the appropriate Response Allow header depending on the other methods defined in the route handler.

I'm not seeing that happen - I have to manually define the OPTIONS method. I've also seen people reporting adding a GET method resolves the issue

Could you share the latest recommended approach? Between the docs and the various github issues, I can't tell what is the recommended approach vs a workaround.

Another issue with various approaches to specifying an OPTIONS method: https://github.com/vercel/next.js/discussions/47933

tomelliot commented 7 months ago

I believe this is resolved with https://github.com/vercel/next.js/pull/63264