Вопрос или проблема
Контекст
Существует два приложения:
auth-server
– провайдер идентификации – это мое собственное решение для единого входа (SSO).img-server
– провайдер услуг – это приложение имеет некоторые маршруты, которые я хотел бы аутентифицировать.
auth-server
имеет два конечных пункта:
GET /
– пример URL:https://auth.domain.com/?redirect=https://img.domain.com/getdata
- Имеет промежуточное ПО, которое проверяет токен из cookies, если он существует.
- Если токен существует и действителен, он создает публичный токен (который может быть проверен провайдером услуг) и перенаправляет пользователя на URL, указанный в параметре запроса
redirect
, включая публичный токен как параметр запроса в перенаправлении (authToken
).
- Если токен существует и действителен, он создает публичный токен (который может быть проверен провайдером услуг) и перенаправляет пользователя на URL, указанный в параметре запроса
- Показывает страницу входа, если промежуточное ПО не возвращает.
- Имеет промежуточное ПО, которое проверяет токен из cookies, если он существует.
POST /
– пример URL:https://auth.domain.com/?redirect=https://img.domain.com/getdata
- Использует переданное имя пользователя и пароль для попытки входа
- Если успешно, сервер сохраняет приватный токен в cookies и генерирует публичный токен (который может быть проверен провайдером услуг) и перенаправляет пользователя на URL, указанный в параметре запроса
redirect
, включая публичный токен как параметр запроса в перенаправлении (authToken
). - Если неудачно, пользователь перенаправляется обратно на страницу входа.
- Если успешно, сервер сохраняет приватный токен в cookies и генерирует публичный токен (который может быть проверен провайдером услуг) и перенаправляет пользователя на URL, указанный в параметре запроса
- Использует переданное имя пользователя и пароль для попытки входа
Пример успешного ответа на пример URL: https://img.domain.com/getdata?authToken={jwt}
, где jwt
– это сгенерированный публичный токен. Провайдеры услуг могут проверить этот токен, потому что он сгенерирован с помощью приватного ключа, публичный ключ которого известен провайдеру услуг.
Используемые библиотеки
express
– для запуска сервера.cookie-parser
– для установки и чтения (подписанных) cookies.jsonwebtoken
– для генерации и проверки JWT (с парой ключей RSA).
Поток аутентификации
Поток, который я пытаюсь реализовать, следующий, для этого примера я буду предполагать, что cookie еще не установлена у провайдера услуг:
- Пользователь отправляет запрос к аутентифицированному пути, например,
https://img.domain.com/getdata
. - Провайдер услуг проверяет, установлена ли cookie
Authorization
, или установлен ли параметр запросаauthToken
. - Поскольку ни cookie, ни параметр запроса не установлены, он перенаправляет пользователя к провайдеру идентификации, который показывает страницу входа, например,
https://auth.domain.com/?redirect=https://img.domain.com/getdata
. - Пользователь входить в систему с использованием своих учетных данных, служба идентификации создает приватный токен и публичный токен и перенаправляет на оригинальный URL вместе с публичным токеном в параметре запроса
authToken
, например,https://img.domain.com/getdata?authToken={jwt}
. - Провайдер услуг проверяет параметр запроса
authToken
, валидирует его и сохраняет как httpOnly cookieAuthorization
. - Затем провайдер услуг перенаправляет на свой собственный путь без параметра запроса (чтобы очистить его), например,
https://img.domain.com/getdata
. - Теперь провайдер услуг должен видеть cookie
Authorization
и использовать его для проверки пользователя.
Код провайдера услуг следующий (упрощенный):
server.get('/getdata', authenticate, (req, res) => { res.send("крутые вещи") });
function authenticate(req, res, next) {
if (req.query.authToken && validateToken(req.query.authToken)) {
// Если действительный, то установить его как cookie.
res.cookie("Authorization", req.query.authToken, { httpOnly: true, signed: true });
// Очистить параметр запроса и перенаправить.
res.redirect( req.originalUrl.split("?").shift() );
return;
} else if (req.signedCookies.Authorization && validateToken(query.signedCookies.Authorization) {
next();
return;
}
// Действительный токен не найден, поэтому перенаправить к службе идентификации.
res.redirect(`auth.domain.com/?redirect=https://img.domain.com/${req.originalUrl}`)
}
function validateToken(token) {
// Делает что-то для проверки токена.
}
Проблема
Поток от провайдера услуг к провайдеру идентификации и обратно проходит корректно (шаги 1 – 4), но сохранение cookie Authorization
не работает.
Параметр запроса authToken
правильно извлекается, и его валидация проходит успешно, но как только параметры запроса очищаются и снова выполняется перенаправление, cookie Authorization
не видна (я также проверил req.cookies
, который не содержит его).
Я изучал проблему и видел разные мнения о том, как cookies точно устанавливаются в HTTP, но так и не смог точно понять, почему это не работает. Кто-нибудь знает почему? Я также открыт для обратной связи по моему потоку аутентификации, возможно, это просто не правильный способ сделать это.
Должен добавить, что я пробовал это только при запуске img-server
локально (http://localhost:3000
). auth-server
размещен на HTTPS.
Спасибо заранее!
Ответ или решение
Как корректно хранить httpOnly cookie после редиректа с кастомного SSO
Важным моментом для обеспечения безопасности и удобства использования является правильное управление куками в процессе аутентификации. В данном случае у нас есть два приложения: auth-server
(идентификатор) и img-server
(провайдер услуг), и возникает задача корректного хранения httpOnly
cookie после перенаправления.
Контекст и описание проблемы
Вы описали аутентификационный процесс между двумя серверами, который включает в себя получение токенов из куки и редиректы. Проблема состоит в том, что после установки нового cookie Authorization
, оно не доступно при следующем запросе, что мешает корректной аутентификации пользователя.
Почему это происходит? В HTTP своим поведением редиректы создают новый запрос, что означает, что куки, установленные на одном этапе (при помощи метода res.cookie
), могут не быть включены в нового запроса, если он происходит на локальном сервере и имеет неправильные настройки.
Решения проблемы
Вот несколько моментов, на которые стоит обратить внимание:
-
Проблема с доменами и путями:
Убедитесь, что куки устанавливаются для правильного домена и пути. Если вы работаете сlocalhost
, возможно, стоит добавить явное указание на домен. Например:res.cookie("Authorization", req.query.authToken, { httpOnly: true, signed: true, domain: 'img.domain.com', // Убедитесь, что здесь указан правильный домен path: '/' // Задайте путь, если это необходимо });
-
Настройки безопасности:
В вашем случаеauth-server
работает через HTTPS, аimg-server
— локально через HTTP. Это может повлиять на возможность передачи куки между серверами. С включённой настройкойsecure: true
куки будут передаваться только по защищенному соединению (HTTPS). Чтобы это исправить в тестовой среде, можно временно убрать это ограничение, хотя это не рекомендуется для производственной среды.res.cookie("Authorization", req.query.authToken, { httpOnly: true, signed: true, secure: process.env.NODE_ENV === 'production' // Устанавливайте по условию окружения });
-
Правильная обработка запросов:
В своем коде убедитесь, что вы обрабатываете куки правильно и проверяете их наличие в дальнейшем коде. Например:else if (req.signedCookies.Authorization && validateToken(req.signedCookies.Authorization)) { next(); return; }
-
Тестирование на производственном окружении:
Так как вы тестируете локально, вы можете столкнуться с дополнительными ограничениями, которые не будут актуальны на реальном сервере. Возможно, стоит сделать тест на тестовой среде, которая близка к производственной.
Потенциальные улучшения потока аутентификации
-
Используйте сингл-серверную архитектуру: Если возможно, рассмотрите возможность объединения аутентификации и обслуживания в одном приложении, чтобы избежать проблем с куками между доменами.
-
JWT как токен доступа: Рассмотрите использование JWT как способ облегчения аутентификации. Вместо передачи токена как куки, в заголовке запроса может передаваться
Authorization: Bearer token
, что упрощает его валидацию на всех уровнях. -
Используйте одни и те же домены: Если возможно, управлять аутентификацией в рамках одного домена (например, через поддомены). Это упростит управление куками.
Заключение: Важно правильно настроить куки, особенно в распределенных системах аутентификации. Следуя представленным рекомендациям, вы можете устранить проблемы с доступностью ваших httpOnly
cookie после редиректов и гарантировать безопасность данных ваших пользователей.