Настройка авторизации через OAUTH2 сервера https://oauth2.volmed.org.ru: различия между версиями

Материал из Wiki МИАЦ ВО
Перейти к навигации Перейти к поиску
 
(не показано 29 промежуточных версий этого же участника)
Строка 5: Строка 5:
# Добавляем в настроечный файл сервиса следующие строки
# Добавляем в настроечный файл сервиса следующие строки
<pre>
<pre>
$GLOBALS['id_resource'] = 7; // Номер WEB интерфейса
$GLOBALS['id_resource'] = 7; // Номер WEB интерфейса из таблицы oauth_serv.oauth_client.client_id
$PassLength=5; //длина пароля
$GLOBALS['oauth_client_secret'] = 'sh%we&&3#(jHg&jgrbn'; // Код из таблицы oauth_serv.oauth_client.client_secret
$GLOBALS['jwt_key'] = 'sdklfwiomwefwepiojwepjowfmwfmwef'; // Строка с набором символов для шифрования JWT токена
$GLOBALS['jwt_key'] = 'sdklfwiomwefwepiojwepjowfmwfmwef'; // Строка с набором символов для шифрования JWT токена
$GLOBALS['max_time'] = 3600 * 24 * 7; // Время жизни COOKIES, например, 7 суток


$oauth2_url = 'https://oauth2.volmed.org.ru';    // URL сервиса авторизации
$oauth2_url = 'https://oauth2.volmed.org.ru';    // URL сервиса авторизации
Строка 16: Строка 15:
</pre>
</pre>
#Для работы с JWT токеном используем библиотеку https://github.com/firebase/php-jwt. Ее нужно установить в каталог class/jwt
#Для работы с JWT токеном используем библиотеку https://github.com/firebase/php-jwt. Ее нужно установить в каталог class/jwt
==Хранение пользовательских данных в COOKIES==
===Создание таблицы для хранения дополнительных данных===
Самое простое для моего случая - хранить в куках id пользователя и время создания куков. Тк все данные пользователя, я могу вытащить из БД по его id.<br>
Создаем таблицу
Их нужно зашифровать с помощью JWT и хранить полученное значение в куках.
<pre>
<pre>
protected function set_cookie()
CREATE TABLE `user_sessions` (
`session_id` VARCHAR(64) NOT NULL COMMENT 'уникальная строка, хранится в JWT и БД' COLLATE 'utf8mb4_0900_ai_ci',
`user_id` INT NOT NULL COMMENT 'идентификатор пользователя',
`ip` VARCHAR(45) NULL DEFAULT NULL COMMENT 'опционально, помогает выявлять кражу сессии' COLLATE 'utf8mb4_0900_ai_ci',
`user_agent_hash` VARCHAR(64) NULL DEFAULT NULL COMMENT 'опционально, помогает выявлять кражу сессии' COLLATE 'utf8mb4_0900_ai_ci',
`created_at` DATETIME NULL DEFAULT NULL COMMENT 'время создания сессии',
`last_activity` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`session_id`) USING BTREE
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
;
</pre>
 
==Класс для авторизации по Aouth2==
<pre>
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
 
