Laravel 5.6 - Passport JWT httponly cookie-аутентификация SPA для самопотребляющего API?

ПРИМЕЧАНИЕ: у меня было 4 щедрости на этот вопрос, но ни один из приведенных ниже ответов не является ответом, необходимым для этого вопроса. Все, что нужно, находится в обновлении 3 ниже, просто для поиска кода Laravel для реализации.


ОБНОВЛЕНИЕ 3: Эта блок-схема - именно тот поток, который я пытаюсь выполнить, все ниже - оригинальный вопрос с некоторыми более старыми обновлениями. Эта блок-схема суммирует все необходимое.

Зеленые части в блок-схеме ниже - это те части, которые я знаю как делать. Красные части вместе с примечаниями к ним - вот то, что я ищу для помощи в использовании кода Laravel.


Я провел много исследований, но информация всегда была короткой и неполной, когда дело доходит до использования Laravel с cookie-файлом JWT httponly для самопотребляющего API (большинство онлайн-уроков показывают только то, что JWT хранится в локальном хранилище, которое не очень безопасно).). Похоже, что файл cookie httponly, содержащий JWT by Passport, должен использоваться для идентификации пользователя на стороне Javascript при отправке с каждым запросом к серверу, чтобы проверить, является ли пользователь тем, кем они себя называют.

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

  1. Laravel Passport (не tymon auth) для генерации зашифрованного JWT и отправки его в виде файла cookie httponly в качестве ответа после входа в систему со стороны JS. Какое промежуточное программное обеспечение использовать? Если токены обновления повышают безопасность, как это реализовать?
  2. JavaScript (например, axios) API-псевдокод, который вызывает конечную точку аутентификации, как cookie-файл httponly передается в бэкэнд и как бэкенд проверяет токен.
  3. Если в одну учетную запись вошли с нескольких устройств, то устройство украдено, как отозвать доступ со всех устройств, прошедших аутентификацию (если пользователь меняет пароль с зарегистрированного устройства, которым он управляет)?
  4. Как бы выглядели методы контроллера "Вход / Регистрация", "Выход из системы", "Смена пароля", "Забыли пароль" для создания / проверки / отзыва токенов?
  5. Интеграция с токеном CSRF.

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

ОБНОВЛЕНИЕ 1:

  1. Обратите внимание, что я попробовал CreateFreshApiToken раньше, но это не сработало, когда дело доходит до отзыва токенов пользователя (для пунктов 3 и 4 выше). Это основано на этом комментарии основного разработчика Laravel, когда речь идет о CreateFreshApiToken промежуточный слой:

Токены JWT, созданные этим промежуточным ПО, нигде не хранятся. Они не могут быть отозваны или "не существуют". Они просто предоставляют способ авторизации ваших вызовов API через cookie-файл laravel_token. Это не связано с токенами доступа. Кроме того: обычно вы не используете токены, выпущенные клиентами в том же приложении, которое их выпускает. Вы бы использовали их в первом или стороннем приложении. Либо используйте промежуточное программное обеспечение, либо клиентские токены, но не оба одновременно.

Таким образом, кажется, что есть возможность удовлетворить пункты 3 и 4, чтобы отозвать токены, это невозможно сделать, если использовать CreateFreshApiToken промежуточное программное обеспечение.

  1. На стороне клиента, кажется Authorization: Bearer <token> это не тот путь, когда нужно иметь дело с безопасным файлом cookie httpOnly. Я думаю, что запрос / ответ должен включать в себя защищенный файл cookie httpOnly в качестве заголовка запроса / ответа, как это основано на документах laravel:

При использовании этого метода аутентификации скаффолдинг JavaScript по умолчанию Laravel указывает Axios всегда отправлять заголовки X-CSRF-TOKEN и X-Requested-With.

headerswindow.axios.defaults.headers.common = {
    'X-Requested-With': 'XMLHttpRequest',
    'X-CSRF-TOKEN': (csrf_token goes here)
};

Это также причина, по которой я ищу решение, которое охватывает все пункты выше. Извиняюсь, я использую Laravel 5.6, а не 5.5.

