Обзор
Cabinet поддерживает два способа авторизации через Telegram:
| Способ | Описание | Когда использовать |
|---|
| Telegram Widget | Iframe-виджет от Telegram | Стандартная кнопка авторизации |
| Telegram OIDC | OpenID Connect через oauth.telegram.org | Popup-авторизация, современный метод |
Оба способа работают параллельно. Когда TELEGRAM_OIDC_ENABLED=true, Cabinet показывает кастомную кнопку с popup-авторизацией. Иначе — стандартный iframe-виджет Telegram.
Требуется CABINET_ENABLED=true и заполненный CABINET_URL.
Telegram OIDC (рекомендуемый)
OIDC (OpenID Connect) — стандартный протокол авторизации. Telegram реализует его через oauth.telegram.org с поддержкой PKCE.
Официальная документация Telegram: Telegram Login — OpenID Connect
Эндпоинты протокола
| Назначение | URL |
|---|
| Discovery | https://oauth.telegram.org/.well-known/openid-configuration |
| Авторизация | https://oauth.telegram.org/auth |
| Обмен кода на токен | https://oauth.telegram.org/token |
| Публичные ключи (JWKS) | https://oauth.telegram.org/.well-known/jwks.json |
| JavaScript SDK | https://oauth.telegram.org/js/telegram-login.js?3 |
Шаг 1: Регистрация приложения в BotFather
- Откройте Mini App @BotFather
- Выберите вашего бота
- Перейдите в Bot Settings → Web Login
- Зарегистрируйте Allowed URLs — укажите все домены, с которых будет выполняться авторизация:
- Origin кабинета:
https://cabinet.example.com
- Redirect URI (если используете Authorization Code Flow напрямую):
https://cabinet.example.com/auth/telegram/callback
Telegram обрабатывает вход только с предварительно зарегистрированных URL. Без регистрации домена авторизация не будет работать.
- BotFather отобразит Client ID и Client Secret — сохраните их
Client ID — это числовой ID вашего бота (тот же, что в BOT_TOKEN до двоеточия). Например, для токена 1234567890:AABBCCdd... Client ID = 1234567890.
Рекомендация Telegram: установите аватар бота, совпадающий с логотипом вашего сайта, а имя бота должно отражать связь с сервисом. Пользователь увидит аватар и имя бота при подтверждении авторизации.
Шаг 2: Настройка .env бота
# Включить OIDC авторизацию
TELEGRAM_OIDC_ENABLED=true
# Client ID (числовой ID бота)
TELEGRAM_OIDC_CLIENT_ID=1234567890
# Client Secret от BotFather
TELEGRAM_OIDC_CLIENT_SECRET=your_oidc_client_secret_here
Перезапустите бота:
Доступные scopes
| Scope | Описание | Возвращаемые claims |
|---|
openid | Обязательный. Уникальный ID и временная метка | sub, iss, iat, exp |
profile | Базовая информация | name, preferred_username, picture |
phone | Верифицированный номер телефона (требует отдельного согласия) | phone_number |
telegram:bot_access | Разрешение боту отправлять сообщения пользователю после входа | — |
Cabinet запрашивает scopes openid и profile. Scope phone не используется.
Структура id_token
После авторизации Telegram возвращает JWT id_token с подписью RS256. Пример декодированного токена:
{
"iss": "https://oauth.telegram.org",
"aud": "1234567890",
"sub": "1234123412341234123",
"iat": 1700000000,
"exp": 1700003600,
"id": 987654321,
"name": "John Doe",
"preferred_username": "johndoe",
"picture": "https://cdn4.telesco.pe/file/..."
}
| Claim | Тип | Описание |
|---|
iss | string | Издатель — всегда https://oauth.telegram.org |
aud | string | Аудитория — числовой Client ID бота |
sub | string | Уникальный идентификатор пользователя |
id | number | Числовой Telegram ID пользователя |
name | string | Имя пользователя |
preferred_username | string | Username в Telegram (без @) |
picture | string | URL аватара |
phone_number | string | Номер телефона (только если запрошен scope phone) |
iat | number | Unix-время выдачи токена |
exp | number | Unix-время истечения токена |
Как работает (пошагово)
┌──────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Браузер │ │ Cabinet Frontend│ │ API бота │ │ Telegram │
│ │ │ │ │ │ │ OIDC │
└────┬─────┘ └────────┬─────────┘ └────────┬─────────┘ └──────┬──────┘
│ │ │ │
│ 1. Загрузка │ │ │
│ страницы входа │ │ │
│──────────────────>│ │ │
│ │ 2. GET /cabinet/ │ │
│ │ branding/ │ │
│ │ telegram-widget │ │
│ │──────────────────────>│ │
│ │ {oidc_enabled: true, │ │
│ │ oidc_client_id: "…"}│ │
│ │<──────────────────────│ │
│ │ │ │
│ │ 3. Загрузка SDK │ │
│ │ telegram-login.js │ │
│ │─────────────────────────────────────────────>│
│ │ 4. Telegram.Login │ │
│ │ .init({client_id}) │ │
│ │ │ │
│ 5. Клик по │ │ │
│ кнопке "Войти" │ │ │
│──────────────────>│ │ │
│ │ 6. Telegram.Login │ │
│ │ .open() │ │
│ │─────────────────────────────────────────────>│
│ │ │ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 7. Popup: пользователь подтверждает авторизацию │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ │ 8. Callback: │ │
│ │ {id_token: "eyJ…"} │ │
│ │<────────────────────────────────────────────│
│ │ │ │
│ │ 9. POST /cabinet/ │ │
│ │ auth/telegram/oidc │ │
│ │ {id_token: "eyJ…"} │ │
│ │──────────────────────>│ │
│ │ │ 10. GET JWKS │
│ │ │──────────────────────>│
│ │ │ {keys: [...]} │
│ │ │<─────────────────────│
│ │ │ │
│ │ │ 11. Валидация JWT: │
│ │ │ - подпись RS256 │
│ │ │ - iss, aud, exp │
│ │ │ - replay-защита │
│ │ │ │
│ │ 12. {access_token, │ │
│ │ refresh_token, user}│ │
│ │<──────────────────────│ │
│ │ │ │
│ 13. Авторизован │ │ │
│<──────────────────│ │ │
Детали каждого шага:
- Пользователь открывает страницу входа Cabinet
- Frontend запрашивает конфигурацию виджета с API бота (публичный эндпоинт, без авторизации)
- Если
oidc_enabled=true — загружается JavaScript SDK Telegram (telegram-login.js?3)
- SDK инициализируется с
client_id и callback-функцией:
Telegram.Login.init({
client_id: 1234567890,
request_access: ['write'],
lang: 'ru'
}, callback)
- Пользователь нажимает кнопку «Войти через Telegram»
- SDK открывает popup-окно авторизации на
oauth.telegram.org
- Пользователь видит имя и аватар бота, подтверждает вход
- Popup закрывается, SDK вызывает callback с
{id_token: "eyJ…"} или {error: "..."}
- Frontend отправляет
id_token на API бота
- Бот загружает публичные ключи JWKS (кэшируются на 1 час)
- Бот валидирует JWT-подпись и claims
- Бот создаёт/находит пользователя, возвращает JWT-токены Cabinet
- Пользователь авторизован и перенаправляется на главную страницу
JavaScript SDK
Cabinet использует Telegram Login SDK. Доступные методы:
| Метод | Описание |
|---|
Telegram.Login.init(options, callback) | Инициализация SDK и регистрация callback |
Telegram.Login.open([callback]) | Открыть popup авторизации |
Telegram.Login.auth(options, callback) | Открыть popup с явными параметрами |
Параметры инициализации (InitOptions):
| Параметр | Тип | Описание |
|---|
client_id | number | Числовой ID бота (обязательный) |
request_access | string[] | Запрашиваемые разрешения: ['write'] и/или ['phone'] |
lang | string | Язык интерфейса popup (ru, en, es и др.) |
nonce | string | Серверный random-string для защиты от replay-атак |
Структура callback:
{
id_token: "eyJhbGciOiJSUzI1NiIs...", // JWT (при успехе)
user: { // Декодированные claims (клиентская копия)
name: "John Doe",
preferred_username: "johndoe",
picture: "https://cdn4.telesco.pe/..."
},
error: "POPUP_CLOSED" // Строка ошибки (при неудаче)
}
Данные из user в callback — только для предварительного отображения. Для авторизации всегда используйте id_token с серверной валидацией.
Валидация id_token на сервере
Бот выполняет следующие проверки при получении id_token:
- Подпись — загружает публичные ключи JWKS, находит ключ по
kid из заголовка JWT, проверяет подпись RS256
- Издатель —
iss должен быть https://oauth.telegram.org
- Аудитория —
aud должен совпадать с TELEGRAM_OIDC_CLIENT_ID
- Срок действия —
exp не должен быть в прошлом
- Обязательные claims — проверяется наличие
exp, iat, iss, aud, sub
- Replay-защита — каждый
id_token может быть использован только один раз (хэш хранится до 10 минут)
Ротация ключей (JWKS)
Telegram может ротировать ключи подписи. Бот автоматически обновляет JWKS-кэш:
| Сценарий | Поведение |
|---|
| Штатное обновление | Каждый час (TTL кэша) |
Неизвестный kid | Немедленное обновление JWKS |
Повторный неизвестный kid | Cooldown 30 секунд между принудительными обновлениями |
Rate Limiting
Эндпоинт POST /cabinet/auth/telegram/oidc защищён rate limiter:
- 10 запросов в 60 секунд на один IP-адрес
- При превышении — HTTP 429 с заголовком
Retry-After
- Режим fail-closed: при ошибке rate limiter запросы отклоняются
Создание и привязка аккаунтов
При авторизации через OIDC:
- Существующий пользователь (по Telegram ID) — выполняется вход, обновляются поля
first_name, username, last_name из актуальных claims
- Новый пользователь — создаётся аккаунт с данными из claims. Если передан
referral_code или campaign_slug — применяются соответствующие бонусы
- Неактивный аккаунт — авторизация отклоняется с HTTP 403
Совместимость с другими OIDC-системами
Telegram OIDC совместим со стандартными OIDC-провайдерами и может использоваться с:
- Keycloak — как внешний Identity Provider
- Authentik — через Generic OIDC
- Auth0 — через Custom Social Connection
Telegram не предоставляет отдельный UserInfo endpoint. Все claims содержатся в id_token. Некоторые OIDC-библиотеки могут требовать настройки для пропуска запроса UserInfo.
Стандартный iframe-виджет Telegram. Используется, когда OIDC отключен (TELEGRAM_OIDC_ENABLED=false).
Настройка домена в BotFather
- Откройте @BotFather в Telegram
- Выберите вашего бота
- Bot Settings → Domain → укажите домен кабинета (например,
cabinet.example.com)
Без привязки домена в BotFather виджет авторизации не будет работать. Telegram проверяет, что виджет загружается с авторизованного домена.
Кастомизация виджета
Внешний вид виджета настраивается через переменные окружения или через Admin Cabinet → Настройки:
| Переменная | Описание | По умолчанию |
|---|
TELEGRAM_WIDGET_SIZE | Размер: large, medium, small | large |
TELEGRAM_WIDGET_RADIUS | Скругление углов (0-20 пикселей) | 8 |
TELEGRAM_WIDGET_USERPIC | Показывать аватар пользователя | true |
TELEGRAM_WIDGET_REQUEST_ACCESS | Запрашивать write access | true |
Настройки виджета можно менять без перезапуска бота через Admin Cabinet → Настройки. Значения из базы данных имеют приоритет над переменными окружения.
Как работает
- Cabinet загружает iframe-виджет Telegram Login на странице входа
- Пользователь нажимает кнопку и подтверждает вход в Telegram
- Telegram возвращает данные пользователя (id, first_name, auth_date, hash) с HMAC-SHA256 подписью
- Cabinet отправляет данные на API бота
- Бот проверяет подпись через
SHA256(BOT_TOKEN) и валидирует auth_date (не старше 24 часов)
- Бот возвращает JWT-токены для авторизованной сессии
| Характеристика | OIDC | Widget |
|---|
| Метод авторизации | Popup-окно | Iframe / Redirect |
| Подпись данных | JWT RS256 (JWKS) | HMAC-SHA256 (BOT_TOKEN) |
| Стандарт | OpenID Connect | Telegram-проприетарный |
| Replay-защита | Встроенная (одноразовый токен) | Нет (проверка auth_date) |
| Совместимость с IdP | Keycloak, Authentik, Auth0 | Только Telegram |
| Настройка | BotFather → Web Login | BotFather → Domain |
| Кастомизация кнопки | Полная (своя кнопка) | Ограничена (iframe) |
Конфигурационный API
Публичный эндпоинт (без авторизации). Возвращает конфигурацию для страницы входа.
Ответ:
{
"bot_username": "my_vpn_bot",
"size": "large",
"radius": 8,
"userpic": true,
"request_access": true,
"oidc_enabled": true,
"oidc_client_id": "1234567890"
}
| Поле | Тип | Описание |
|---|
bot_username | string | Username бота (без @) |
size | string | Размер виджета: large, medium, small |
radius | number | Скругление углов (0-20) |
userpic | boolean | Показывать аватар |
request_access | boolean | Запрашивать write access |
oidc_enabled | boolean | Включен ли OIDC |
oidc_client_id | string | Client ID (пусто если OIDC выключен) |
Frontend использует oidc_enabled и oidc_client_id чтобы выбрать способ авторизации: OIDC popup или legacy widget.
POST /cabinet/auth/telegram/oidc
Валидация id_token и авторизация пользователя.
Запрос:
{
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"campaign_slug": "summer2025",
"referral_code": "abc123"
}
| Поле | Тип | Описание |
|---|
id_token | string | JWT id_token от Telegram (макс. 4096 символов) |
campaign_slug | string? | Slug рекламной кампании (опционально) |
referral_code | string? | Реферальный код (опционально) |
Ответ (200):
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "bearer",
"expires_in": 900,
"user": {
"id": 42,
"telegram_id": 987654321,
"username": "johndoe",
"first_name": "John",
"email": null,
"balance_kopeks": 0,
"referral_code": "abc123"
},
"campaign_bonus": null
}
Ошибки:
| Код | Причина |
|---|
| 400 | Telegram OIDC is not configured — OIDC выключен или Client ID не задан |
| 401 | Invalid or expired Telegram OIDC token — невалидный JWT, истёкший, или повторное использование |
| 401 | Missing user ID in OIDC claims — нет id или sub в токене |
| 403 | User account is not active — аккаунт заблокирован |
| 429 | Rate limit (10 req / 60s per IP) |
Итоговая настройка .env
Пример с виджетом и OIDC:
# === CABINET (обязательно) ===
CABINET_ENABLED=true
CABINET_URL=https://cabinet.example.com
CABINET_JWT_SECRET=your_jwt_secret_here
CABINET_ALLOWED_ORIGINS=https://cabinet.example.com
# === TELEGRAM WIDGET (кастомизация) ===
TELEGRAM_WIDGET_SIZE=large
TELEGRAM_WIDGET_RADIUS=8
TELEGRAM_WIDGET_USERPIC=true
TELEGRAM_WIDGET_REQUEST_ACCESS=true
# === TELEGRAM OIDC (рекомендуется) ===
TELEGRAM_OIDC_ENABLED=true
TELEGRAM_OIDC_CLIENT_ID=1234567890
TELEGRAM_OIDC_CLIENT_SECRET=your_oidc_secret
Проверка
API: проверка конфигурации
curl -s https://hooks.example.com/cabinet/branding/telegram-widget | jq
Ответ должен содержать oidc_enabled: true и oidc_client_id с ID бота.
- Откройте Cabinet в браузере
- На экране входа должна появиться iframe-кнопка Telegram
- Нажмите — откроется авторизация Telegram
- После подтверждения — вход в Cabinet
OIDC (если включен)
- На странице входа отображается кастомная кнопка «Войти через Telegram»
- При нажатии откроется popup-окно авторизации
- После подтверждения popup закроется и пользователь авторизован
Устранение проблем
Кнопка «Войти через Telegram» не появляется
- Проверьте конфигурацию:
curl -s https://hooks.example.com/cabinet/branding/telegram-widget | jq
- Если
oidc_enabled: false:
- Проверьте
TELEGRAM_OIDC_ENABLED=true в .env
- Проверьте что
TELEGRAM_OIDC_CLIENT_ID заполнен
- Перезапустите бота
- Если OIDC выключен, но виджет тоже не появляется:
- Домен кабинета добавлен в BotFather → Bot Settings → Domain?
VITE_TELEGRAM_BOT_USERNAME указан верно (без @)?
- Страница загружается по HTTPS?
- URL кабинета зарегистрирован в BotFather → Bot Settings → Web Login → Allowed URLs?
- Убедитесь что домен в
CABINET_URL совпадает с зарегистрированным
TELEGRAM_OIDC_ENABLED=true в .env?
TELEGRAM_OIDC_CLIENT_ID не пустой?
- Перезапустили бота после изменений?
”Invalid or expired Telegram OIDC token” (401)
| Причина | Решение |
|---|
| Неверный Client ID | TELEGRAM_OIDC_CLIENT_ID должен совпадать с числовым ID бота |
| Токен просрочен | Пользователь слишком долго подтверждал — повторите вход |
| Повторное использование | Каждый id_token одноразовый — повторите вход |
| Расхождение часов | Настройте NTP на сервере (допуск ±5 минут) |
“Unknown kid in id_token” в логах
Telegram ротировал ключи подписи. Бот автоматически обновит JWKS.
Если ошибка повторяется:
- Проверьте доступность
https://oauth.telegram.org/.well-known/jwks.json с вашего сервера:
curl -s https://oauth.telegram.org/.well-known/jwks.json | jq '.keys | length'
- Убедитесь что нет блокировки исходящих HTTPS-запросов к
oauth.telegram.org
- Проверьте DNS-резолвинг:
Rate limit (429)
Эндпоинт OIDC ограничен 10 запросами в 60 секунд на IP. Это защита от брутфорса. В нормальной работе лимит не должен срабатывать.
Если срабатывает в production:
- Проверьте что reverse proxy передаёт реальный IP пользователя (заголовок
X-Forwarded-For)
- Без этого все запросы идут с IP прокси и быстро исчерпывают лимит