Что именно идентифицирует посетителя сайта?

1,00
р.
Изучая backend, столкнулся с такой проблемой недопонимания: есть сайт, сервер Nodejs (в принципе, какой угодно, но меня интересует именно Nodejs) и зашел на сайт посетитель. Не важно есть ли регистрация на сайте или нет — что именно идентифицирует посетителя?
Я знаю, что управляется это сессиями. Читал тут, но не понял откуда именно и чего и с каких данных server понимает, что вот сейчас именно тот самый посетитель? Допустим, на сайт зашли два посетителя: один из Москвы другой из Воркуты и на сайте есть чат. Чат простой и банальный - можно без регистрации отправить какой-нибудь пост или поболтать. Надо в чате сделать так, чтобы сообщение пользователя из Воркуты было на синем фоне, а сообщения из Москвы — на зеленом. Тут же на этот сайт зашел новый посетитель тоже из Москвы и с того же IP адреса, но с другого компа который в соседней комнате нашего москвича (допустим это его супруга), вот ее (супруги) сообщение должен иметь фон оранжевый...
В итоге, не прошу никакого кода (если это только не обязательно для наглядного примера). Прошу объяснить саму логику восприятия сервером посетителей. Как определить кто есть кто?

Ответ
Перечислю все известные мне способы идентификации пользователя.
IP-адрес
Указываю этот способ потому, что он единственный, который невозможно подделать. Его можно позаимствовать у других (прокси, VPN, Tor, просто динамический IP), но это обычно сложнее, чем, например, почистить куки. Удалить IP-адрес, аналогично чистке cookies, нельзя: какой-нибудь обязательно будет. В связи с его относительной надёжностью (не всем не лень держать наготове сотни прокси-серверов для смены IP) его часто используют для усиления безопасности: например, ограничивают максимальное число запросов в секунду/минуту/час с одного IP. Однако разных людей, сидящих через один интернет, IP различить не даст, что противоречит условию вопроса, поэтому едем дальше.
Банальные логин и пароль
Суть проста: тупо шлём логин и пароль в каждом запросе. Один из вариантов реализации этого способа уже присутствует в самом протоколе HTTP, через заголовок Authorization, уже реализован во всех основных веб-браузерах и веб-серверах.
В HTTP-варианте суть такова:
при первом посещении сайта у клиента ничего нет и никакой дополнительной информации серверу не шлёт. Сервер отвечает ошибкой 401 Unauthorized и добавляет HTTP-заголовок WWW-Authenticate с информацией о способах входа (для простого логина-пароля это Basic realm="default") клиент получает это всё и просит у пользователя логин и пароль. После чего отправляет свой запрос повторно, но уже с HTTP-заголовком Authorization, в котором содержится логин-пароль в base64: Basic YWRtaW46MTIzNDU2. Если этот пример раскодировать, получим admin:123456 — логин и пароль, разделённые двоеточием сайт это всё проверяет и или отвечает нормально, или опять 401 и запрашиваем логин-пароль на новый Этот Authorization: Basic YWRtaW46MTIzNDU2 шлём каждый раз во всех последующих запросах.
Достоинства:
простота. HTTP Basic Auth уже реализован в большинстве веб-браузеров и веб-серверов, ничего изобретать не надо. Если делать свой вариант, то достаточно реализовать проверку логина-пароля в каждом запросе без дополнительных сложностей.
Проблемы:
без HTTPS безопасность вообще никакая: логин-пароль по сути ходят по интернету в открытом виде (base64 не является шифрованием). Клиент тоже вынужден помнить у себя пароль в открытом виде, да и сервер тоже знает пароль (существуют схемы аутентификации, при которых сервер может и не знать пароль, но вопрос не об этом) HTTP Basic Auth в браузерах работает только в пределах текущей сессии после перезапуска браузера логин-пароль нужно вводить снова.
Справедливости ради отмечу, что HTTP умеет не только голые логин-пароль (возможно полный список спосбов авторизации), но останавливаться на других способах не буду в связи с их низкой распространённостью.
Случайная строка
Самый простой, самый сбалансированный в отношении «безопасность/удобство» и самый популярный способ идентификации. Самая распространённая в мире (наверно) кука PHPSESSID — это именно оно. Суть такова:
при первом посещении сайта у клиента ничего нет. Сайт замечает это, создаёт новую случайную строку (подлиннее, чтоб трудно было подобрать символов 30 хотя бы) и вместе с обычным ответом на запрос тем или иным образом отправляет эту сгенерированную строку (Set-Cookie, редирект на специальную ссылку или просто в теле ответа, если это например JSON API) клиент вместе с ответом получает эту строку и запоминает её где-нибудь (браузер сам хранит в cookies, SPA может положить её в localStorage и т.п.) при последующих посещениях сайта клиент добавляет эту строку к своему запросу (cookies, HTTP-заголовок Authentication или просто GET-параметр в запрашиваемом адресе — на некоторых старых PHP-форумах до сих пор можно наблюдать id сессии прямо в адресной строке) если нужно идентифицировать клиента более конкретно (вход по логину-паролю, например), сайт в своей базе данных после записывает, что такой-то случайной строке соответствует такой-то логин, а потом при последующих запросах считывает эту информацию из базы.
Если говорить о PHP, то всё это в него встроено: при вызове функции session_start() создаётся кука PHPSESSID из случайных букв и цифр (или читается существующая, если она уже есть). Данные, которые связаны с этой кукой, доступны через массив $_SESSION (а физически хранятся по умолчанию где-то в специальном каталоге на сервере), и вы можете его читать и изменять. При последующих запросах от пользователя содержимое сессии автоматически прочитается из файла при вызове session_start(), и все данные, которые вы положили в массив $_SESSION при обработке предыдущих запросов, снова станут доступны. Подробности в документации.
Достоинства:
простота — сравнивать строки тривиально при смене IP-адреса (а на мобильниках это частое явление) идентификация не слетает реализация кнопочки «Разлогинить меня на всех устройствах» сводится к простому удалению всех записей в базе, а если для каждого устройства создавать отдельную строку, то можно разлогинивать устройства выборочно (некоторые сайты предоставляют такую возможность, например ВК).
Проблемы:
генератор случайной строки должен быть действительно случайным (или не совсем случайным, но криптостойким, не uniqid()), так как псевдослучайность злоумышленник может попытаться подобрать (например, подбор состояния генератора в PHP или Python, или подбор сессий, созданных черех uniqid(), в Invision Power Board). Ни в коем случае в качестве строки нельзя использовать хэш логина, хэш пароля, текущее время, одну-единственную заранее заготовленную строку и прочие неслучайные вещи, так как это сильно упрощает подбор. Как получить настоящую случайность, читайте в документации к вашему языку программирования. Или просто используйте готовую реализацию вроде session_start() в PHP дополнительная нагрузка на сервер. Чтобы узнать, какой именно пользователь прячется за случайной строкой, ему приходится обращаться к базе данных. Не проблема для подавляющего большинства сайтов, но для гигантов типа гугла уже проблема куки иногда баганутые: например, IE11 добавляет куки к поддоменам, даже когда его не просят (в Edge уже исправлено), что может привести к утечке данных на сторонние CDN, например. Поэтому следите за тем, как браузеры, для которых вы затачиваете сайт, манипулируют с куками. Ну и про HttpOnly не забывайте, чтобы нельзя было угнать куки через XSS (и про Secure, если сайт использует HTTPS).
Неслучайная, но защищённая строка (например, JWT)
Суть такова: нагло нарушаем вышеупомянутый запрет на неслучайные данные и пихаем в строку, например, ID пользователя и, опционально, имеющиеся права доступа (например, админ ли он), срок годности строки и какие-нибудь ещё данные. Но! Дополнительно к этой строке добавляем какой-нибудь хэш, который считается по данным плюс некой секретной строке, которую знает только сайт и никому не отдаёт. При запросе от клиента сайт, соответственно, проверяет, что хэш правильный. Это защищает от подбора и подделок: чтобы подделать данные, нужно пересчитать хэш, а злоумышленник, не зная секретной строки, этого сделать не сможет. (Секретная строка должна быть ОЧЕНЬ длинной, символов сто, чтоб вообще не подобрать, так как на ней вся безопасность.) (В JWT также вместо просто секретной строки можно применять RSA для подписи, что повышает безопасность, но расписывать все детали реализации не буду, и так длинно получилось)
Достоинства:
меньшая нагрузка на сервер. Клиент уже сам прислал все нужные данные, серверу остаётся лишь посчитать хэш от этих данных и секретной строки и проверить, что он совпадает с присланным. В базу данных ходить не надо: секретная строка обычно лежит в какой-нибудь переменной поблизости, так что всё это делается быстро независимость от централизованной базы данных позволяет легко проверять аутентификацию на независимых и никак не связанных друг с другом микросервисах, в том числе географически разбросанных по миру, ведь им достаточно знать лишь секретную строку, которая меняется очень редко, для проверки присланного пользователем хэша, и не нужно связываться с другими микросервисами или с базой клиент сам может прочитать JWT и понять, кто он такой (если данные только защищать хэшем, а не шифровать) при смене IP-адреса тоже не слетает.
Проблемы:
реализация усложняется. Если делать всё самому, то можно накосячить и получить дырку в безопасности, поэтому лучше брать готовые реализации вроде того же JWT (впрочем, в них тоже иногда находят дырки, так что обязательно мониторим новости и почитываем Хабр) кнопочку «Разлогинить меня на всех устройствах» сделать вообще нельзя. Чтобы пользовательская строка с данными стала недействительной, нужно или сменить секретную строку, или запомнить где-то в базе, что именно такая-то строка с такими-то данными стала недействительна. Но это всё довольно проблематично и сводит на нет все преимущества данного способа идентификации. Поэтому такие строки, как правило, делают короткоживущими: например, Google в своих API выдаёт JWT, действительный всего полчаса (информация о сроке годности хранится прямо в JWT и тоже защищается хэшем, в базу ходить не надо). информация может протухнуть. Например, если записать в JWT, что пользователь является админом, а потом отобрать права админа, то сайт, опираясь на данные JWT, будет продолжать считать клиента админом, пока сам JWT не протухнет целиком. Можно брать информацию из базы, но тогда опять становится проще использовать случайную строку. JWT и аналоги из-за того, что содержат всю необходимую информацию, обычно длинные при большом количестве данных строка может, например, не влезть в cookies. Впрочем, если хранить только id пользователя, то это не проблема.
Суперкуки и прочий фингерпринтинг
Суть в использовании технологий не по назначению. У каждого браузера и каждой ОС есть свои особенности поведения, и по этим особенностям можно довольно точно идентифицировать, кто именно зашёл. Например, они рисуют текст немного по-разному, и по мелким отличиям в пикселях текста браузеры можно различать, или же на разных компьютерах будет немножко разный размер окна браузера, развёрнутого на весь экран (поэтому Tor Browser рекомендует не разворачивать его и блокирует сайтам доступ к canvas). Не буду расписывать всё во всех подробностях, оставлю ссылки для дальнейшего чтения:
Evercookie — самые устойчивые куки Panopticlick 2.0 для фингерпринтинга браузера Супер-куки на основе HSTS отследят вас даже в приватном режиме
Достоинства:
хрен выпилишь. Если захотеть, то можно, конечно, но уж очень много мороки: нужно уметь мимикрировать под популярное сочетание браузер/ОС/устройство, чтобы «смешаться с серой массой». Это уже не просто кнопочку «Очистить cookies» нажать. Без специальных мер устройство клиента будет идентифицировано независимо от того, сменил ли он IP-адрес, почистил ли куки и т.п.
Проблемы:
точность не стопроцентная. Все айфоны довольно одинаковые, и отличить один айфон X от другого айфона X вряд ли получится (хотя это касается только фингерпринтинга, для суперкук попроще) пользователи вас найдут и больно побьют.