ОБНОВЛЕНИЕ 2:

Похоже, что комбинация Password Grant/Refresh Token Grant - это то, что нужно. Ищите простое в использовании руководство по внедрению с помощью паролей Grant / Refresh Token Grant.

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

Обновление предоставления токена. Когда сервер выдает токен доступа, он также устанавливает срок действия токена доступа. Обновление разрешения токена используется, когда мы хотим обновить токен доступа после его истечения. В этом случае сервер авторизации отправит токен обновления при выдаче токена доступа, который можно использовать для запроса нового токена доступа.

Я ищу простой в реализации, понятный и целостный ответ, используя комбо " Предоставление пароля / Обновление токена", которое охватывает все части вышеупомянутых оригинальных 5 пунктов с помощью httpOnly безопасных файлов cookie, создания / отзыва / обновления токенов, создания файлов cookie для входа в систему, отмена cookie для выхода из системы, методы контроллера, CSRF и т. д.

4 ответа

Я постараюсь ответить на это в общем виде, чтобы ответ был применим ко всем фреймворкам, реализациям и языкам, потому что ответы на все вопросы могут быть получены из общих спецификаций протокола или алгоритма.

Какой тип гранта OAuth 2.0 мне следует использовать?

Это первое, что нужно решить. Когда дело доходит до SPA, возможны два варианта:

  1. Предоставление кода авторизации (рекомендуется при условии, что секрет клиента хранится на стороне сервера)
  2. Предоставление учетных данных паролем владельца ресурса

Причины, по которым я не упоминаю тип неявного предоставления в качестве опции:

  1. Шаг аутентификации клиента путем предоставления секрета клиента и кода авторизации отсутствует. Так меньше безопасности
  2. Токен доступа отправляется обратно в виде фрагмента URL (чтобы токен не отправлялся на сервер), который будет оставаться в истории браузера.
  3. Если происходит XSS-атака, вредоносный скрипт может очень хорошо отправить токен на удаленный сервер, контролирующий злоумышленника.

(Тип гранта Client Credentials не входит в сферу данного обсуждения, поскольку он используется, когда клиент не действует от имени пользователя. Например, для пакетного задания)

В случае типа предоставления кода авторизации сервер авторизации обычно отличается от сервера ресурсов. Лучше хранить сервер авторизации отдельно и использовать его как общий сервер авторизации для всех SPA в организации. Это всегда рекомендуемое решение.

Здесь (в типе предоставления кода авторизации) поток выглядит следующим образом:

  1. пользователь нажимает кнопку входа на целевой странице SPA
  2. пользователь перенаправляется на страницу входа на сервер авторизации. Идентификатор клиента указывается в параметре запроса URL
  3. Пользователь вводит свои учетные данные и нажимает кнопку входа. Имя пользователя и пароль будут отправлены на сервер авторизации с использованием HTTP POST. Учетные данные следует отправлять в теле или заголовке запроса, а НЕ в URL (так как URL-адреса регистрируются в истории браузера и на сервере приложений). Кроме того, должны быть установлены надлежащие кэширующие заголовки HTTP, чтобы учетные данные не кэшировались: Cache-Control: no-cache, no-store, Pragma: no-cache, Expires: 0
  4. Сервер авторизации аутентифицирует пользователя на основе пользовательской базы данных (скажем, сервера LDAP), где имя пользователя и хэш пароля пользователя (алгоритмы хеширования, такие как Argon2, PBKDF2, Bcrypt или Scrypt) хранятся со случайной солью.
  5. При успешной аутентификации сервер авторизации получит из своей базы данных URL-адрес перенаправления по указанному идентификатору клиента в параметре запроса URL-адреса. URL перенаправления - это URL сервера ресурсов.
  6. Затем пользователь будет перенаправлен на конечную точку сервера ресурсов с кодом авторизации в параметре запроса URL.
  7. Затем сервер ресурсов отправит HTTP-запрос POST серверу авторизации для получения токена доступа. Код авторизации, идентификатор клиента, секрет клиента должны быть указаны в теле запроса. (Следует использовать соответствующие заголовки кэширования, как указано выше)
  8. Сервер авторизации возвращает токен доступа и токен обновления в теле или заголовке ответа (с соответствующим заголовком кэширования, как упомянуто выше)
  9. Сервер ресурсов теперь перенаправляет пользователя (код ответа HTTP 302) на URL-адрес SPA, устанавливая соответствующие файлы cookie (что будет подробно объяснено ниже)

