Статьи

Не кладите токен в LocalStorage: как выбрать хранилище на клиенте

Не все данные веб-приложению нужно отправлять на сервер: часть информации можно хранить на стороне клиента, прямо в браузере. Например, выбранную тему, язык интерфейса, настройки или данные незавершённой формы. Для простых сценариев здесь часто используют два инструмента: localStorage и sessionStorage.

На первый взгляд они почти одинаковые: оба позволяют работать с данными в формате «ключ → значение» и имеют одинаковый API. Но отличаются главным образом тем, в рамках какого жизненного цикла браузерного контекста данные будут доступны.

LocalStorage: данные хранятся, пока их не удалят

localStorage — это хранилище для конкретного origin. Данные в нём сохраняются после перезагрузки страницы, закрытия вкладки и даже полного закрытия браузера. Они останутся доступными при следующем посещении сайта, пока пользователь не очистит данные сайта или приложение само их не удалит.

Например, выбранную тему можно сохранить так:
А при загрузке страницы получить:
Удалить конкретное значение можно через removeItem():
А clear() полностью очищает хранилище текущего origin:
При этом важно помнить: LocalStorage хранит только строки. Если сохранить число, при чтении оно вернётся строкой:
Для объектов и массивов обычно используют JSON:
Если ключа нет, getItem() вернёт null, поэтому при чтении данных стоит учитывать такой сценарий.

SessionStorage: данные живут, пока существует вкладка

У sessionStorage абсолютно такой же API:
Главное отличие — жизненный цикл данных. sessionStorage привязан к конкретной вкладке и доступен в рамках её браузерной сессии. Если страницу перезагрузить, данные сохранятся. Если закрыть вкладку, сессия для неё завершится и соответствующие данные будут удалены.

Поэтому sessionStorage хорошо подходит для данных, которые актуальны только в рамках текущей браузерной сессии. Например, пользователь заполняет многошаговую форму, и приложение хочет сохранить уже введённые значения на случай случайной перезагрузки:
При этом персистентность всё равно нужно учитывать на уровне логики приложения. Если пользователь успешно отправил форму, её данные больше не нужны — значит, приложение должно само очистить их:
Иначе данные продолжат существовать до конца жизни соответствующей вкладки.

Есть и ещё одна важная особенность: sessionStorage привязан к конкретному контексту вкладки. Поэтому две вкладки одного сайта не стоит рассматривать как одно общее хранилище.

LocalStorage и SessionStorage: главное различие

LocalStorage
SessionStorage
После перезагрузки
сохраняется
сохраняется
После закрытия вкладки
сохраняется
удаляется
Срок жизни
пока данные не удалят
пока существует соответствующая сессия вкладки
Значения
строки
строки
API
одинаковый
одинаковый

Что значит «для конкретного origin»?

Доступ к Web Storage определяется origin — комбинацией схемы, хоста и порта.

Поэтому, например, https://example.com и http://example.com работают с разными хранилищами. JavaScript одного origin не может просто прочитать localStorage другого сайта.
Это часть стандартной модели безопасности браузера: сайт получает доступ к своему хранилищу, а не к данным произвольных сайтов.

LocalStorage — не база данных

Простота Web Storage — одновременно его плюс и ограничение. Здесь есть только ключи и строковые значения. Если нужно сохранить небольшой объект, JSON прекрасно справится с задачей.
Но представим, что приложение хранит тысячи объектов.
Чтобы изменить один элемент массива, придётся получить всю строку, распарсить JSON, изменить объект, снова сериализовать весь массив и записать его обратно.

Для небольших данных это нормально, но для больших объёмов и сложных структур такой подход быстро становится неудобным. Здесь уже появляется IndexedDB — полноценное браузерное хранилище для структурированных данных. Оно позволяет работать с объектными хранилищами, индексами и транзакциями и рассчитано на более серьёзные клиентские сценарии, например, большие объёмы данных или работу приложения офлайн.
При этом IndexedDB — не просто «LocalStorage, только побольше». У неё другая модель работы и асинхронный API, поэтому подробно разбирать её лучше отдельно.

А сколько можно хранить?

У Web Storage есть ограничения по объёму, причём конкретные лимиты зависят от браузера и среды. Главное — понимать, что localStorage не предназначен для больших объёмов данных.

Если приложение попытается сохранить больше данных, чем позволяет доступный объём, браузер выбросит ошибку QuotaExceededError:
Поэтому хранить там большие API-ответы, изображения или постоянно растущую историю действий — плохая практика.

Можно ли хранить токены?

Технически можно:
Но вопрос здесь не в возможности, а в безопасности.

JavaScript страницы имеют доступ к localStorage. Поэтому при XSS-уязвимости злоумышленник потенциально может прочитать сохранённый там токен.
По этой причине Web Storage не стоит воспринимать как защищённое место для секретов. Для сессионных идентификаторов часто используют cookies, но это не просто «другое хранилище для токена».

Cookie автоматически отправляется браузером с HTTP-запросами к соответствующему домену, поэтому сервер может использовать её для аутентификации без необходимости читать значение из JavaScript. Для защиты cookie от доступа JavaScript используется атрибут HttpOnly, а Secure запрещает передачу cookie по обычному HTTP. SameSite позволяет контролировать отправку cookie в cross-site сценариях и помогает снизить некоторые виды CSRF-атак.
! При этом cookies тоже не являются универсальным решением: у них другая модель работы, ограничения и особенности безопасности. Выбор между cookies, Web Storage и другими механизмами должен зависеть от архитектуры приложения и модели угроз.

Особенно осторожно стоит относиться к паролям, access/refresh-токенам и другим данным, компрометация которых может привести к захвату аккаунта.

Что тогда хранить?

  • Для localStorage хорошо подходят небольшие данные, которым нужно пережить закрытие браузера: настройки интерфейса, выбранный язык, тему, некоторые пользовательские предпочтения или небольшие черновики.

  • sessionStorage логичнее использовать для состояния, которое нужно только в рамках текущей вкладки: данных многошаговой формы, временного состояния пользовательского сценария или других данных, которые не должны переживать закрытие вкладки.

  • А вот если появляются большие объёмы данных, сложные структуры, поиск по индексам или полноценная работа приложения офлайн, лучше рассматривать IndexedDB.

И ещё один полезный механизм — событие storage

У localStorage есть событие storage, которое позволяет одному документу узнать об изменениях в хранилище, сделанных другим документом того же origin:
Например, пользователь изменил настройку в одной вкладке, а другая вкладка может получить уведомление и обновить интерфейс.

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

Если нужна более полноценная коммуникация между вкладками и другими контекстами одного origin, в современном вебе можно использовать, например, Broadcast Channel API. А для более сложных сценариев, в том числе связанных с общим состоянием и фоновой логикой, существуют Shared Workers и другие механизмы межконтекстного взаимодействия.

Что выбрать?

  • Если данные должны переживать закрытие браузера и их немного — localStorage.
  • Если данные нужны только в рамках текущей вкладки — sessionStorage.
  • Если данных становится много, они сложные, нужен поиск по индексам или полноценная работа приложения офлайн — IndexedDB.

Главное, что стоит вынести: localStorage и sessionStorage — удобные инструменты для простых задач, но не универсальная база данных для фронтенда. Их сила именно в простоте: несколько методов, строковые значения и понятный жизненный цикл.
А когда приложение начинает требовать от браузерного хранилища больше, чем просто сохранить пару настроек или состояние текущего сценария, пора смотреть в сторону IndexedDB.


Хотите узнать больше? Изучите другие статьи из раздела:
Frontend