class AuthSait
{
{
/*
    /*
* Устанавливаем COOKIE
    *   Класс по авторизации на сайт
*/
    */
  if (empty($this->login_expires_set)) {
    public array $auth = [];
    $this->login_expires_set = 0;
    public array $picture;
  }
    private string $ua_hash = '';
  if (empty($_COOKIE['jwt']) && !empty($this->auth['id'])) {
    private int $jwt_ttl = 3600; // 1 час время жизни JVT токена
    $payload = [
    private string $jwt_algo = 'HS256';
      'id_user' => !empty($this->auth['id_real']) ? $this->auth['id_real'] : $this->auth['id'],
    private $secure;
      'time_beg' => time(),
    private int $sess_id_live = 4800; // 8 часов Время жизни сессии.
    ];
 
    setcookie('jwt', JWT::encode($payload, $GLOBALS['jwt_key'], 'HS256'), [
 
      "expires" => strtotime($this->login_expires_set),
 
      "path" => "/",
    public function __construct()
      "httponly" => true,
    {
    ]);
        // Проверяем, что есть объект работы с БД
  }
        if (!empty($GLOBALS['db']) && is_object($GLOBALS['db'])) {
        } else {
            return 'Нет подключения к БД сайта';
        }
 
        // Проверяем, что есть объект работы с БД пользователей
        if (!empty($GLOBALS['lpu_user']) && is_object($GLOBALS['lpu_user'])) {
        } else {
            return 'Нет подключения к БД пользователей';
        }
        $this->secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
        $this->ua_hash = hash('sha256', $_SERVER['HTTP_USER_AGENT']);  // Создаем hash от User_Agent
        // Переменная с путями до картинок пользователей по умолчанию, в зависимости от пола
        $this->picture = [
            0 => '/users/pictures/user-m.png',
            1 => '/users/pictures/user-m.png',
            2 => '/users/pictures/user-w.png',
        ];
    }
}
}
</pre>
</pre>
И проверка кукис
===Основной метод авторизации===
<pre>
public function auth()
    {
        // printr($_COOKIE);
        session_start();
        $page = $_GET['page'] ?? '';
 
        // Logout пользователя
        if ($page === 'logout') {
            $this->auth['logout'] = $this->logout(1);
            return $this->auth;
        }
 
        // 1️⃣ Проверяем JWT cookie
        if (!empty($_COOKIE['jwt'])) {
            $this->test_cookie();
            return $this->auth;
        }
 
        // 2️⃣ Если возвращаемся с сервера OAuth2 с code
        if (!empty($_GET['code'])) {
            $return = $this->test_code();
            header("Location: $return");
            exit;
        }
 
        // Если это первый вход, то переходим на страницу Авторизации
        $url = $this->oauth2_login();
        header("Location: $url");
        exit;
    }
</pre>
===Проверка пользователя===
<pre>
    private function test_cookie()
    {
        // Проверка авторизации и создание переменной авторизации $auth
        try {
            $this->load_cookie();
            $this->load_data_user();
            $this->role_list();
        } catch (\Exception $e) {
            // JWT невалиден → удаляем cookie
            $this->logout(1);
        }
    }
</pre>
 
===Метод выхода из системы===
<pre>
<pre>
protected function load_cookie()
public function logout($num)
{
    {
  /*
        /*
   * Метод по проверке COOKIE и загрузки их в переменные для проверки авторизации
        *   Выход из системы
  */
        */
  if (!empty($_COOKIE['jwt'])) {
        // 2️⃣ Удаляем сессию на сервере (если есть)
      $decoded_obj = JWT::decode($_COOKIE['jwt'], new Key($GLOBALS['jwt_key'], 'HS256'));
        if (!empty($_COOKIE['jwt'])) {
      $decoded = get_object_vars($decoded_obj);
            try {
      $this->id_user = $decoded['id_user'];
                $payload = \Firebase\JWT\JWT::decode($_COOKIE['jwt'], new \Firebase\JWT\Key($GLOBALS['jwt_key'], $this->jwt_algo));
      $this->time_beg = $decoded['time_beg'];
                if (!empty($payload->session_id)) {
}
                    $GLOBALS['db']->mysqli_qw(
}
                        'DELETE FROM user_sessions WHERE session_id = ?',
                        $payload->session_id
                    );
                }
            } catch (\Exception $e) {
                // JWT невалиден → ничего не делаем
            }
        }
        if (!empty($this->auth['error']) && !empty($this->auth['text_error'])) {
            $this->auth['error'] .= $this->auth['text_error'];
        }
        // printr($this->auth);
        foreach ($_COOKIE as $key => $val) {
            if (!empty($_COOKIE[$key])) {
                unset($_COOKIE[$key]);
                setcookie($key, "", time() - 3600, '/');
            }
        }
        $this->auth = [];
        $this->oauth2_logout();
        $logout = 1;
        return $logout;
    }
</pre>
 
===Метод выхода из OAUTH2 сервера===
<pre>
    protected function oauth2_logout()
    {
        /*
        * Выход из системы через oauth2
        */
        // echo "oauth2_logout<br>";
        $url_arr = [
            'logout' => 1,
            'redirect_uri' => (empty($_SERVER['HTTPS']) ? 'http' : 'https') . "://$_SERVER[HTTP_HOST]",
        ];
        $url = $GLOBALS['oauth2_url'] . '?' . http_build_query($url_arr);
        header('Location: ' . $url);
        exit();
    }