С другой стороны, для типа предоставления учетных данных пароля владельца ресурса сервер авторизации и сервер ресурсов совпадают. Его проще реализовать, а также можно использовать, если оно соответствует требованиям и срокам реализации.

Также см. Мой ответ по этому вопросу для получения дополнительной информации о типе гранта Владельца ресурса.

Здесь может быть важно отметить, что в SPA все защищенные маршруты должны быть включены только после вызова соответствующей службы, чтобы убедиться, что в запросе присутствуют действительные токены. Точно так же защищенные API должны также иметь соответствующие фильтры для проверки токенов доступа.

Почему я не должен хранить токены в браузере localalstorage или sessionstorage?

Многие SPA хранят токен доступа и / или обновляют в локальном хранилище браузера или сеансе хранения. Я думаю, что мы не должны хранить токены в этих хранилищах браузера:

  1. Если происходит XSS, вредоносный скрипт может легко прочитать токены оттуда и отправить их на удаленный сервер. На удаленном сервере или злоумышленнике не возникнет проблем с выдачей себя за пользователя-жертву.

  2. localstorage и sessionstorage не являются общими для поддоменов. Таким образом, если у нас есть два SPA, работающих на разных поддоменах, мы не получим функцию единого входа, потому что токен, сохраненный одним приложением, не будет доступен для другого приложения в организации.

Однако, если токены все еще хранятся в любом из этих хранилищ браузера, необходимо включить надлежащий отпечаток пальца. Отпечаток пальца - это криптографически сильная случайная строка байтов. Строка Base64 необработанной строки будет затем сохранена в HttpOnly, Secure, SameSite cookie с префиксом имени __Secure-, Правильные значения для Domain а также Path атрибутов. Хэш строки SHA256 также будет передан в заявке JWT. Таким образом, даже если атака XSS отправляет токен доступа JWT удаленному серверу, управляемому злоумышленником, он не может отправить исходную строку в файле cookie, и в результате сервер может отклонить запрос на основании отсутствия файла cookie.

Замечания:

  1. SameSite=strict гарантирует, что данный файл cookie не будет сопровождать запросы, отправленные с другого сайта (AJAX или по следующей гиперссылке). Проще говоря - любой запрос, исходящий от сайта с тем же "регистрируемым доменом", что и целевой сайт, будет разрешен. Например, если " http://www.example.com/ " является именем сайта, регистрируемый домен - "example.com". Для получения дополнительной информации обратитесь к Ссылка №. 3 в последнем разделе ниже. Таким образом, он обеспечивает некоторую защиту от CSRF. Однако это также означает, что если указан URL-адрес форума, прошедший проверку пользователь не может перейти по ссылке. Если это серьезное ограничение для приложения, SameSite=lax может использоваться, что позволит межсайтовых запросов, пока методы HTTP безопасны, а именно. ПОЛУЧИТЕ, ГОЛОВУ, ВАРИАНТЫ И TRACE. Поскольку CSRF основан на небезопасных методах, таких как POST, PUT, DELETE, lax по-прежнему обеспечивает защиту от CSRF

  2. Чтобы разрешить передачу cookie во всех запросах на любой поддомен "example.com", атрибут домена cookie должен быть установлен как "example.com"

Почему я должен хранить токен доступа и / или обновлять токен в куки?

  1. При хранении токенов в куки мы можем установить куки как secure а также httpOnly, Таким образом, если XSS происходит, вредоносный скрипт не может прочитать и отправить их на удаленный сервер. XSS может по-прежнему выдавать себя за пользователя из браузера пользователя, но если браузер закрыт, скрипт не сможет нанести дальнейший ущерб. secure флаг гарантирует, что токены не могут быть отправлены через незащищенные соединения - SSL/TLS является обязательным
  2. Установка корневого домена в куки как domain=example.comнапример, гарантирует, что файл cookie доступен во всех поддоменах. Таким образом, разные приложения и серверы в организации могут использовать одни и те же токены. Логин требуется только один раз

