Авторизация на сайте очень важна, с ее помощью можно разделять функционал или контент сайта для посетителей в зависимости от их ролей. На этом сайте это возможность оставлять комментарии под статьями - доступно только для авторизованных пользователей.
У Телеграм есть официальный виджет для авторизации, по мне так он очень удобный, но у него есть ограничения - на момент написания статьи он недоступен без обхода блокировки Телеграм. Настраивается он очень просто и работает эффективно. Но речь сейчас не о нем.
Сегодня рассмотрим простой алгоритм для регистрации и авторизации пользователей с помощью Телеграм. Взаимодействие сайта и Телеграм будет происходить через бот.
Весь процесс очень прост. Создадим ссылку по нажатию на которую будут запущены два действия:
- в бот мы передадим значение в параметре 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, он же прописывается в куку. Почему нельзя оставить тот же самый, вроде бы на безопасность это не влияет?
Если честно то не помню)) видимо какие-то мысли были.
ребята помогите пожалуйста , я нажымаю на кнопку старт и мне не присылаеться уведомление и в базу тоже не записываеться.В чем проблема может быть?
Добрый люди помогите пожалуйста
Прям дай Бог автору статьи здоровья!