Zeit (Vercel) Now 无服务器身份验证请求因 CORS 而失败

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

当浏览器发送 Authorization [=53= 的 PATCH/POST/PUT 请求时,我无法正确处理 CORS 问题] 在 Zeit Now serverless.

中带有 Bearer token(这在浏览器外部和 GET 请求中正常工作)

如果有帮助,我正在使用 Auth0 作为授权方。


这是我的 now.json header 部分,我已经尝试了很多组合,但在浏览器中都没有成功。


  1. 我尝试使用 npm cors 包但没有成功
  2. 试图在 now.json
  3. 中添加 routes
  4. 尝试使用 res.addHeader()
  5. 在无服务器函数的顶部设置 headers
  6. 还尝试手动处理 OPTIONS 请求,并执行以下变体:

最后,这是我得到的错误

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.

不确定我错了什么或如何正确处理。

我能够使用 micro-cors 绕过这个问题。

我检查了 its code,它与我尝试通过手动使用 res.setHeader 自己尝试做的事情没有太大区别,我猜可能漏掉了一些东西。

然而,我不明白为什么 now.json 中的设置无法正常工作,我需要在无服务器功能中手动执行此操作。

无论如何,万一其他人发现这个 post,我最终得到这样的东西:

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);

我可能会用 self-written 解决方案再试一次,以便更好地理解哪里出了问题,也因为我不想要额外的依赖。

如果我这样做会更新这个答案。


编辑: 深入检查后,我发现另一个问题是库 express-jwt 专门更改 res object 时jwt 解析失败。

我有一个小的中间件,它破坏了一切:

await authValidateMiddleware(req, res);

await 失败时,它破坏了所有内容,因为 express-jwt 在不知不觉中更改了 res headers (设置错误)然后我尝试设置res headers 自己手动尝试正确处理错误,因此抛出有关 "changing the res headers more than once".

的问题

我遇到了类似的问题,通过向路由添加 header 解决了问题,如下所示:

"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"
    }
  ]

记得加continue: true.

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

我的处境几乎相同。我在 Vercel(现在)中有几个无服务器功能,我希望它们可供任何来源的任何人使用。我解决的方法类似于@illiteratewriter's answer.

首先,我的项目根目录中有以下 now.json

{
  "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"
    }
  ]
}

下面是两条路由配置的细目:

{
  "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/(.*)"

匹配去往 /api/*.

的任何请求
  • "headers": [...]

将 CORS headers 应用于路由,表示允许 CORS。

  • "continue": true

在应用 CORS headers 后继续寻找其他路由匹配项。这允许我们将 CORS headers 应用于 所有 路由,而不是必须这样做 per-route。例如,现在 /api/auth/login/api/main/sub/resource 都将应用 CORS headers。

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

此配置所做的是拦截所有 HTTP/OPTIONS 请求,即 CORS pre-flight 检查,并将它们 re-route 到 /api/cors 的特殊处理程序。

路由配置细分的最后一点将我们引向 /api/cors.ts 函数。处理程序如下所示:

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

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

这个处理程序所做的基本上是接受 CORS pre-flight OPTIONS 请求并用 200/OK 响应它,向客户端指示 "Yes, we are open for CORS business."

我对 CORS 和 Vercel 无服务器函数有非常相似的问题。

经过大量 尝试 → 失败 过程后,我找到了解决方案。


解决方案

tldr

最简单的解决方案,只需使用 micro-cors

并有类似的实现;

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);

更长的版本,但没有任何新的依赖关系

使用vercel.json处理请求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"
        }
      ]
    }
  ]
}

经过自己尝试,上述设置中有2个键重要

  1. 您必须将 Access-Control-Allow-Origin 设置为您想要的
  2. Access-Control-Allow-Headers 中,您 必须将 Access-Control-Allow-Origin 包含在其值中。

那么在serverless函数中,你还需要处理pre-flight request

/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
};

我建议通读 micro-cors 中的代码,这是非常简单的代码,您可以在几分钟内理解它会做什么,这让我不担心将其添加到我的依赖。

接受的答案对我不起作用。然而vercel现在似乎有updated their advice,他们的示例代码是:

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)

值得一提的是,我不完全确定 res.endres.send 之间的区别,但为了将响应实际提取到我的前端 (React),我更改了 handler作用于:

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

这让我可以在 React 中摄取为:

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

所以我遇到了同样的问题,通过在 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"]
    }
  ]
}

我缺少 "OPTIONS""PATCH" 方法,添加它们后一切正常。

顺便提一下,这就是我使用 cors 的方式,希望这个答案对某人有所帮助

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