Как мне проверить токен?

Токены обычно являются токенами JWT. Обычно содержимое токена не является секретным. Следовательно они обычно не зашифрованы. Если требуется шифрование (возможно, из-за того, что некоторая конфиденциальная информация также передается внутри токена), существует отдельная спецификация JWE. Даже если шифрование не требуется, нам необходимо обеспечить целостность токенов. Никто (пользователь или злоумышленник) не должен иметь возможности изменять токены. Если они это сделают, сервер должен быть в состоянии обнаружить это и отклонить все запросы с поддельными токенами. Чтобы обеспечить эту целостность, токены JWT имеют цифровую подпись с использованием такого алгоритма, как HmacSHA256. Для генерации этой подписи необходим секретный ключ. Сервер авторизации будет владеть и защищать секрет. Всякий раз, когда api сервера авторизации вызывается для проверки токена, сервер авторизации пересчитывает HMAC на переданном токене. Если он не совпадает с входным HMAC, он возвращает отрицательный ответ. Токен JWT возвращается или сохраняется в кодированном формате Base64.

Однако для каждого вызова API на сервере ресурсов сервер авторизации не участвует в проверке токена. Сервер ресурсов может кэшировать токены, выданные сервером авторизации. Сервер ресурсов может использовать сетку данных в памяти (то есть Redis) или, если все не может быть сохранено в ОЗУ, базу данных на базе LSM (то есть Riak с уровнем DB) для хранения токенов.

Для каждого вызова API сервер ресурсов проверяет свой кэш.

  1. Если токен доступа отсутствует в кэше, API должны возвращать соответствующее ответное сообщение и код ответа 401, чтобы SPA мог перенаправить пользователя на соответствующую страницу, где пользователю будет предложено повторно войти в систему.

  2. Если токен доступа действителен, но срок его действия истек (обратите внимание, что токены JWT обычно содержат имя пользователя и дату окончания срока действия, помимо прочего), API должны возвращать соответствующее ответное сообщение и код ответа 401, чтобы SPA мог вызывать соответствующий API сервера ресурсов для обновить токен доступа с помощью маркера обновления (с соответствующими заголовками кэша). Затем сервер вызывает сервер авторизации с токеном доступа, токеном обновления и секретом клиента, а сервер авторизации может возвращать новые токены доступа и обновления, которые в конечном итоге передаются в SPA (с соответствующими заголовками кэша). Затем клиент должен повторить исходный запрос. Все это будет обрабатываться системой без вмешательства пользователя. Отдельный файл cookie может быть создан для хранения токена обновления, аналогичного токену доступа, но с соответствующим значением для Path атрибут, чтобы токен обновления не сопровождал каждый запрос, а был доступен только в запросах на обновление

  3. Если токен обновления недействителен или срок его действия истек, API должны возвращать соответствующее ответное сообщение и код ответа 401, чтобы SPA мог перенаправить пользователя на соответствующую страницу, где пользователю будет предложено повторно войти в систему.

Зачем нам два токена - токен доступа и токен обновления?

  1. Маркер доступа обычно имеет короткий срок действия, скажем, 30 минут. Обновление токена обычно длится более 6 месяцев. Если токен доступа каким-либо образом скомпрометирован, злоумышленник может выдать себя за пользователя-жертву только до тех пор, пока токен доступа действителен. Поскольку у злоумышленника не будет секрета клиента, он не сможет запросить у сервера авторизации новый токен доступа. Однако злоумышленник может запросить сервер ресурсов на обновление токена (как в приведенной выше настройке, запрос на обновление проходит через сервер ресурсов, чтобы избежать сохранения секретного ключа клиента в браузере), но с учетом других принятых шагов это маловероятно и, более того, сервер может принять дополнительные меры защиты на основе IP-адреса.

  2. Если этот короткий срок действия токена доступа помогает серверу авторизации отозвать выданные токены от клиентов, если это необходимо. Сервер авторизации также может поддерживать кеш выданных токенов. Администраторы системы могут при необходимости пометить токены определенных пользователей как отозванные. По истечении срока действия маркера доступа, когда сервер ресурсов перейдет на сервер авторизации, пользователь будет вынужден снова войти в систему.