</pre>
 
===Проверка авторизации и создания $auth===
<pre>
    private function test_cookie()
    {
        // Проверка авторизации и создание переменной авторизации $auth
        try {
            $this->load_cookie();
            $this->load_data_user();
            $this->role_list();
        } catch (\Exception $e) {
            // JWT невалиден → удаляем cookie
            $this->logout(1);
        }
    }
</pre>
===Метод по проверке COOKIE и загрузки их в переменные для проверки авторизации===
<pre>
    protected function load_cookie()
    {
        /*
        * Метод по проверке COOKIE и загрузки их в переменные для проверки авторизации
        */
 
        $payload = \Firebase\JWT\JWT::decode(
            $_COOKIE['jwt'],
            new \Firebase\JWT\Key($GLOBALS['jwt_key'], $this->jwt_algo)
        );
 
        if (!empty($payload->session_id) && !empty($payload->id_user)) {
            // Проверяем сессию на сервере
            $res = $GLOBALS['db']->mysqli_qw(
                'SELECT session_id, user_id
                    FROM user_sessions
                    WHERE session_id = ? AND user_id = ?
                    AND user_agent_hash=? AND last_activity > DATE_SUB(NOW(), INTERVAL ? MINUTE)',
                $payload->session_id,
                $payload->id_user,
                $this->ua_hash,
                $this->sess_id_live
            );
            $session = mysqli_fetch_assoc($res);
            if ($session) {
                // обновляем last_activity
                $GLOBALS['db']->mysqli_qw(
                    'UPDATE user_sessions SET last_activity = NOW() WHERE session_id = ?',
                    $payload->session_id
                );
 
                // обновляем JWT если прошло больше половины жизни
                if ($payload->iat < time() - ($this->jwt_ttl / 2)) {
                    $new_payload = [
                        'id_user' => $payload->id_user,
                        'session_id' => $payload->session_id,
                        'iat' => time(),
                        'exp' => time() + $this->jwt_ttl
                    ];
                    $this->set_cookie($new_payload, $this->jwt_ttl);
                }
 
                // возвращаем авторизацию
                $this->auth = [
                    'id' => $payload->id_user,
                ];
            } else {
                // Сессия протухла - Пользователь не работал более $sess_id_live минут
                $this->logout(1);
                exit;
            }
        }
    }
</pre>
</pre>


