13

I'm not able to correctly handle CORS issues when doing either PATCH/POST/PUT requests from the browser sending an Authorization header with a Bearer token (this works correctly outside of the browser and for GET requests) in Zeit Now serverless.

I'm using Auth0 for the authorization side if that helps.


This is my now.json headers section, I've tried a lot of combinations for these, but neither succeeded from the browser.

Headers in now.json


  1. I tried using npm cors package without success
  2. Tried to add routes in now.json
  3. Tried setting headers at the top of the serverless function using res.addHeader()
  4. Also tried handling OPTIONS request manually doing variations of this:

OPTIONS method custom handling

Finally, this is the error that I get

Access to XMLHttpRequest at 'https://api.example.org/api/users' from origin 'https://example.org' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Not sure what I'm wrong or how to handle this properly.

GMaiolo
  • 4,207
  • 1
  • 21
  • 37

7 Answers7

6

I have pretty much similar issues with CORS and Vercel serverless function.

After lots of try → failed process I just found solutions for this.


Solutions

tldr

The simplest solution, just using micro-cors.

And having an implementation something like;

import { NowRequest, NowResponse } from '@now/node';
import microCors from 'micro-cors';

const cors = microCors();

const handler = (request: NowRequest, response: NowResponse): NowResponse => {
  if (request.method === 'OPTIONS') {
    return response.status(200).send('ok');
  }

  // handle incoming request as usual
};

export default cors(handler);

Longer version, but without any new dependency

using vercel.json to handle request headers

vercel.json

{
  "headers": [
    {
      "source": "/.*",
      "headers": [
        {
          "key": "Access-Control-Allow-Origin",
          "value": "*"
        },
        {
          "key": "Access-Control-Allow-Headers",
          "value": "X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept"
        },
        {
          "key": "Access-Control-Allow-Credentials",
          "value": "true"
        }
      ]
    }
  ]
}

After self tried, there are 2 keys important in an above setting,

  1. You must set Access-Control-Allow-Origin as what you want
  2. In Access-Control-Allow-Headers you must include Access-Control-Allow-Origin into its value.

then in serverless function, you still need to handle pre-flight request as well

/api/index.ts

const handler = (request: NowRequest, response: NowResponse): NowResponse => {
  if (request.method === 'OPTIONS') {
    return response.status(200).send('ok');
  }

  // handle incoming request as usual
};

I would suggest to read through the code in micro-cors, it's very simple code, you can understand what it'll do in under few minutes, which makes me didn't concern about adding this into my dependency.

hamcompe
  • 61
  • 2
5

I was able to bypass this issue using micro-cors.

I checked its code and it doesn't differ that much of what I attempted to do myself by using res.setHeader manually, probably missed something I guess.

Nevertheless I don't understand why the settings in now.json were not working correctly and I need to perform this manually in the serverless function.

Anyways, in case someone else finds this post, I ended up with something like this:

import micro from "micro-cors";

function MyApi(req, res) {
  if (req.method === "OPTIONS") {
    return res.status(200).end();
  }
  // handling other requests normally after this
}

const cors = micro();

export default cors(MyApi);

I'll probably will try again with a self-written solution in order to understand better what went wrong and also because I don't want an extra dependency.

Will update this answer if I do that.


Edit: After checking a bit deeper I found that another issue was the library express-jwt specifically changing the res object when the jwt parse failed.

I had a small middleware that was breaking everything by doing:

await authValidateMiddleware(req, res);

When that await failed, it broke everything down the line because express-jwt changed the res headers unknowingly (setting the error) and then I tried to set the res headers manually trying to handle the error correctly myself, therefore throwing issues about "changing the res headers more than once".

GMaiolo
  • 4,207
  • 1
  • 21
  • 37
  • 1
    Do you have another solution for this? I am also having a problem with cors. – keisaac May 09 '20 at 17:22
  • @keisaac are you using any express middlewares? For auth or something like that? If so those middlewares also change the `res` object so you may need to workaround those – GMaiolo May 09 '20 at 20:10
  • I am not using express, I am following the guide on Vercel using helpers. I end up using setHeader, but I encountered another problem by having undefined if I'm trying to access the body. – keisaac May 10 '20 at 04:49
3

I faced a similar problem and the problem was solved byadding the header to the routes as follows:

"routes": [
    {
      "src": ".*",
      "methods": ["GET", "POST", "OPTIONS"],
      "headers": {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
        "Access-Control-Allow-Credentials": "true"
      },
      "dest": "index.js",
      "continue": true
    },
    {
      "src": "/user/login", "methods": ["POST"], "dest": "index.js"
    }
  ]

remember to add continue: true.

https://github.com/super-h-alt/zeit-now-cors-problems/blob/master/now.json