А как насчет CSRF?

  1. Чтобы защитить пользователя от CSRF, мы можем следовать подходу, используемому в таких средах, как Angular (как описано в документации Angular HttpClient, где сервер должен отправлять не-HttpOnly cookie (другими словами, читаемый cookie), содержащий уникальный непредсказуемый значение для этого конкретного сеанса. Это должно быть криптографически сильное случайное значение. Затем клиент всегда будет читать файл cookie и отправлять значение в настраиваемом заголовке HTTP (кроме запросов GET & HEAD, которые не должны иметь какой-либо логики изменения состояния. Примечание CSRF не может читать что-либо из целевого веб-приложения из-за одной и той же политики происхождения), чтобы сервер мог проверить значение из заголовка и файла cookie. Поскольку междоменные формы не могут прочитать файл cookie или установить собственный заголовок, в случае запросов CSRF пользовательское значение заголовка будет отсутствовать, и сервер сможет обнаружить атаку

  2. Чтобы защитить приложение от входа в CSRF, всегда проверяйте referer заголовок и принимать запросы только тогда, когда referer является доверенным доменом Если referer заголовок отсутствует или домен не занесен в белый список, просто отклоните запрос. При использовании SSL / TLS referrer обычно присутствует. Целевые страницы (которые в основном являются информационными и не содержат форму входа или какой-либо защищенный контент) могут быть немного смягчены и разрешать запросы с отсутствующими referer заголовок

  3. TRACE HTTP-метод должен быть заблокирован на сервере, так как его можно использовать для чтения httpOnly печенье

  4. Также установите заголовок Strict-Transport-Security: max-age=<expire-time>; includeSubDomains Разрешить только защищенные соединения, чтобы предотвратить перезапись cookie-файлов CSRF из поддоменов любым посредником

  5. Кроме того, SameSite следует использовать настройку, как указано выше

Наконец, SSL/TLS является обязательным для всех видов связи - так как на сегодняшний день версии TLS ниже 1.1 неприемлемы для соответствия PCI/DSS. Для обеспечения прямой секретности и аутентифицированного шифрования должны использоваться надлежащие комплекты шифров. Кроме того, токены доступа и обновления должны быть помещены в черный список, как только пользователь явно нажмет "Выход", чтобы предотвратить любую возможность неправильного использования токенов.

Рекомендации

  1. RFC 6749 - OAuth2.0
  2. Шпаргалка OWASP JWT
  3. SameSite Cookie IETF Draft
  4. Префиксы печенья
  5. RFC 6265 - Cookie

Laravel Passport JWT

  1. Чтобы использовать эту функцию, вам необходимо отключить сериализацию файлов cookie. В Laravel 5.5 есть проблема с сериализацией / десериализацией значений cookie. Вы можете прочитать больше об этом здесь ( https://laravel.com/docs/5.5/upgrade)

  2. Удостоверься что

    • у тебя есть <meta name="csrf-token" content="{{ csrf_token() }}"> в вашей голове шаблона лезвия

    • axios настроен на использование csrf_token для каждого запроса.

Вы должны иметь что-то подобное в resources/assets/js/bootstrap.js

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
  window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
  console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
  1. Настройка маршрутов аутентификации описана здесь ( https://laravel.com/docs/5.5/authentication)
  2. Настройка паспорта поясняется здесь ( https://laravel.com/docs/5.5/passport).

Важными частями являются:

  • добавить Laravel\Passport\HasApiTokens черта к вашему User модель
  • установить driver вариант api проверка подлинности для passport в вашем config/auth.php
  • добавить \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, промежуточное программное обеспечение для вашего web группа промежуточного программного обеспечения в app/Http/Kernel.php

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

  1. Сделать POST запрос к /login передавая ваши полномочия. Вы можете сделать запрос AJAX или отправить обычную форму.

Если запросом на вход является AJAX (с использованием axios), ответными данными будет HTML, но вас интересует код статуса.

axios.get(
  '/login, 
  {
    email: 'user@email.com',
    password: 'secret',
  },
  {
    headers: {
      'Accept': 'application/json', // set this header to get json validation errors.
    },
  },
).then(response => {
  if (response.status === 200) {
      // the cookie was set in browser
      // the response.data will be HTML string but I don't think you are interested in that
    }
    // do something in this case
}).catch(error => {
  if (error.response.status === 422) {
    // error.response.data is an object containing validation errors
  }
  // do something in this case
});

При входе в систему сервер находит пользователя по предоставленным учетным данным, генерирует токен на основе информации о пользователе (идентификатор, адрес электронной почты...) (этот токен нигде не сохраняется), затем сервер возвращает ответ с зашифрованным файлом cookie, который содержит сгенерированный токен,

  1. Сделайте вызов API для защищенного маршрута.

Предполагая, что у вас есть защищенный маршрут

Route::get('protected', 'SomeController@protected')->middleware('auth:api');

Вы можете сделать ajax-вызов, используя axios, как обычно. Файлы cookie устанавливаются автоматически.

axios.get('/api/protected')
  .then(response => {
    // do something with the response
  }).catch(error => {
    // do something with this case of error
  });

Когда сервер получает вызов, расшифровывает запрос laravel_cookie и получить информацию о пользователе (например: id, email...). Затем с этой информацией о пользователе выполняется поиск в базе данных, чтобы проверить, существует ли пользователь. Если пользователь найден, он получает доступ к запрошенному ресурсу. Остальное 401 возвращается.

Аннулирование токена JWT. Когда вы упоминаете комментарий, вам не нужно об этом беспокоиться, так как этот токен нигде не сохраняется на сервере.

Обновить

Относительно пункта 3 Laravel 5.6 Auth имеет новый метод logoutOtherDevices, Вы можете узнать больше здесь ( https://laracasts.com/series/whats-new-in-laravel-5-6/episodes/7), поскольку документация очень легкая.

Если вы не можете обновить свою версию Laravel, вы можете проверить, как это делается в 5.6 и построить свою собственную реализацию для 5.5

Пункт 4 из вашего вопроса. Посмотрите на контроллеры, найденные в app/Http/Controllers/Auth,

Что касается access_tokens и refresh_tokens, это совершенно другой и более сложный подход. Вы можете найти много онлайн-уроков, объясняющих, как это сделать.

Я надеюсь, что это помогает.

PS. Счастливого Нового года!!:)

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

  1. Я использовал предоставление пароля для создания токена доступа и обновления токена. Вы можете выполнить следующие действия, чтобы настроить паспорт и реализовать его. В вашем методе входа в систему вы должны проверить учетные данные пользователя и сгенерировать токены и прикрепить cookie( прикрепление cookie к ответу) к ответу. Если вам нужно, я могу привести несколько примеров.
  2. Я добавил два промежуточного программного обеспечения для CORS(обработка заголовков входящих запросов) и для проверки, является ли входящий токен доступа действительным или нет, если он недействителен, генерирует токен доступа из сохраненного токена обновления ( токен обновления). Я могу показать вам пример.
  3. После входа в систему все запросы со стороны клиента должны содержать заголовок авторизации (Authorization: Bearer <token>).

Дайте мне знать, если вы согласны с вышеуказанными пунктами.

  • Laravel Passport - это реализация OAuth-сервера The PHP League
  • Тип предоставления пароля может использоваться для аутентификации по имени пользователя и паролю
  • Не забудьте скрыть свои учетные данные клиента, сделав запрос авторизации в прокси
  • Сохраните токен обновления в файле cookie HttpOnly, чтобы минимизировать риск XSS-атак.

Больше информации вы можете увидеть здесь

http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/

Другие вопросы по тегам