Всем привет. Недавно пришлось решать проблему идентификации пользователя по номеру телефона в маленьком стартапе, позволяющим оформлять и оплачивать заказы онлайн. Почему именно номер телефона, а не электронная почта, например, или авторизация через соц. сети? Телефон сейчас, как мне кажется, де-факто стандарт для таких кейсов - это во-первых, а во-вторых, используя телефонный номер, можно подключать разные варианты его подтверждения: от смс до звонков с дальнейшим вводом либо кода из смс, либо последних цифр звонившего номера. Думаю, тут всем знакомы подобные механики.

Изначально я сразу и предложил использовать механику с СМС-шлюзом, но так как я тут имею дело со стартапом без инвестиций, то меня попросили придумать как на первоначальном этапе можно сэкономить (в идеале обойтись на этом этапе совсем без затрат), так как основной целью запуска было тестирование бизнес-идеи.

По предварительным исследованиям целевая пользовательская аудитория продукта является пользователями мессенджера 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)


  1. sergiks
    13.10.2025 17:18

    Пара сомнений:

    1. "поделиться номером" ведь отдаст номер, на который зарегистрирован Тг аккаунт? Но номер этот не обязательно совпадает с активно используемым номером телефона пользователя, клторый он вводит на сайте.

    2. вместо поллинга с фронта может, прикрутить websocket, чтобы бэк мог отправить сообщение, как только, так сразу?


    1. shoytov Автор
      13.10.2025 17:18

      отличные вопросы, отвечаю:
      1 - да, все верное, нужно поделиться номером, который привязан к аккаунту и который юзер указал в форме
      2 - не бывает идеальных решений, есть компромиссы или личные предпочтения (не люблю я веб-сокеты:))


    1. swcasimiro
      13.10.2025 17:18

      А я с вами не согласен. То что, если человек введет номер телефона без посредника в виде телеграма. В любом случае не гарантирует, то что номер будет настоящий. Кучу лет живут сервисы, которые дают в аренду сим-карты для получения кода. Если человек хочет скрыть номер. Он в любом случае - это сделает.

      А насчет веб-сокетов согласен. Даже для стартапа, но при аренде vps, хотябы 2 ram.


      1. shoytov Автор
        13.10.2025 17:18

        Да тут нет проблемы ненастоящего номера, ну не настоящий введёт - сам себе злой Буратино, дальше не будет истории заказов и персонализации


  1. ky0
    13.10.2025 17:18

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


    1. shoytov Автор
      13.10.2025 17:18

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


      1. inkelyad
        13.10.2025 17:18

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

        Сделаете "Появился новый способ авторизации. <Далее идет описание, через что>. Если хотите перейти и настроить - делай то-то." И уже в 'то-то' запрашивайте все что надо для этого способа.


        1. shoytov Автор
          13.10.2025 17:18

          Ну такое с точки зрения юзер флоу...


  1. ksokol
    13.10.2025 17:18

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


    1. shoytov Автор
      13.10.2025 17:18

      опишите плз процесс как вы видите


      1. ksokol
        13.10.2025 17:18

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


        1. shoytov Автор
          13.10.2025 17:18

          Спасибо, я не знал про это, изучу на досуге


          1. ksokol
            13.10.2025 17:18

            Да, штука удобная и быстрая. Деньги берут только за успешные доставки в отличие от смс.

            Но минус очевиден: подходит только если есть телеграм. В России, например, не проблема. Но в других странах с его распространённостью все заметно хуже.

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


      1. Apokalepsis
        13.10.2025 17:18

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


        1. inkelyad
          13.10.2025 17:18

          А еще лучше - именно для авторизации использовать Passkeys (сейчас их практически всюду завезли) и оставьте эти коды (особенно в связке с телефоном) в покое.
          А все контакты для связи - пускай потом в профиле заполняются. Если оно действительно нужно. Потому что общение можно и просто в личном кабинете сервиса делать.


          1. ksokol
            13.10.2025 17:18

            Я пока не видел, чтобы passkey был единственным способом аутентификации. А это означает, что в итоге все равно вопрос OTP остаётся.


  1. koltykov
    13.10.2025 17:18

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


    1. shoytov Автор
      13.10.2025 17:18

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


      1. koltykov
        13.10.2025 17:18

        Я просто разработал пару миниапп приложений для TG/VK (в обычном TG боте тоже так делал) и там используется просто функция "поделится номером". Когда фронт получает номер, отправляем на бэк асинхронно полученный номер и все. Т.е. у меня клиент нигде вручную не вводит своего номера телефона.
        Понятно, что привязанного к TG номера может не быть , но пока таких клиентов не попадалось, да и бизнес логика там решает такую проблему другим методом


        1. shoytov Автор
          13.10.2025 17:18

          а как у вас фронт получает номер телефона? у меня фронт - это react приложение, которое входит в коммуникацию с бэком по REST API


          1. koltykov
            13.10.2025 17:18

            Ну у меня фронт на VUE.
            Получаю через "request_contact"

            https://core.telegram.org/bots/api#keyboardbutton

            В одном из моих приложений вот так это в UI реализовано. Т.е. пользователь заходит в бота, делится телефоном который отправляется в бэк и получает список своих заказов. Т.е. все одной кнопкой.

            У вас тот же метод. Но просто не могу понять, зачем человеку вручную вводить номер телефона, если он и так отдается телегой. Вот про это я


            1. shoytov Автор
              13.10.2025 17:18

              я что-то торможу и все равно не понимаю: вот есть фронт - по сути собранная статика JS, вот есть бэк - Django приложение. фронт посылает запросы на бэк, бэк что-то отвечает, когда нужна авторизация - фронт делает редирект юзера на тг-бота (по сути, тоже приложение бэкенда в соседнем контейнере), дальше фронт как узнает авторизационные данные? Фронту по факту нужны токены access/refresh. Опишите, пожалуйста, подробно механизм их получения фронтом в вашем флоу без ввода телефона