==Формирование строки авторизации==
===Если возвращаемся с сервера OAuth2 с code===
Для перехода на страницу сервера авторизации, генерим следующий код
<pre>
<pre>
if (!empty($_GET['code'])) { // Если это уже ответ oauth2 сервера (Есть параметр state)
    protected function test_code()
  if ($_GET['state'] == $_SESSION['state']) { // Если state совпадает с сохраненным в Сессии
    {
      $oauth2_access = $this->oauth2_post();   // Посылаем уже с помощью POST запрос
        if (empty($_GET['state']) || $_GET['state'] !== ($_SESSION['state'] ?? '')) {
      $this->auth = $this->auth($oauth2_access); // И после возврата id пользователя, обрабатываем его вход
            die('Ошибка авторизации: invalid state');
  } else die('Ошибка авторизации (Invalid state), обратитесь к администраотру сайта'); // если state не совпал, то ошибка
        }
} else { // Ето первый цикл обращения к oauth2 серверу
 
  $state = bin2hex(random_bytes(16));   // Генерим код state - 20 символов случайного кода
        $oauth2_access = $this->oauth2_post($_GET['code']); // получает access_token и id_user
  $_SESSION['state'] = $state;   // Сохраняем в сессии
 
  $url_data = [
        if (empty($oauth2_access['access_token']) || empty($oauth2_access['id_user'])) {
    'response_type' => 'code',
            die('OAuth авторизация не удалась');
    'client_id' => $GLOBALS['id_resource'],   // ID сервиса в МИАЦ
        }
    'state' => $state,                        // Случайный код, который сервис должен вернуть
 
    'redirect_uri' => $GLOBALS['redirect_uri'] // Адрес возврата дб вв виде https://sait.volmed.org.ru
        // Проверяем, есть ли уже активная сессия для этого пользователя и UA
  ];
        $existing = $GLOBALS['db']->mysqli_qw(
  $url = $GLOBALS['oauth2_url'] . '?' . http_build_query($url_data);   // URL для отправки на сервер авторизации
            'SELECT session_id FROM user_sessions
  header('Location:' . $url); //
            WHERE user_id=?
  exit;
                AND user_agent_hash=?
}
                AND last_activity > DATE_SUB(NOW(), INTERVAL ? MINUTE)',
            $oauth2_access['id_user'],
            $this->ua_hash,
            $this->sess_id_live
        );
        $row = mysqli_fetch_assoc($existing);
 
        if ($row) {
            $session_id = $row['session_id'];
        } else {
            // создаём новую сессию
            $session_id = bin2hex(random_bytes(16));
            $GLOBALS['db']->mysqli_qw(
                'INSERT INTO user_sessions (session_id, user_id, user_agent_hash, created_at, last_activity)
                VALUES (?, ?, ?, NOW(), NOW())',
                $session_id,
                $oauth2_access['id_user'],
                $this->ua_hash
            );
        }
 
        // создаём JWT
        $new_payload = [
            'id_user'   => $oauth2_access['id_user'],
            'session_id' => $session_id,
            'iat'       => time(),
            'exp'       => time() + $this->jwt_ttl
        ];
        $this->set_cookie($new_payload);
        // редиректим на исходную страницу
        $url = $_SESSION['oauth_return_to'] ?? '/';
        unset($_SESSION['state'], $_SESSION['oauth_return_to']);
        return $url;
    }
 
</pre>
</pre>
После перехода на сервер авторизации, вводим Логин и Пароль. И если он правильный, то сервер возвращает на страницу редиректа


==Формирование POST запроса авторизации==
===Если это начало авторизации===
<pre>
<pre>
private function oauth2_post()
    private function oauth2_login()
{
    {
  $post_data = [
        // 3️⃣ Начинаем OAuth, если нет cookie и нет code
  "user_id" => $_GET['user_id'],           // ID пользователя, возвращенного oauth2 серверром
        $state = bin2hex(random_bytes(16));
  "code" => $_GET['code'],
        $_SESSION['state'] = $state;
  'grant_type' => 'authorization_code',   // Тип гранта
        $_SESSION['oauth_return_to'] = $_SERVER['REQUEST_URI'];
  'client_id' => $GLOBALS['id_resource'],   // ID сервиса
 
  'client_secret' => 'wetreyrtbv:KJLKO',  // это код из БД OUTH2 сервера таблица oauth_clients.client_secret для $GLOBALS['id_resource']
        $url_data = [
  'redirect_uri' => $GLOBALS['redirect_uri'],
            'response_type' => 'code',
  // 'scope' => 'read write',
            'client_id'    => $GLOBALS['id_resource'],
];
            'state'        => $state,
  $data_string = json_encode($post_data);
            'redirect_uri'  => $GLOBALS['redirect_uri']
  $ch = curl_init();
        ];
  curl_setopt($ch, CURLOPT_URL, $GLOBALS['oauth2_url'] . '/token');
        $url = $GLOBALS['oauth2_url'] . '?' . http_build_query($url_data);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        return $url;
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
    }
  curl_setopt($ch, CURLOPT_POST, true);
</pre>
  curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
===Если получили code с Aouth2 сервера и запрашиваем данные авторизации===
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
<pre>
'Content-Type: application/json',
    private function oauth2_post()
'Content-Length: ' . strlen($data_string),
    {
));
        $post_data = [
  $oauth2_access = [];
            "user_id" => $_GET['user_id'],
  $output = curl_exec($ch);
            "code" => $_GET['code'],
  // execute the request
            'grant_type' => 'authorization_code',
  if ($output === false) echo 'Ошибка curl: ' . curl_error($ch);
            'client_id' => $GLOBALS['id_resource'],     // Код должен совпадать с oauth2_db.oauth_client.client_id
  else {
            'client_secret' => $GLOBALS['oauth_client_secret'],  // Код должен совпадать с oauth2_db.oauth_client.client_secret
    $oauth2_access = json_decode($output, true);
            'redirect_uri' => $GLOBALS['redirect_uri'],
    if (!empty($oauth2_access['error'])) {
            // 'scope' => 'read write',
        printr($oauth2_access);
        ];
        exit();
        // printr($post_data);
    }
        $data_string = json_encode($post_data);
    $oauth2_access['id_user'] = $_GET['user_id'];
        $ch = curl_init();
  }
        curl_setopt($ch, CURLOPT_URL, $GLOBALS['oauth2_url'] . '/token');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen($data_string),
        ));
        $oauth2_access = [];
 
 
        $output = curl_exec($ch);
        // echo "output=$output<br>";
        // execute the request
        if ($output === false) echo 'Ошибка curl: ' . curl_error($ch);
        else {
            $oauth2_access = json_decode($output, true);
            // printr($oauth2_access);
            if (!empty($oauth2_access['error'])) {
                printr($oauth2_access);
                exit();
            }
            $oauth2_access['id_user'] = $_GET['user_id'];
        }
         curl_close($ch);
         curl_close($ch);
        // exit();
         return $oauth2_access;
         return $oauth2_access;
     }
     }
