Всем привет. Недавно пришлось решать проблему идентификации пользователя по номеру телефона в маленьком стартапе, позволяющим оформлять и оплачивать заказы онлайн. Почему именно номер телефона, а не электронная почта, например, или авторизация через соц. сети? Телефон сейчас, как мне кажется, де-факто стандарт для таких кейсов - это во-первых, а во-вторых, используя телефонный номер, можно подключать разные варианты его подтверждения: от смс до звонков с дальнейшим вводом либо кода из смс, либо последних цифр звонившего номера. Думаю, тут всем знакомы подобные механики.
Изначально я сразу и предложил использовать механику с СМС-шлюзом, но так как я тут имею дело со стартапом без инвестиций, то меня попросили придумать как на первоначальном этапе можно сэкономить (в идеале обойтись на этом этапе совсем без затрат), так как основной целью запуска было тестирование бизнес-идеи.
По предварительным исследованиям целевая пользовательская аудитория продукта является пользователями мессенджера Telegram. Вот в эту сторону я и начал думать. “Коробочный” механизм авторизации через виджет Telegram не отдает номер телефона пользователя, а нам именно он и нужен. Городить велосипед по получению номера телефона по id пользователя идея не самая лучшая, поэтому я решил сделать своего ТГ-бота для механики авторизации. Я на паре сайтов уже видел подобные решения, быстрый поиск по примерам реализации этой механики не дал внятных результатов. При поиске “авторизация telegram” я наткнулся на сайт, который был первым в поисковой выдаче. Пошел смотреть как у них все устроено, рассмотрим по шагам:
пользователь вводит номер телефона
сайт делает редирект в телеграм-бота
пользователь нажимает кнопку “Поделится номером”, подтверждает свое согласие
в диалог с ботом прилетает код для подтверждения
пользователь вставляет полученный код на сайте
На первый взгляд схема рабочая, но как мне показалась, избыточная, поэтому вот какой механизм реализации авторизации через ТГ-бота придумал я (имеем бэкенд + фронтэнд приложения, которые общаются между собой по REST API):
пользователь на фронте вводит номер телефона
фронт отправляет запрос на бэк и передает указанный пользователем телефон
бэк генерирует
токен авторизационной сессиив видеuuid4, помещает пару телефон-токен в Redis с заданным TTL (в моем случае 5 мин) и возвращает фронту этот токенфронт делает диплинк на ТГ-бота и передает в параметрах полученный от бэка токен. Ссылка вида
https://t.me/BOT?start={auth_token}пользователь открывает Telegram. Бот парсит параметр auth_token, далее предлагает пользователю поделиться контактом, пользователь соглашается
после того, как номер телефона от пользователя получен, бот проверяет в Redis пару телефон-токен и если все нашлось - создает пользователю сессию: в моем случае - это пара access/refresh токенов и отправляет в чат с пользователем сообщение об успешной авторизации и необходимости далее вернуться на сайт. Если же пары телефон-токен в Redis нет, то бот сообщает об ошибке
если в Redis есть пара телефон-токен авторизации, бэк (бот) идет в таблицу с пользователями и ищет юзера по номеру телефона, если его нет - создает нового пользователя. Далее удаляем из Redis найденную пару, Генерируем access/refresh токены и записываем их в соответствующую таблицу
Вспомним на минутку, что у нас REST API, то есть фронт ничего не знает о действиях пользователя в ТГ-боте и тем более о том, что там на сервере происходит: удалось авторизовать пользователя или нет. Поэтому после того, как фронт отдает юзеру диплинк на ТГ-бота, он (фронт) начинает с определенной периодичностью опрашивать бэк на предмет статуса авторизации для пользователя, передавая пару телефон-токен авторизации. Если бэк отвечает 204 - авторизация еще в процессе, то есть пользователь не закончил процесс, если 200 и с payload в виде пары access/refresh токенов - авторизация успешна, если 401 - авторизация не удалась.
Процесс создания нового ТГ-бота описывать смысла не вижу, давайте сразу в пойдем смотреть код. У меня тут бэк на Python на Django, для бота - TeleBot.
Для начала рассмотрим пакет с названием tg_bot:
# initial.py
from django.conf import settings
from telebot import TeleBot
from telebot.types import BotCommand
bot = TeleBot(token=settings.TG_BOT_TOKEN, parse_mode="HTML")
commands = [
BotCommand(
"start",
"Для авторизации необходимо нажать кнопку 'Поделиться номером телефона'",
),
]
bot.set_my_commands(commands)
# handlers.py
from loguru import logger
from telebot import types
from tg_bot.initial import bot
from user_auth.services.auth_service import AuthService
from user_auth.utils import save_token_and_chat_id_to_cache
@bot.message_handler(commands=["start"])
def send_contact_request(message) -> None:
command_args = message.text.split()
if len(command_args) != 2:
bot.send_message(message.chat.id, "Неверный код авторизации")
return
# сохраняем связку токена и id чата для дальнейшей проверки при вводе телефона пользователем
save_token_and_chat_id_to_cache(command_args[1], message.chat.id)
logger.debug(f"Saved token {command_args[1]} and chat id {message.chat.id} to cache")
keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
contact_button = types.KeyboardButton(text="Поделиться номером телефона", request_contact=True)
keyboard.add(contact_button)
bot.send_message(
message.chat.id, "Пожалуйста, поделитесь своим номером телефона:", reply_markup=keyboard
)
@bot.message_handler(content_types=["contact"])
def handle_contact(message) -> None:
contact = message.contact
phone_number = contact.phone_number
logger.debug(f"Received phone number: {phone_number}")
auth_service = AuthService(phone_number)
if auth_service.check_auth_token_from_chat_id(message.chat.id):
auth_service.obtain_pair_tokens()
bot.send_message(message.chat.id, "Спасибо! Авторизация прошла успешно, вернитесь на сайт")
return
auth_service.revoke_auth_token()
bot.send_message(message.chat.id, "Ошибка авторизации, повторите попытку")
Ну и запускаем это дело при помощи python manage.py tg_bot, для этого:
# management/commands/tg_bot.py
from django.core.management.base import BaseCommand
from loguru import logger
from tg_bot.handlers import bot
class Command(BaseCommand):
help = "Запуск телеграм бота"
def handle(self, *args, **kwargs):
logger.info("Запуск бота...")
bot.infinity_polling()
Код AuthService приводить смысла не вижу, так как весь процесс описан выше, можете его сами имплементировать как вам больше нравится.
Не забываем добавить tg_bot в INSTALLED_APPS в settings.py
Ну вот, собственно говоря, и все, надеюсь было интересно и полезно :-)
Комментарии (22)

