Когда настраиваешь авторизацию в Telegram Mini Apps, поначалу всё выглядит просто: Telegram передаёт initData, сервер проверяет подпись HMAC-SHA-256, секрет получается из bot token. Работает из коробки, проверено. Но как только сервис начинает ориентироваться на российскую аудиторию, одной проверки initData перестаёт хватать. Приходится думать о том, как соблюсти требования к идентификации пользователей.

Мы в своём проекте оставили Telegram как удобную точку входа и канал доставки, а саму идентификацию вынесли в отдельные ID-сервисы. Разделили понятия: «кто пользователь» — это identity, «куда ему слать уведомления» — channel identity. Человек входит через VK ID, Яндекс ID или MAX, а его telegram_id потом просто привязывается к аккаунту для отправки ленты. Не для входа.

В базе это выглядит примерно так:

поле

идентификатор

max_user_id

от MAX

vk_user_id

от VK ID

yandex_id

от Яндекс ID

поле

канал доставки

max_chat_id

в MAX

vk_chat_id

в VK

telegram_id

в Telegram

В интерфейсе мини-приложения три кнопки: MAX, VK, Яндекс. Кнопки «Войти через Telegram» нет вообще.

Три кнопки входа внутри Telegram: MAX, VK, Яндекс.
Три кнопки входа внутри Telegram: MAX, VK, Яндекс.

Для VK ID у нас используется PKCE. Браузер генерирует code_verifier, вычисляет code_challenge (S256), передаёт его вместе со state в запрос на авторизацию. Сервер сохраняет code_verifier по state, чтобы потом проверить.

Кнопки открывают вход в новой вкладке. Это оказалось удобно: пользователь может пройти авторизацию в основном браузере, не переключая VPN, даже если мини-приложение запущено внутри Telegram WebView на телефоне. Или вообще на другом устройстве — например, на компьютере. После успешного входа вкладка сама закрывается, а основной интерфейс получает уведомление через polling. Та же схема пригодилась для авторизации в системе умного дома через Home Assistant. Один аккаунт — разные каналы управления.

С Яндексом похожая история, OAuth 2.0, но URL формируется на бэкенде. Сервер обменивает полученный code на access token, получает идентификатор пользователя и сохраняет его. Результат авторизации доступен по state через эндпоинт /api/auth/result-by-state. Клиент опрашивает его — например, 120 попыток с интервалом 3 секунды.

Здесь тонкость: в exchange передаётся не OAuth-токен, а одноразовый application-level code. Сервер в ответ устанавливает HttpOnly cookie с сессией. OAuth-токен в браузер не попадает — клиентский JavaScript не может его прочитать. Это снижает риски при XSS.

state должен быть криптостойким и одноразовым. Math.random() не подходит, берём crypto.randomUUID() или crypto.getRandomValues() с энтропией не меньше 128 бит. На сервере state хранится вместе с code_verifier, у него короткий TTL, удаляется после успешного callback. При каждом обращении сервер проверяет срок действия и что state соответствует конкретной попытке авторизации. Так мы ограничиваем время жизни попытки, связываем колбэк с исходным запросом и исключаем повторное использование.

Про колбэк ещё нюанс. Передавать auth_token в URL небезопасно: он может засветиться в истории браузера, Referer или серверных/прокси-логах. Лучше использовать короткоживущий code, который клиент обменивает на сессию через POST.

Одна и та же страница работает в трёх сценариях: браузер, Telegram WebApp, расширение. Флаг from_extension нужен только для UI-сценариев, сервер не должен считать его признаком безопасности. Мы передаём его в URL при открытии страницы из расширения, чтобы корректно обработать поведение интерфейса — например, закрытие вкладки после авторизации или показ специальных сообщений.

MAX подключается через deep link с одноразовым токеном. После привязки бот присылает подтверждение в чат.

Вот как это выглядит в сравнении:

Провайдер

Протокол

Наша реализация

VK ID

OAuth 2.1 + PKCE

code_challenge S256, передаём device_id

Яндекс ID

OAuth 2.0

Берём только login:info

MAX

Deep link + одноразовый токен

В MAX чате

Ещё нюанс с согласием на обработку данных: для Telegram WebApp мы показываем две галочки — общее согласие и отдельное на передачу данных в Telegram. Это требование закона, что и реализовано буквально. В PWA-версии (без Telegram) хватает одной галочки.

Эту схему мы применили в проекте «Моя АнтиСоцсеть» — агрегаторе с индивидуальной лентой новостей. Чтобы не было рекламы, ссылку на приложение не прикладываю. Проект включает ботов в Telegram, MAX и VK, PWA, браузерные расширения, мобильные приложения, интеграцию с умным домом и голосовыми помощниками.

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

Итог: Сейчас в РФ нельзя собирать в базу (и уж тем более отправлять любые уведомления) идентификатор телеграм до того, как пользователь сделал вход через РФ сервис и поставил галочку согласия. Все кто, не сделал такой вход, нарушители.

Посмотреть код фронтенда можно на GitHub

Комментарии (17)