Безопасный доступ третьих лиц к REST API

Применение политик безопасности в веб-приложениях в наши дни «относительно просто» благодаря использованию правильных заголовков в ответах HTTP. Возьмем следующий пример приложения:

  • https://example.org предоставляет своим пользователям внешний интерфейс замечательных примеров приложений. Это домен, который знает пользователь.
  • https://api.example.org обрабатывает всю информацию. Если пользователь входит в систему, то внешний интерфейс отправляет запрос в этот домен и изменяется в соответствии с ответом.

В этом случае ни у кого другого, кроме внешнего интерфейса, не должно быть возможности получить доступ к API. К настоящему времени браузеры пытаются защитить пользователя и пытаются блокировать вредоносные запросы. Такой запрос будет, если пользователь посещает https://malicious.example.org, а веб-приложение попытается получить доступ к https://api.example.org. Чтобы предотвратить это, веб-сервер, предоставляющий API, может отправить заголовок Access-Control-Allow-Origin следующим образом:

Access-Control-Allow-Origin: https://example.org

Таким образом, вредоносный веб-сайт больше не имеет доступа к нашему API, если пользователь использует последний браузер.

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

  • https://staging.example.org, который содержит промежуточную среду внешнего интерфейса.
  • https://payment.example.org, который является внешним платежным провайдером, которому необходим доступ к API для подтверждения платежей.
  • https://localhost:8080 — это локальный сервер, который разработчик использует для локального опробования изменений.

Заголовок Access-Control-Allow-Origin не может содержать несколько доменов, например, разделяя разные домены пробелами или запятыми. Помимо указания одного домена, только '*' является еще одним допустимым параметром, который разрешает доступ отовсюду. И это не безопасный вариант в данном случае.

Поэтому API необходимо проверить источник запроса и соответствующим образом скорректировать поле заголовка. Он может использовать заголовок Origin в запросе, чтобы проверить, кто пытается получить доступ к ресурсу. Если это один из разрешенных доменов, он соответствующим образом устанавливает Access-Control-Allow-Origin. В противном случае он просто устанавливает https://example.org, чтобы запрос блокировался браузером. В следующем примере показано, как проект Laravel может использовать промежуточное ПО для правильной установки этого заголовка:

<?php

class CORSMiddleware
{
   [...]
   /**
    * Add Access Control headers.
    *
    * @param \Illuminate\Http\Request  $request
    * @param \Illuminate\Http\Response $response
    *
    * @return \Illuminate\Http\Response
    */
   protected function addAccessControlHeaders($request, $response)
   {
      $possibleOrigins = [
         'https://example.com',
         'https://api.example.com',
         'https://staging.example.com',
         'https://payment.example.org',
      ];

      if (env('APP_ENV') == 'development') {
         $possibleOrigins[] = 'https://localhost:8080';
      }

      if (in_array($request->header('origin'), $possibleOrigins)) {
         $origin = $request->header('origin');
      } else {
         $origin = 'https://example.com';
      }

      $headers = [
         'Access-Control-Allow-Origin'      => $origin,
         'Vary'                             => 'Origin',
      ];
      
      foreach ($headers as $header => $value) {
         $response->header($header, $value);
      }

      return $response;
   }
}

В дополнение к заголовку Access-Control-Allow-Origin устанавливается заголовок Vary, чтобы браузер знал, что ответ этого API может различаться в зависимости от происхождения. Поэтому он не будет использовать какой-либо кешированный ответ, когда API вызывается с другого внешнего сайта в том же браузере.

Источники: