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

后端 未结 5 1820
梦谈多话
梦谈多话 2021-02-20 02:17

I\'m not able to correctly handle CORS issues when doing either PATCH/POST/PUT requests from the browser sending an Authorization

相关标签:
5条回答
  • 2021-02-20 02:33

    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

    0 讨论(0)
  • 2021-02-20 02:34

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

    0 讨论(0)
  • 2021-02-20 02:38

    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))
    }
    
    0 讨论(0)
  • 2021-02-20 02:41

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

    0 讨论(0)
  • 2021-02-20 02:43

    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.

    0 讨论(0)
提交回复
热议问题