Авторизация на сайте очень важна, с ее помощью можно разделять функционал или контент сайта для посетителей в зависимости от их ролей. На этом сайте это возможность оставлять комментарии под статьями - доступно только для авторизованных пользователей.
У Телеграм есть официальный виджет для авторизации, по мне так он очень удобный, но у него есть ограничения - на момент написания статьи он недоступен без обхода блокировки Телеграм. Настраивается он очень просто и работает эффективно. Но речь сейчас не о нем.
Сегодня рассмотрим простой алгоритм для регистрации и авторизации пользователей с помощью Телеграм. Взаимодействие сайта и Телеграм будет происходить через бот.
Весь процесс очень прост. Создадим ссылку по нажатию на которую будут запущены два действия:
- в бот мы передадим значение в параметре start, которое обработаем и запишем в базу вместе с данными пользователя
- запустим JS скрипт, который будет с интервалом в 2 секунды делать AJAX запрос на сервер для проверки появления в базе записи переданного в бот значения, и в случае обнаружения произведет авторизацию пользователя
Для начала создадим таблицу users в базе MySQL (используйте кодировку utf8mb4_unicode_ci
)
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`telegram_id` bigint(20) DEFAULT NULL,
`first_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`auth_key` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
* * *
index.php
Этот файл будет выводить ссылку на авторизацию через бот в случае, если пользователь не авторизован. JS скрипт вешает на ссылку прослушку на событие click с функцией, которая через 2-х секундный интервал делает запрос на сервер. Когда ответ с сервера возвращается положительный, скрипт перезагружает страницу.
Если у пользователя обнаружена кука token то скрипт делает запрос в бд и получает первое и второе имя пользователя, которые выводит вместе с ссылкой на выход. При обработке действия "Выход", скрипт просто удаляет куку и перезагружаем страницу.
Не забудьте указать ___USERNAME__ВАШЕГО__БОТА___
<?php
// проверяем на действие - выхода
if($_GET['act'] == "logout") {
// удаляем куку
setcookie("token", "", time() - 60);
// перезагружаем страницу
header("Location: index.php");
} else {
// подключаем соединение с БД
require_once("Db.php");
// создаем объект соединения
$db = new Db();
// проверяем на авторизацию
if($_COOKIE['token']) {
// проверяем на корректность
if(!preg_match('~^[a-f0-9]{32}+$~', $_COOKIE['token'])) {
// выходим если проверка не прошла
exit();
}
// делаем запрос в БД
$order = $db->connect()->prepare("SELECT * FROM users WHERE auth_key = :key LIMIT 1");
$order->execute(['key' => $_COOKIE['token']]);
// если запись есть то работаем
if ($order->rowCount() > 0) {
$orderRaw = $order->fetch();
// Выводим имя
echo trim($orderRaw['first_name'] .' '.$orderRaw['last_name'])." | ";
// ссылка на выход
echo "<a href='?act=logout'>Выход</a>";
} else {
// удаляем кукку
header("Location: index.php?act=logout");
}
} else {
// генерируем ключ
$key = md5(rand(0, 1000));
///////////////////////////////////////////////
//// username Bot
///////////////////////////////////////////////
$bot = "___USERNAME__ВАШЕГО__БОТА___";
// создаем ссылки на авторизацию
echo "<a href='tg://resolve?domain=".$bot."&start=".$key."' data-key='".$key."' class='link_auth'>
Авторизоваться через Телеграм</a>";
?>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
jQuery(function ($) {
$("body").on("click", ".link_auth", function () {
var auth_key = $(this).data("key");
var timerId = setInterval(function () {
$.ajax({
type: "GET",
dataType: "json",
url: "checkKey.php",
data: {
key: auth_key
},
success: function (data) {
if (data.result == "success") {
clearInterval(timerId);
setTimeout(function () {
location.reload();
}, 1000);
}
}
});
}, 2000);
});
});
</script>
<?
}
}
?>
* * *
webHook.php
Это вебхук для бота, в который Telegram Bot API будет направлять объекты с данными запроса от пользователя. Мы подключаем класс Auth, создаем его объект и запускаем метод инициализации. Как установить вебхук можете почитать в статье "Регистрируем бот у @BotFather, устанавливаем WebHook"
<?php
// определим кодировку UTF-8
header("HTTP/1.1 200 OK");
header('Content-type: text/html; charset=utf-8');
// подключаем класс авторизации
require_once("Auth.php");
// создаем объект авторизации
$auth = new Auth();
// запускаем
$auth->init();
?>
* * *
Auth.php
После инициализации объекта, передаем данные от Bot API в роутер, в котором определяем, что делать с данными. Нас интересует объект message с переданным значением параметра start в виде ключа из 32 символов. Его мы передаем в метод авторизации. По telegram_id узнаем, есть ли такой пользователь у нас в базе, если есть, то обновляем ключ, если нет то добавляем пользователя в базу, при этом записываем все нужные нам данные и ключ для авторизации.
Тут нужно еще учесть тот момент, что если вам нужна актуальная информация о пользователе, то при обновлении ключа обновляйте и все нужные данные, ведь пользователь может поменять свои данные в Телеграм, и после следующей авторизации на вашем сайте вы будете его приветствовать именем, которое получили при его регистрации, но это на ваше усмотрение.
Не забудьте указать ___TOKEN__ВАШЕГО__БОТА___
<?php
// Подключаем класс для работы с БД
require_once("Db.php");
/**
* Class Auth
*/
class Auth extends Db
{
// токен API BOT
private $token = "___TOKEN__ВАШЕГО__БОТА___";
/** Инициализируем работу класса
* @return bool
*/
public function init()
{
// получаем данные от АПИ и преобразуем их в ассоциативный массив
$rawData = json_decode(file_get_contents('php://input'), true);
// направляем данные из бота в метод
// для определения дальнейшего выбора действий
$this->router($rawData);
// в любом случае вернем true для бот апи
return true;
}
/** Роутер
* @param $data array
* @return bool
*/
private function router($data)
{
// проверяем на объект Message
if (array_key_exists("message", $data)) {
// получаем чат
$chat_id = $data['message']['chat']['id'];
// проверяем на наличие объекта Text
if (array_key_exists("text", $data['message'])) {
// Получаем значение отправленных данных
$text = $data['message']['text'];
// Если это просто старт бота
if ($text == "/start") {
// отправляем сообщение
$this->botApiQuery("sendMessage", [
'chat_id' => $chat_id,
'text' => 'Бот авторизации'
]);
// Если это старт бота с передаваемым значением параметра start
} elseif (preg_match('~^(\/start)+ ([a-f0-9]{32}+)$~', $text, $matches)) {
// передаем в метод авторизации
$this->setAuth($data['message'], $matches[2]);
// если это какой-то другой текст
} else {
// просто пишем чего-нибудь в чат
$this->botApiQuery("sendMessage", [
'chat_id' => $chat_id,
'text' => 'Бот авторизации'
]);
}
// если это что-то другое - картинка или еще что-то
} else {
// просто пишем чего-нибудь в чат
$this->botApiQuery("sendMessage", [
'chat_id' => $chat_id,
'text' => 'Бот авторизации'
]);
}
}
// другие объекты не рассматриваем
return true;
}
/** Авторизация
* @param $data
* @param $key
*/
private function setAuth($data, $key)
{
// делаем запрос в БД
$order = $this->connect()->prepare("SELECT * FROM users WHERE telegram_id = :id LIMIT 1");
$order->execute(['id' => $data['chat']['id']]);
// если запись есть то обновляем
if ($order->rowCount() > 0) {
$orderRaw = $order->fetch();
// запрос на обновление
$update = $this->connect()->prepare("UPDATE users SET auth_key = :new_key WHERE id = :id");
// если обновили
if ($update->execute(['id' => $orderRaw['id'], 'new_key' => $key])) {
$text = "Вы успешно авторизовались. Возвращайтесь на сайт.";
} else {
$text = "При авторизации произошла ошибка";
}
} else {
// если записи нет то добавляем
$insert = $this->connect()->prepare("INSERT INTO users SET
telegram_id = :telegram_id,
first_name = :first_name,
last_name = :last_name,
username = :username,
auth_key = :auth_key");
// готовим данные
$array = [
'telegram_id' => $data['chat']['id'],
'first_name' => $data['chat']['first_name'],
'last_name' => $data['chat']['last_name'],
'username' => $data['chat']['username'],
'auth_key' => $key,
];
// если удалось добавить товар
if ($insert->execute($array)) {
$text = "Вы успешно авторизовались. Возвращайтесь на сайт.";
} else {
$text = "При авторизации произошла ошибка";
}
}
// отправляем сообщение
$this->botApiQuery("sendMessage", [
'chat_id' => $data['chat']['id'],
'text' => $text
]);
}
/** Работаем с API BOT
* @param $method
* @param array $fields
* @return mixed
*/
private function botApiQuery($method, $fields = array())
{
$ch = curl_init('https://api.telegram.org/bot' . $this->token . '/' . $method);
curl_setopt_array($ch, array(
CURLOPT_POST => count($fields),
CURLOPT_POSTFIELDS => http_build_query($fields),
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 10
));
$r = json_decode(curl_exec($ch), true);
curl_close($ch);
return $r;
}
}
?>
* * *
Db.php
Класс для получения соединения с базой данных. Укажите данные ваших настроек базы данных.
<?php
/**
* Class Db
*/
class Db {
// для соединения с БД
private $host = 'localhost';
private $db = '';
private $user = '';
private $pass = '';
private $charset = 'utf8mb4';
private $pdo = null;
/** Получаем соединение
* @return bool
*/
public function connect()
{
if(is_null($this->pdo)) {
$this->setPdo();
}
return $this->pdo;
}
/**
* Создаем соединение с БД
*/
private function setPdo()
{
// задаем тип БД, хост, имя базы данных и чарсет
$dsn = "mysql:host=".$this->host.";dbname=".$this->db.";charset=".$this->charset;
// дополнительные опции
$opt = [
// способ обработки ошибок - режим исключений
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// тип получаемого результата по-умолчанию - ассоциативный массив
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// отключаем эмуляцию подготовленных запросов
PDO::ATTR_EMULATE_PREPARES => false,
// определяем кодировку запросов
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
];
// // записываем объект PDO в свойство $this->pdo
$this->pdo = new PDO($dsn, $this->user, $this->pass, $opt);
}
}
?>
* * *
checkKey.php
Этот файл принимает AJAX запрос со страницы index.php когда пользователь нажимает ссылку для авторизации. Он по ключу проверяет наличии записи в базе, и в случае обнаружения обновляет ключ, записывает куки и возвращает положительный результат.
<?php
// подключаем соединение с БД
require_once("Db.php");
// создаем соединение с БД
$db = new Db();
// получаем ключ
$key = $_GET['key'];
// проверяем на корректность ключа
if(!preg_match('~^[a-f0-9]{32}+$~', $key)) {
exit(json_encode(['result'=>'error']));
}
// создаем массив для возврата результата
$data = [];
// обращаемся в БД - проверяем наличие записи с переданным ключом
$order = $db->connect()->prepare("SELECT * FROM users WHERE auth_key = :key LIMIT 1");
$order->execute(['key' => $key]);
// если запись есть то работаем
if ($order->rowCount() > 0) {
$orderRaw = $order->fetch();
// создаем новый ключ
$new_key = md5(rand(0, 1000));;
// записываем новый ключ в БД
$update = $db->connect()->prepare("UPDATE users SET auth_key = :new_key WHERE id = :id");
// если обновили то авторизуем
if ($update->execute(['id' => $orderRaw['id'], 'new_key' => $new_key])) {
// ставим куку
setcookie('token', $new_key);
// возвращаем результат
$data['result'] = "success";
} else {
$data['result'] = "error";
}
} else {
$data['result'] = "error";
}
// возвращаем результат
echo json_encode($data);
?>
* * *
Заключение
По итогу у нас получилась вполне рабочая авторизация на сайт через Телеграм без использования официального виджета Telegram. Файлы можно скачать и использовать на свое усмотрение.
Откройте по ссылке или QR бот @iMakeBot, нажмите кнопку Старт/Start.
Следуйте инструкциям бота.
Спасибо. Очень интересно
Солидарен)
Согласен :)
Да, я так и знал что боту придётся самому писать.
Но это вроде не сложно...
Блин, как круто и бысто!
Круто
После редиректа в бота и нажатию кнопки start ничего не происходит. В чём может быть проблема?
API токен ввёл
WebHook настроен?
Доброго дня 😉
А можете поделиться, как у вас комментарии подвязаны к авторизации через телеграм?
Простая проверка авторизации на Laravel
В blade шаблоне через @auth
Благодарю за ответ, покопаю в этом направлении 👍
Здравствуйте, подскажите, пожалуйста, есть ли подобное решение с авторизацией через ТГ бота для Wordpress? Буду благодарен если отпишите немного подробнее!
К сожалению здесь (на этом сайте) это единственный вариант решения.
Круто
Я ещё не проверил работу этого кода, но судя по логике работы с базой, то key - он же token в cookie хранится в одной записи, привязанной к ТГ-id. Что из этого следует: если пользователь авторизовался в одном браузере (например Google Chrome), а потом еще зашел на тот же сайт в другом браузере (например, Fire Fox), то в первом браузере авторизация слетит. Я прав или ошибаюсь?
Да здесь это реализовано через уникальность auth_key
И еще прощу объяснить, почему auth_key, переданный из бота в базу, при проверке и обнаружении записи сразу меняется на new_key, он же прописывается в куку. Почему нельзя оставить тот же самый, вроде бы на безопасность это не влияет?
Если честно то не помню)) видимо какие-то мысли были.
ребята помогите пожалуйста , я нажымаю на кнопку старт и мне не присылаеться уведомление и в базу тоже не записываеться.В чем проблема может быть?
Добрый люди помогите пожалуйста
Прям дай Бог автору статьи здоровья!
После нажатия кнопки старт ничего не происходит. подскажите что делать?
Webhook настроили?
что то пробовал но без результата. подскажите что надо сделать
в браузере все работает. а вот в mini app авторизоваться не могу. подскажите можно как то сделать?
Там используется встроенный браузер, у него область видимости иная, в веб апп при открытии уже приходят данные которыми можно авторизовать пользователя
чтоб один скрипт и в браузере и в мини апп авторизовывал можно как то реализовать?
Можно, это два разных события: Для сайта выводить авторизацию, для веб апп авторизовывать при входе
спасибо. подскажите как аватар пользователя получить при авторизации?
В апи есть метод как-то называется примерно