ky0
13.10.2025 17:18При логине через внешнего провайдера телефон не нужен - что через Гугл, что через ТГ - достаточно того, что пользователь подтвердил телефон там, у них. Получаете номер - сразу начинается обработка ПД со всем сопутствующим головняком, зачем он вам?

shoytov Автор
13.10.2025 17:18а как мы будем получать номер телефона юзера, если в дальнейшем решим изменить механику авторизации? если в дальнейшем все же перейти на подтверждение по смс

inkelyad
13.10.2025 17:18а как мы будем получать номер телефона юзера, если в дальнейшем решим изменить механику авторизации?
Сделаете "Появился новый способ авторизации. <Далее идет описание, через что>. Если хотите перейти и настроить - делай то-то." И уже в 'то-то' запрашивайте все что надо для этого способа.

ksokol
13.10.2025 17:18А это не то, что делает https://core.telegram.org/gateway?

shoytov Автор
13.10.2025 17:18опишите плз процесс как вы видите

ksokol
13.10.2025 17:18Человек вводит при регистрации номер. Телеграм отправляет ему уведомление с кодом, который человек вводит в вашей форме. То есть это полный аналог подтверждения по смс, только через телеграм, и с низкой стоимостью сообщения.

shoytov Автор
13.10.2025 17:18Спасибо, я не знал про это, изучу на досуге

ksokol
13.10.2025 17:18Да, штука удобная и быстрая. Деньги берут только за успешные доставки в отличие от смс.
Но минус очевиден: подходит только если есть телеграм. В России, например, не проблема. Но в других странах с его распространённостью все заметно хуже.
Да, ещё не забыть, что есть люди, у которых телеграм к номеру не привязан. Точнее, привязан к +888. Для них нужно отдельное решение.