</pre>
</pre>
===Метод по установке COOKIE===
<pre>
    protected function set_cookie($new_payload)
    {
        /*
        * Устанавливаем COOKIE
        */
        setcookie(
            'jwt',
            \Firebase\JWT\JWT::encode($new_payload, $GLOBALS['jwt_key'], $this->jwt_algo),
            [
                'expires' => time() + $this->jwt_ttl,
                'path' => '/',
                'secure' => $this->secure,
                'httponly' => true,
                'samesite' => 'Lax'
            ]
        );
    }
</pre>
===Осталные методы===
Методы load_data_user() и role_list() не связаны с OAUTH2 авторизацией.
#load_data_user() - вытаскивает из базы данные оператора
#role_list() - вытаскивает роли этого оператора.
==Запрос пользовательских данных==
==Запрос пользовательских данных==
Так же пользовательские данные можно запросить методом с сервера OAUTH2.<br>
Но надо учесть проблему: Тк access_token через короткое время протухает, то нужно эти данные сразу куда то сохранять. И если есть возможность, как у меня вытащить эти данные прямо из БД, то лучше сделать так.
<pre>
<pre>
public function take_userinfo()
public function take_userinfo()

Текущая версия от 14:52, 11 марта 2026

Подготовительные операции

  1. Идем в БД oauth_db на сервере 172.16.130.31 и добавляем в таблицу oauth_clients
    1. client_id - Номер WEB интерфейса, который вы собираетесь подключить
    2. client_secret - Набор любых символов для шифрования
  2. Добавляем в настроечный файл сервиса следующие строки
$GLOBALS['id_resource'] = 7; // Номер WEB интерфейса из таблицы oauth_serv.oauth_client.client_id
$GLOBALS['oauth_client_secret'] = 'sh%we&&3#(jHg&jgrbn'; // Код из таблицы oauth_serv.oauth_client.client_secret
$GLOBALS['jwt_key'] = 'sdklfwiomwefwepiojwepjowfmwfmwef'; // Строка с набором символов для шифрования JWT токена

$oauth2_url = 'https://oauth2.volmed.org.ru';    // URL сервиса авторизации
$GLOBALS['oauth2_url'] = $oauth2_url;
$redirect_uri =  $http."://$_SERVER[HTTP_HOST]";
$GLOBALS['redirect_uri'] = $redirect_uri;
  1. Для работы с JWT токеном используем библиотеку https://github.com/firebase/php-jwt. Ее нужно установить в каталог class/jwt

Создание таблицы для хранения дополнительных данных

Создаем таблицу

