Как обезопасить токены JWT с помощью серверной части DRF, поддерживающей как мобильных, так и SPA-клиентов?
Я разрабатываю приложение, которое использует фреймворк Django REST для предоставления REST API.
Я намерен защитить его с помощью аутентификации по токену с помощью simple_jwt
.
Пример обзора API:
/auth/login/
: ПоддерживаетPOST
, требует допустимыхusername
иpassword
, возвращает JWTaccess
иrefresh
/auth/refresh/
: ПоддерживаетPOST
, требует действительный токенrefresh
, возвращает новые токены доступа и обновления/protected/endpoint/
: Требуется действительный токен доступа
Из прочитанного о CSRF я сделал вывод:
- CSRF необходим только в том случае, если задействованы файлы cookie, поэтому используйте его только для клиента SPA
- Для сохранения токена JWT:
SPA
: Сохраняется в файле cookiehttpOnly
с параметрамиsecure=true
иsame-origin
, установленными наApp
: Сохраняется, однако постоянное состояние приложения работает
Пока все хорошо, однако это означало бы, что мой REST API должен требовать токены CSRF для запросов, поступающих из SPA, и не требовать / игнорировать токены CSRF для запросов, поступающих от клиентов мобильных приложений.
Поскольку это логически невозможно, я сначала подумал о реализации 2 отдельных API. т.е.:
/spa/auth/...
,/spa/protected/...
: Для клиентов SPA, где для всех конечных точек требуется токен CSRF/mobile/auth/...
,/mobile/protected/...
: Для мобильных клиентов, где все конечные точки не подпадают под действие CSRF.
Но это просто означает, что моя защита от CSRF бесполезна, поскольку злоумышленник может просто нацелиться на мой мобильный API вместо моего SPA API.
Я также немного читал об АВТОМОБИЛЯХ, но не уверен, как это вписывается во все это.
Вопросов:
- Правильно ли я понимаю, что CSFR необходим только при использовании файлов cookie?
- Если ответ на предыдущий вопрос "да", могу ли я вообще не сохранять токен JWT в cookie для SPA, но при этом каким-то образом надежно сохранять вход в SPA во время сеансов браузера и обновлений страницы?
- Если ответ на предыдущий вопрос "нет", то как я могу поддерживать оба типа клиентов, одновременно предотвращая атаки CSRF?
- Если я не могу использовать токены CSRF для SPA, как мне следует хранить файл cookie CSRF (нужно ли мне вообще, чтобы он был в файле cookie?)? Я предполагаю, что для этого потребуется установить как минимум
same-origin
, чтобы избежать утечки в другие домены, но должны ли они также бытьhttpOnly
иsecure=true
? Я читал противоречивые мнения по этому поводу. - Как я должен реализовать CSRF с помощью DRF? В документации для SessionAuthentication упоминается CSRF, но она расплывчата и даже указывает, что реализация DRF CSRF по умолчанию не подходит для представлений входа в систему.
- Как
CORS
вписывается во все это? Могу ли я использовать это для устранения возникающих у меня проблем?
РЕДАКТИРОВАТЬ:
Я думал об этом больше - что, если я создам подкласс промежуточного программного обеспечения CSRF и разрешу его обходить, если присутствует заголовок Authorization
? Затем запрос может быть удовлетворен или отклонен непосредственно промежуточным программным обеспечением аутентификации (фактически я предполагаю, что при наличии заголовка авторизации я имею дело с мобильным клиентом, который не нуждается в защите CSRF).
Это плохая идея?
Если это не так, то как должен выглядеть LoginView? Он по-прежнему должен быть защищен от CSRF, но ни у мобильных, ни у SPA-клиентов не будет токена для установки в качестве заголовка Authorizaton
...
Я не стремился к самостоятельному ответу, но после дополнительных исследований и отсутствия полезных ответов от присутствующих здесь людей я остановился на следующем подходе:
- Я установлю строгую политику CORS, разрешающую запросы только из одного домена
- Я установлю срок действия токена доступа на небольшой период (возможно, 60 секунд). Срок действия токена обновления можно настроить от нескольких часов до нескольких дней
- Мне потребуется, чтобы пользовательский заголовок аутентификации присутствовал для всех запросов. Например, что-то в виде
X-MyApp-Auth: Bearer <token>
- Для предотвращения CSRF при входе в систему я потребую, чтобы заголовок аутентификации содержал пару
<userid>:<password>
в кодировке base64 (HTTP Basic Auth) вместо токена JWT. При успешной аутентификации я верну доступ к JWT и обновлю токены в теле ответа. - Для обновления токена доступа у меня будет тот же рабочий процесс, но в заголовке аутентификации должен присутствовать действительный токен обновления
Дополнительно:
- Я проверю наличие дополнительного пользовательского заголовка для определения типа клиента (например,
X-MyApp-ClientType: SPA/mobile
), в противном случае я проверю, присутствует ли заголовокOrigin
в запросе, и если это так, я рассмотрю следующий вариант. клиент должен стать СПА-центром. - Если клиентом является мобильное приложение, применяется описанный выше рабочий процесс
- Если клиентом является SPA-центр:
- При успешном входе в систему я верну токен доступа в теле ответа, но установлю токен обновления как файл cookie HttpOnly (с тем же исходным кодом и
secure=true
, если сервер не находится в режиме отладки) - При запросе конечной точки обновления я буду требовать, чтобы в запросе присутствовал заголовок аутентификации, но не обращаю внимания на его значение и беру токен обновления из файла cookie HttpOnly.
- При успешном входе в систему я верну токен доступа в теле ответа, но установлю токен обновления как файл cookie HttpOnly (с тем же исходным кодом и
Хранение токенов:
- SPA: токен доступа только в памяти SPA. Токен обновления только в файле cookie HttpOnly. При обновлении страницы токен доступа теряется, и с сервера необходимо запросить новый.
- Мобильное приложение: Доступ к токенам, надежно хранящимся в постоянном хранилище приложения, и их обновление в соответствии с рекомендациями платформы.
Обоснование
- Большинство (если не все) современных браузеров реализуют политику "одного и того же источника", которая требует успешной предполетной проверки, прежде чем разрешить пользовательский заголовок, который должен быть отправлен через "небезопасный" (как определено в RFC 9110) перекрестный HTTP-запрос. Таким образом, если CORS настроен правильно, можно гарантировать, что атака CSRF не произошла, если в запросе присутствуют пользовательские заголовки
- Файл cookie HttpOnly предназначен для дополнительной безопасности клиентов SPA, чтобы предотвратить случайную утечку токена обновления с длительным сроком службы через JS