Как построить авторизацию на уровне веб-сокетов для изоляции сообщений клиентов
Что проверяют
Понимаете ли вы, что изоляцию сообщений обеспечивает только сервер, а фронтенд лишь предъявляет доказательство личности. Следом спрашивают, как передать токен из браузера, если в WebSocket нельзя поставить заголовок Authorization, и что делать, когда токен истёк посреди соединения.
Как отвечать
Разделите ответ на аутентификацию соединения и авторизацию каждого действия. Способы передать личность при подключении:
- cookie: браузер сам отправит её при рукопожатии. Удобно с
HttpOnly-сессией, но сервер обязан проверять заголовокOrigin, иначе чужой сайт откроет сокет от имени пользователя (атака CSWSH); - одноразовый тикет в URL: фронтенд получает короткоживущий тикет обычным авторизованным HTTP-запросом и подключается с ним в строке запроса. Сам JWT туда не кладут: URL оседает в логах прокси;
- первое сообщение: сервер ничего не делает, пока не придёт сообщение с токеном, и закрывает сокет по таймауту.
Изоляция на сервере:
- личность из сессии:
userIdпривязывается к соединению после проверки и никогда не берётся из тела сообщения; - комнаты: клиент просит подписку на канал, сервер проверяет право доступа и только тогда добавляет соединение в комнату. Рассылка идёт в комнату, а не всем подряд;
- проверка каждого действия: права могли измениться уже после подключения;
- истечение и отзыв: соединение живёт дольше токена, поэтому сервер закрывает сокет кодом из диапазона 4000-4999, а фронтенд переподключается со свежим токеном. При выходе из аккаунта рвутся все сокеты пользователя.
При нескольких инстансах сервера рассылка идёт через Redis pub/sub или брокер, иначе сообщение не дойдёт до сокета на соседней машине.
Где спотыкаются
Фильтруют на клиенте: сервер шлёт всё всем, а фронтенд скрывает чужое. Любой откроет вкладку Network и прочитает чужие сообщения.
Доверяют recipientId или userId из присланного клиентом JSON. Подделать их можно одной строкой в консоли.
Проверяют права только при рукопожатии. После отзыва доступа или выхода из аккаунта соединение ещё часами получает данные.