CREATE TABLE `user_sessions` (
	`session_id` VARCHAR(64) NOT NULL COMMENT 'уникальная строка, хранится в JWT и БД' COLLATE 'utf8mb4_0900_ai_ci',
	`user_id` INT NOT NULL COMMENT 'идентификатор пользователя',
	`ip` VARCHAR(45) NULL DEFAULT NULL COMMENT 'опционально, помогает выявлять кражу сессии' COLLATE 'utf8mb4_0900_ai_ci',
	`user_agent_hash` VARCHAR(64) NULL DEFAULT NULL COMMENT 'опционально, помогает выявлять кражу сессии' COLLATE 'utf8mb4_0900_ai_ci',
	`created_at` DATETIME NULL DEFAULT NULL COMMENT 'время создания сессии',
	`last_activity` DATETIME NULL DEFAULT NULL,
	PRIMARY KEY (`session_id`) USING BTREE
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
;

Класс для авторизации по Aouth2

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class AuthSait
{
    /*
     *   Класс по авторизации на сайт
     */
    public array $auth = [];
    public array $picture;
    private string $ua_hash = '';
    private int $jwt_ttl = 3600; // 1 час время жизни JVT токена
    private string $jwt_algo = 'HS256';
    private $secure;
    private int $sess_id_live = 4800; // 8 часов Время жизни сессии.



    public function __construct()
    {
        // Проверяем, что есть объект работы с БД
        if (!empty($GLOBALS['db']) && is_object($GLOBALS['db'])) {
        } else {
            return 'Нет подключения к БД сайта';
        }

        // Проверяем, что есть объект работы с БД пользователей
        if (!empty($GLOBALS['lpu_user']) && is_object($GLOBALS['lpu_user'])) {
        } else {
            return 'Нет подключения к БД пользователей';
        }
        $this->secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
        $this->ua_hash = hash('sha256', $_SERVER['HTTP_USER_AGENT']);   // Создаем hash от User_Agent
        // Переменная с путями до картинок пользователей по умолчанию, в зависимости от пола
        $this->picture = [
            0 => '/users/pictures/user-m.png',
            1 => '/users/pictures/user-m.png',
            2 => '/users/pictures/user-w.png',
        ];
    }
}

Основной метод авторизации

public function auth()
    {
        // printr($_COOKIE);
        session_start();
        $page = $_GET['page'] ?? '';

        // Logout пользователя
        if ($page === 'logout') {
            $this->auth['logout'] = $this->logout(1);
            return $this->auth;
        }

        // 1️⃣ Проверяем JWT cookie
        if (!empty($_COOKIE['jwt'])) {
            $this->test_cookie();
            return $this->auth;
        }

        // 2️⃣ Если возвращаемся с сервера OAuth2 с code
        if (!empty($_GET['code'])) {
            $return = $this->test_code();
            header("Location: $return");
            exit;
        }

        // Если это первый вход, то переходим на страницу Авторизации
        $url = $this->oauth2_login();
        header("Location: $url");
        exit;
    }

Проверка пользователя

     private function test_cookie()
    {
        // Проверка авторизации и создание переменной авторизации $auth
        try {
            $this->load_cookie();
            $this->load_data_user();
            $this->role_list();
        } catch (\Exception $e) {
            // JWT невалиден → удаляем cookie
            $this->logout(1);
        }
    }

Метод выхода из системы

public function logout($num)
    {
        /*
         *   Выход из системы
         */
        // 2️⃣ Удаляем сессию на сервере (если есть)
        if (!empty($_COOKIE['jwt'])) {
            try {
                $payload = \Firebase\JWT\JWT::decode($_COOKIE['jwt'], new \Firebase\JWT\Key($GLOBALS['jwt_key'], $this->jwt_algo));
                if (!empty($payload->session_id)) {
                    $GLOBALS['db']->mysqli_qw(
                        'DELETE FROM user_sessions WHERE session_id = ?',
                        $payload->session_id
                    );
                }
            } catch (\Exception $e) {
                // JWT невалиден → ничего не делаем
            }
        }
        if (!empty($this->auth['error']) && !empty($this->auth['text_error'])) {
            $this->auth['error'] .= $this->auth['text_error'];
        }
        // printr($this->auth);
        foreach ($_COOKIE as $key => $val) {
            if (!empty($_COOKIE[$key])) {
                unset($_COOKIE[$key]);
                setcookie($key, "", time() - 3600, '/');
            }
        }
        $this->auth = [];
        $this->oauth2_logout();
        $logout = 1;
        return $logout;
    }

Метод выхода из OAUTH2 сервера

    protected function oauth2_logout()
    {
        /*
         * Выход из системы через oauth2
         */
        // echo "oauth2_logout<br>";
        $url_arr = [
            'logout' => 1,
            'redirect_uri' => (empty($_SERVER['HTTPS']) ? 'http' : 'https') . "://$_SERVER[HTTP_HOST]",
        ];
        $url = $GLOBALS['oauth2_url'] . '?' . http_build_query($url_arr);
        header('Location: ' . $url);
        exit();
    }

Проверка авторизации и создания $auth

    private function test_cookie()
    {
        // Проверка авторизации и создание переменной авторизации $auth
        try {
            $this->load_cookie();
            $this->load_data_user();
            $this->role_list();
        } catch (\Exception $e) {
            // JWT невалиден → удаляем cookie
            $this->logout(1);
        }
    }
     protected function load_cookie()
    {
        /*
         * Метод по проверке COOKIE и загрузки их в переменные для проверки авторизации
         */

        $payload = \Firebase\JWT\JWT::decode(
            $_COOKIE['jwt'],
            new \Firebase\JWT\Key($GLOBALS['jwt_key'], $this->jwt_algo)
        );

        if (!empty($payload->session_id) && !empty($payload->id_user)) {
            // Проверяем сессию на сервере
            $res = $GLOBALS['db']->mysqli_qw(
                'SELECT session_id, user_id 
                     FROM user_sessions
                     WHERE session_id = ? AND user_id = ? 
                     AND user_agent_hash=? AND last_activity > DATE_SUB(NOW(), INTERVAL ? MINUTE)',
                $payload->session_id,
                $payload->id_user,
                $this->ua_hash,
                $this->sess_id_live
            );
            $session = mysqli_fetch_assoc($res);
            if ($session) {
                // обновляем last_activity
                $GLOBALS['db']->mysqli_qw(
                    'UPDATE user_sessions SET last_activity = NOW() WHERE session_id = ?',
                    $payload->session_id
                );

                // обновляем JWT если прошло больше половины жизни
                if ($payload->iat < time() - ($this->jwt_ttl / 2)) {
                    $new_payload = [
                        'id_user' => $payload->id_user,
                        'session_id' => $payload->session_id,
                        'iat' => time(),
                        'exp' => time() + $this->jwt_ttl
                    ];
                    $this->set_cookie($new_payload, $this->jwt_ttl);
                }

                // возвращаем авторизацию
                $this->auth = [
                    'id' => $payload->id_user,
                ];
            } else {
                // Сессия протухла - Пользователь не работал более $sess_id_live минут
                $this->logout(1);
                exit;
            }
        }
    }

Если возвращаемся с сервера OAuth2 с code

    protected function test_code()
    {
        if (empty($_GET['state']) || $_GET['state'] !== ($_SESSION['state'] ?? '')) {
            die('Ошибка авторизации: invalid state');
        }

        $oauth2_access = $this->oauth2_post($_GET['code']); // получает access_token и id_user

        if (empty($oauth2_access['access_token']) || empty($oauth2_access['id_user'])) {
            die('OAuth авторизация не удалась');
        }

        // Проверяем, есть ли уже активная сессия для этого пользователя и UA
        $existing = $GLOBALS['db']->mysqli_qw(
            'SELECT session_id FROM user_sessions
             WHERE user_id=? 
                AND user_agent_hash=? 
                AND last_activity > DATE_SUB(NOW(), INTERVAL ? MINUTE)',
            $oauth2_access['id_user'],
            $this->ua_hash,
            $this->sess_id_live
        );
        $row = mysqli_fetch_assoc($existing);

        if ($row) {
            $session_id = $row['session_id'];
        } else {
            // создаём новую сессию
            $session_id = bin2hex(random_bytes(16));
            $GLOBALS['db']->mysqli_qw(
                'INSERT INTO user_sessions (session_id, user_id, user_agent_hash, created_at, last_activity) 
                 VALUES (?, ?, ?, NOW(), NOW())',
                $session_id,
                $oauth2_access['id_user'],
                $this->ua_hash
            );
        }

        // создаём JWT
        $new_payload = [
            'id_user'    => $oauth2_access['id_user'],
            'session_id' => $session_id,
            'iat'        => time(),
            'exp'        => time() + $this->jwt_ttl
        ];
        $this->set_cookie($new_payload);
        // редиректим на исходную страницу
        $url = $_SESSION['oauth_return_to'] ?? '/';
        unset($_SESSION['state'], $_SESSION['oauth_return_to']);
        return $url;
    }

Если это начало авторизации

     private function oauth2_login()
    {
        // 3️⃣ Начинаем OAuth, если нет cookie и нет code
        $state = bin2hex(random_bytes(16));
        $_SESSION['state'] = $state;
        $_SESSION['oauth_return_to'] = $_SERVER['REQUEST_URI'];

        $url_data = [
            'response_type' => 'code',
            'client_id'     => $GLOBALS['id_resource'],
            'state'         => $state,
            'redirect_uri'  => $GLOBALS['redirect_uri']
        ];
        $url = $GLOBALS['oauth2_url'] . '?' . http_build_query($url_data);
        return $url;
    }

Если получили code с Aouth2 сервера и запрашиваем данные авторизации

    private function oauth2_post()
    {
        $post_data = [
            "user_id" => $_GET['user_id'],
            "code" => $_GET['code'],
            'grant_type' => 'authorization_code',
            'client_id' => $GLOBALS['id_resource'],     // Код должен совпадать с oauth2_db.oauth_client.client_id
            'client_secret' => $GLOBALS['oauth_client_secret'],   // Код должен совпадать с oauth2_db.oauth_client.client_secret
            'redirect_uri' => $GLOBALS['redirect_uri'],
            // 'scope' => 'read write',
        ];
        // printr($post_data);
        $data_string = json_encode($post_data);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $GLOBALS['oauth2_url'] . '/token');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen($data_string),
        ));
        $oauth2_access = [];


        $output = curl_exec($ch);
        // echo "output=$output<br>";
        // execute the request
        if ($output === false) echo 'Ошибка curl: ' . curl_error($ch);
        else {
            $oauth2_access = json_decode($output, true);
            // printr($oauth2_access);
            if (!empty($oauth2_access['error'])) {
                printr($oauth2_access);
                exit();
            }
            $oauth2_access['id_user'] = $_GET['user_id'];
        }
        curl_close($ch);
        // exit();
        return $oauth2_access;
    }
     protected function set_cookie($new_payload)
    {
        /*
         * Устанавливаем COOKIE
         */

        setcookie(
            'jwt',
            \Firebase\JWT\JWT::encode($new_payload, $GLOBALS['jwt_key'], $this->jwt_algo),
            [
                'expires' => time() + $this->jwt_ttl,
                'path' => '/',
                'secure' => $this->secure,
                'httponly' => true,
                'samesite' => 'Lax'
            ]
        );
    }