Apokalepsis
13.10.2025 17:18Это называется OTP коды. И отправлять их можно не только в телегу но и в WhatsApp, Viber, VK. Выстраивая полноценные флоу.

inkelyad
13.10.2025 17:18А еще лучше - именно для авторизации использовать Passkeys (сейчас их практически всюду завезли) и оставьте эти коды (особенно в связке с телефоном) в покое.
А все контакты для связи - пускай потом в профиле заполняются. Если оно действительно нужно. Потому что общение можно и просто в личном кабинете сервиса делать.
ksokol
13.10.2025 17:18Я пока не видел, чтобы passkey был единственным способом аутентификации. А это означает, что в итоге все равно вопрос OTP остаётся.

koltykov
13.10.2025 17:18А зачем человеку вводить номер телефона? Телеграм же его и так отдает когда нажимаем "поделиться номером"?

shoytov Автор
13.10.2025 17:18если на фронте не будет ввода телефона, то мы потеряем связь фронт-бэк, так как все будет происходить на стороне бэка, а фронт понятия иметь не будет что и как и что это за пользователь и где для него токены брать. Или я неправильно понял вашу мысль...

koltykov
13.10.2025 17:18Я просто разработал пару миниапп приложений для TG/VK (в обычном TG боте тоже так делал) и там используется просто функция "поделится номером". Когда фронт получает номер, отправляем на бэк асинхронно полученный номер и все. Т.е. у меня клиент нигде вручную не вводит своего номера телефона.
Понятно, что привязанного к TG номера может не быть , но пока таких клиентов не попадалось, да и бизнес логика там решает такую проблему другим методом
shoytov Автор
13.10.2025 17:18а как у вас фронт получает номер телефона? у меня фронт - это react приложение, которое входит в коммуникацию с бэком по REST API

koltykov
13.10.2025 17:18Ну у меня фронт на VUE.
Получаю через "request_contact"https://core.telegram.org/bots/api#keyboardbutton
В одном из моих приложений вот так это в UI реализовано. Т.е. пользователь заходит в бота, делится телефоном который отправляется в бэк и получает список своих заказов. Т.е. все одной кнопкой.
У вас тот же метод. Но просто не могу понять, зачем человеку вручную вводить номер телефона, если он и так отдается телегой. Вот про это я

shoytov Автор
13.10.2025 17:18я что-то торможу и все равно не понимаю: вот есть фронт - по сути собранная статика JS, вот есть бэк - Django приложение. фронт посылает запросы на бэк, бэк что-то отвечает, когда нужна авторизация - фронт делает редирект юзера на тг-бота (по сути, тоже приложение бэкенда в соседнем контейнере), дальше фронт как узнает авторизационные данные? Фронту по факту нужны токены access/refresh. Опишите, пожалуйста, подробно механизм их получения фронтом в вашем флоу без ввода телефона
sergiks
Пара сомнений:
"поделиться номером" ведь отдаст номер, на который зарегистрирован Тг аккаунт? Но номер этот не обязательно совпадает с активно используемым номером телефона пользователя, клторый он вводит на сайте.
вместо поллинга с фронта может, прикрутить websocket, чтобы бэк мог отправить сообщение, как только, так сразу?
shoytov Автор
отличные вопросы, отвечаю:
1 - да, все верное, нужно поделиться номером, который привязан к аккаунту и который юзер указал в форме
2 - не бывает идеальных решений, есть компромиссы или личные предпочтения (не люблю я веб-сокеты:))
swcasimiro
А я с вами не согласен. То что, если человек введет номер телефона без посредника в виде телеграма. В любом случае не гарантирует, то что номер будет настоящий. Кучу лет живут сервисы, которые дают в аренду сим-карты для получения кода. Если человек хочет скрыть номер. Он в любом случае - это сделает.
А насчет веб-сокетов согласен. Даже для стартапа, но при аренде vps, хотябы 2 ram.
shoytov Автор
Да тут нет проблемы ненастоящего номера, ну не настоящий введёт - сам себе злой Буратино, дальше не будет истории заказов и персонализации