Zeit (Vercel) Теперь бессерверные аутентифицированные запросы не работают из-за CORS

Я не могу правильно обрабатывать проблемы CORS при выполнении либо _1 _ / _ 2 _ / _ 3_ запросов из браузера, отправляющих заголовок Authorization с Bearer token (это правильно работает вне браузера и для GET запросов) в Zeit Now serverless.

Я использую Auth0 для авторизации, если это помогает.


Это мой раздел now.json заголовков, я перепробовал много их комбинаций, но ни один из них не преуспел в браузере.

Заголовки в now.json


  1. Я безуспешно пытался использовать пакет npm cors
  2. Пытался добавить routes в now.json
  3. Пытался установить заголовки в верхней части бессерверной функции, используя res.addHeader()
  4. Также пробовал обрабатывать OPTIONS запрос вручную, выполняя следующие варианты:

Пользовательская обработка метода 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.

Не уверен, что я ошибаюсь и как с этим справиться.


person GMaiolo    schedule 14.04.2020    source источник
comment
Как выглядит ваш complete now.json?   -  person Isaac Pak    schedule 09.05.2020


Ответы (5)


Мне удалось обойти эту проблему с помощью микро-коров.

Я проверил его код, и он не отличается многое из того, что я пытался сделать сам, используя res.setHeader вручную, наверное, я что-то упустил.

Тем не менее я не понимаю, почему настройки в now.json работали некорректно, и мне нужно выполнить это вручную в бессерверной функции.

В любом случае, если кто-то еще найдет этот пост, я получу что-то вроде этого:

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

Я, вероятно, попробую еще раз с самописным решением, чтобы лучше понять, что пошло не так, а также потому, что мне не нужна дополнительная зависимость.

Обновлю этот ответ, если я это сделаю.


Изменить. Проверив немного глубже, я обнаружил, что еще одна проблема заключалась в том, что библиотека express-jwt специально изменяла объект res, когда анализ jwt не удался.

У меня было небольшое промежуточное ПО, которое все ломало, делая:

await authValidateMiddleware(req, res);

Когда этот await потерпел неудачу, он сломал всю строку, потому что express-jwt изменил заголовки res неосознанно (установив ошибку), а затем я попытался установить заголовки res вручную, пытаясь самостоятельно обработать ошибку, поэтому возникли проблемы с изменением " заголовки res более одного раза ".

person GMaiolo    schedule 16.04.2020
comment
У вас есть другое решение для этого? У меня тоже проблема с корсом. - person keisaac; 09.05.2020
comment
@keisaac, вы используете какое-либо промежуточное ПО для экспресс-доставки? Для авторизации или что-то в этом роде? Если это так, эти промежуточные программы также изменяют объект res, поэтому вам может потребоваться обходное решение - person GMaiolo; 09.05.2020
comment
Я не использую экспресс, я следую руководству по Vercel с помощью помощников. В конечном итоге я использую setHeader, но столкнулся с другой проблемой: у меня было undefined, если я пытаюсь получить доступ к телу. - person keisaac; 10.05.2020

У меня возникла аналогичная проблема, и проблема была решена путем добавления заголовка к маршрутам следующим образом:

"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

person illiteratewriter    schedule 24.04.2020
comment
Можно ли этого добиться и с помощью аксиомов? или это должен быть файл JSON? - person Matthew Francis; 14.05.2020
comment
Спасибо! Заголовки вроде бы отправлены, но я получаю 500 ОШИБКА каждый раз, когда делается почтовый запрос. Не могли бы вы проверить мой now.json и мой server.js на codepen.io/mattfrancis888/pen/vYNPRZW? - person Matthew Francis; 24.05.2020

У меня очень похожие проблемы с CORS и бессерверной функцией Vercel.

После множества попыток попыток → сбой я просто нашел решения для этого.


Решения

TLDR

Самое простое решение, просто используя микро-корсы.

И реализация чего-то вроде:

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 для обработки заголовков запросов

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 в его значение.

то в бессерверной функции вам все равно нужно обрабатывать предполетный запрос также

/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, это очень простой код, вы можете понять, что он сделает менее чем за несколько минут, поэтому я не беспокоился о добавлении этого в свою зависимость.

person hamcompe    schedule 27.05.2020

Я был примерно в такой же ситуации. У меня есть пара бессерверных функций в Vercel (сейчас), и я хотел, чтобы они были доступны для всех в любом месте. Я решил, что это похоже на ответ @ неграмотного писателя.

Во-первых, у меня в корне моего проекта есть 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 к маршруту, указывая, что CORS разрешен.

  • "continue": true

Продолжает поиск других совпадений маршрута после применения заголовков CORS. Это позволяет нам применять заголовки CORS ко всем маршрутам вместо того, чтобы делать это для каждого маршрута. Например, теперь к /api/auth/login и /api/main/sub/resource будут применяться заголовки CORS.

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

Эта конфигурация перехватывает все HTTP/OPTIONS запросы, что является предполетной проверкой CORS, и перенаправляет их специальному обработчику на /api/cors.

Последний пункт разбивки конфигурации маршрутов приводит нас к функции /api/cors.ts. Обработчик выглядит так:

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

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

Этот обработчик в основном принимает предварительный OPTIONS запрос CORS и отвечает 200/OK на него, указывая клиенту «Да, мы открыты для бизнеса CORS».

person ful-stackz    schedule 10.05.2020
comment
Вам нужно создать now.JSON? это можно сделать через аксиомы? - person Matthew Francis; 14.05.2020
comment
@MatthewFrancis Я не совсем понимаю, что вы имеете в виду, делая это с помощью аксиомов. Не могли бы вы уточнить? Я использовал axios только как клиентскую библиотеку для выполнения HTTP-запросов. Здесь все о коде на стороне сервера, получении и обработке таких запросов. Однако, если вы хотите настроить CORS для бессерверных функций Vercel, тогда создание now.json - единственный способ (о котором я пока знаю). - person ful-stackz; 15.05.2020

Принятый ответ у меня не сработал. Однако теперь кажется, что Vercel обновил свой совет, с примером кода:

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.end и res.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))
}
person Concrete_Buddha    schedule 16.08.2020