Zeit (Vercel) Now serverless authenticated requests failing because of CORS

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.


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".