illiteratewriter
  • 4,155
  • 1
  • 23
  • 44
  • Can this also be achieved by axios? or must it be a JSON file? – Matthew Francis May 14 '20 at 14:16
  • Thank you! The headers seem to be sent, but I'm getting a 500 ERROR whenever a post request is made now. Would you mind checking out my now.json and my server.js at https://codepen.io/mattfrancis888/pen/vYNPRZW? – Matthew Francis May 23 '20 at 21:50
3

I was in pretty much the same situation. I have a couple of serverless functions in Vercel (Now) and I wanted them to be available to anyone at any origin. The way I solved is similar to @illiteratewriter's answer.

First, I have the following now.json at the root of my project:

{
  "routes": [
    {
      "src": "/api/(.*)",
      "headers": {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
        "Access-Control-Allow-Credentials": "true"
      },
      "continue": true
    },
    {
      "src": "/api/(.*)",
      "methods": ["OPTIONS"],
      "dest": "/api/cors"
    }
  ]
}

Here is a breakdown of the two routes configurations:

{
  "src": "/api/(.*)",
  "headers": {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
    "Access-Control-Allow-Credentials": "true"
  },
  "continue": true
}
  • "src": "/api/(.*)"

Matches any request going to /api/*.

  • "headers": [...]

Applies the CORS headers to the route, indicating that CORS is allowed.

  • "continue": true

Continues looking for other route matches after applying the CORS headers. This allows us to apply the CORS headers to all routes, instead of having to do it per-route. For example, now /api/auth/login and /api/main/sub/resource will both have the CORS headers applied.

{
  "src": "/api/(.*)",
  "methods": ["OPTIONS"],
  "dest": "/api/cors"
}

What this config does is to intercept all HTTP/OPTIONS requests, which is the CORS pre-flight check, and re-route them to a special handler at /api/cors.

The last point of the routes configuration breakdown leads us to the /api/cors.ts function. The handler looks like this:

import {NowRequest, NowResponse} from '@now/node';

export default (req: NowRequest, res: NowResponse) => {
  return res.status(200).send();
}

What this handler does is basically accept the CORS pre-flight OPTIONS request and respond with 200/OK to it, indicating to the client "Yes, we are open for CORS business."

Ray Shan
  • 1,646
  • 3
  • 17
  • 25
ful-stackz
  • 302
  • 4
  • 10
  • Do you have to create the now.JSON? can this be done via axios? – Matthew Francis May 14 '20 at 14:16
  • @MatthewFrancis I am not sure exactly what you mean by doing it with axios. Can you please elaborate? I have only used axios as a client-side library for making HTTP requests. Here it's all about server-side code and receiving, and handling such requests. However, if you want to configure CORS for Vercel serverless functions then creating `now.json` is the only way (that I know of so far). – ful-stackz May 15 '20 at 09:57
1

The accepted answer did not work for me. However vercel now appear to have updated their advice, with their example code being:

const allowCors = fn => async (req, res) => {
  res.setHeader('Access-Control-Allow-Credentials', true)
  res.setHeader('Access-Control-Allow-Origin', '*')
  // another option
  // res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS')
  res.setHeader(
    'Access-Control-Allow-Headers',
    'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
  )
  if (req.method === 'OPTIONS') {
    res.status(200).end()
    return
  }
  return await fn(req, res)
}

const handler = (req, res) => {
  const d = new Date()
  res.end(d.toString())
}

module.exports = allowCors(handler)

It's worth saying I'm not entirely sure of the difference between res.end and res.send but to actually ingest the response into my front end (React) I changed the handler function to:

const handler = (req, res) => {
        const d = {data: "Hello World"}; 
        res.send(d)
}

which allowed me to ingest in React as:

function getAPIHelloWorld () {
    let connectStr = "/api"
    fetch(connectStr)
        .then(response => response.json())
        .then(response => {console.log(response.data)})
        .catch(err => console.error(err))
}
Concrete_Buddha
  • 556
  • 4
  • 16
0

So I was facing the same issue and it worked for me by applying the following code inside vercel.json

{
"version": 2,
  "builds": [
    {
      "src": "src/app.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/app.ts",
      "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]
    }
  ]
}

I was missing the "OPTIONS" and "PATCH" methods, after I added them everything works fine.

And to mention, this is how I'm using cors , hope this answer helps somebody

app.use(cors({ origin: ['http://localhost:3000', /\.regenci\.online$/], credentials: true }))
Esc Official
  • 99
  • 10
0

Hoping this could help, I faced the same issue and tried several ways (middleware handler, headers in vercel.json, etc) and none of the above seemed to work until I deleted the project from Vercel and deployed it again.

Then I tried to modify the allowed origin and the CORS failed again, until I deleted the project from Vercel and deployed again with this new config.

=> Seems that if you modify the CORS settings after initial deployment, Vercel may see it as a diff in config (as seen per an alert in the settings of the project) and for some reason CORS setting will not function anymore.

Melladoma
  • 9
  • 2