Осталные методы

Методы load_data_user() и role_list() не связаны с OAUTH2 авторизацией.

  1. load_data_user() - вытаскивает из базы данные оператора
  2. role_list() - вытаскивает роли этого оператора.

Запрос пользовательских данных

Так же пользовательские данные можно запросить методом с сервера OAUTH2.
Но надо учесть проблему: Тк access_token через короткое время протухает, то нужно эти данные сразу куда то сохранять. И если есть возможность, как у меня вытащить эти данные прямо из БД, то лучше сделать так.

public function take_userinfo()
{
   $accessToken = $this->access_token; // <-- access token
   $userInfoUrl = $GLOBALS['oauth2_url'] . '/userinfo'; // <-- endpoint

   $ch = curl_init();

   curl_setopt($ch, CURLOPT_URL, $userInfoUrl);
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
   curl_setopt($ch, CURLOPT_HTTPHEADER, [
     'Authorization: Bearer ' . $accessToken,
     'Accept: application/json'
   ]);

   $response = curl_exec($ch);
   $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

   if (curl_errno($ch)) {
      echo 'Ошибка cURL: ' . curl_error($ch);
   } elseif ($httpCode !== 200) {
      echo "Ошибка HTTP $httpCode: $response";
   } else {
     $userData = json_decode($response, true);
     //printr($userData);
   }

  curl_close($ch);
  return $userData;
}