Наш собственный продукт [Standup] позволяет пользователям автоматически получать отчеты о проделанной работе для своих инженерных групп. Чтобы предоставить такую ​​детальную отчетность, мы должны поддерживать OAuth2 для всех их инструментов (Github, JIRA, Slack и т. Д.).

Наш бэкэнд построен на [Typescript] на Node (Express). Мы используем популярную платформу аутентификации [Паспорт] для обработки всех наших потоков аутентификации и авторизации. Для меня реализация потока OAuth была новинкой, но я предполагал, что это будет работать следующим образом:

  1. Пользователь выбирает сервис для OAuth из нашего веб-приложения, которое отправляет запрос на наш сервер.
  2. Наш сервер отвечает URL-адресом перенаправления, который перенаправляет пользователя на страницу OAuth выбранной службы, например, Jira.
  3. Пользователь входит в свою учетную запись Jira, которая создает токен верификатора.
  4. Затем этот токен верификатора отправляется обратно на наш сервер и обменивается на токен доступа, который завершает поток OAuth.

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

Ошибка: нет Access-Control-Allow-Origin для запрошенного ресурса.

Что это за ошибка и почему мы ее получаем?

Чтобы обеспечить безопасность данных между клиентом и браузером, браузер принимает к сведению каждый фрагмент кода, который он загружает с сервера, отслеживая происхождение сервера. Источник - это комбинация URI, имени хоста и номера порта, взятая из заголовков запроса / ответа.

Эти части работают вместе, чтобы создать отпечаток пальца для сервера, с которого браузер получает информацию. Браузер позволит любому коду, загруженному с сервера, делать последующие запросы обратно к серверу, если и код, выполняющий запрос, и сервер, принимающий запрос; происходят из того же происхождения. Другими словами, с конфигурацией по умолчанию, код, загруженный с одного сервера, может запрашивать информацию только с того же сервера. Такое поведение является результатом одинаковой политики происхождения, которая используется во всех браузерах для обеспечения безопасности данных, хранящихся в браузере.

Существует ряд механизмов, позволяющих обойти политику одного и того же происхождения, один из которых называется CORS - Cross Origin Resource Sharing. В простейшей форме спецификация CORS предлагает набор заголовков, которые можно использовать для разрешения или ограничения связи между браузером и сервером. Я знал, что указанная выше ошибка связана с отсутствующим заголовком CORS, но не понимал, как правильно отформатировать мой запрос, особенно потому, что наш сервер уже был настроен для использования CORS.

Инструменты разработчика Chrome - отличный ресурс для отладки всего, что связано с Интернетом, и он может дать вам представление обо всех запросах от / к браузеру. Когда я попытался проанализировать проблему перенаправления с помощью этого инструмента, я увидел, что мой веб-приложение отправляет запрос, но никогда не получал ответа от сервера. Это особенно сбивало с толку, поскольку в моих журналах сервера было указано, что запрос выполняется ...

Это было чрезвычайно сложно отладить, но после некоторого рытья я понял, что это связано с тем, как Chrome обрабатывает коды состояния 302. На начальном этапе потока OAuth вы будете перенаправлены на сторонний сайт для аутентификации. Промежуточное ПО моего сервера отвечало веб-приложению кодом 302, и Chrome перехватывал этот запрос. Ошибка указывает на то, что запрашиваемый ресурс, на который мой сервер запрашивал у моего браузера перенаправление, (URL-адрес Jira), не имел соответствующих заголовков ответа.

Чтобы решить эту проблему при сохранении CORS, следует обратить внимание на два основных момента. Во-первых, из-за ограничений CORS сервер должен ответить кодом состояния, отличным от 302. Это ограничение применяется для предотвращения автоматического выполнения браузером HTTP-запроса до того, как WebApp получит возможность принять полезную нагрузку перенаправления. Во-вторых, как только веб-приложение получает ответ, содержащий URL-адрес перенаправления, браузер должен быть перенаправлен способом, не подпадающим под действие политики CORS. Это позволит избежать фильтрации по перекрестному происхождению из-за той же политики происхождения.

Для этого мне сначала пришлось изменить код перенаправления, расположенный в промежуточном программном обеспечении паспорта. Паспорт предполагает, что правильный код состояния для отправки в этом сценарии - 302, поэтому измените код перенаправления с:

To:

Теперь, когда мы отправляем код состояния 200, браузер не будет думать, что он должен выполнять автоматическое перенаправление, и теперь веб-приложение получит ответ от сервера, содержащий URL-адрес перенаправления. Следующий шаг - просто перенаправить WebApp на URL-адрес, содержащийся в ответе сервера, используя window.location.assign (url), window.location.href (url) или тот, который я использовал, window.location = (url) .

Когда WebApp читает эту строку кода, браузер выполняет внутреннее перенаправление, на которое не распространяются ограничения CORS. Это означает, что проверка Access-Control-Allow-Header не будет и поток OAuth будет завершен, как и планировалось.

Вышеупомянутое решение отлично работает и позволит WebApp увидеть ответ сервера, содержащий полезную нагрузку URL-адреса перенаправления. Единственная проблема - вносить изменения в библиотеку паспорта, что может быть проблемой. Если вы не возражаете, что перенаправление происходит автоматически и не хотите иметь дело с изменением библиотеки паспорта, есть другой способ сделать это перенаправление без проблем с CORS. Это решение включает в себя упаковку начального запроса к серверу для URL-адреса перенаправления в тег привязки.

<a href="http://request.to.server.for.redirect.url/"></a>

Значение атрибута «href» в теге привязки используется для направления браузера к внешнему или внутреннему ресурсу. В этой реализации мы делаем внешний запрос к серверу, и поскольку гиперссылка уже указывает на внешний ресурс, разрешен ответ, содержащий перенаправление (которое также указывает на внешний ресурс, то есть Jira). Оба этих решения будут работать, чтобы избежать ошибок CORS и успешно завершить поток OAuth.