OAuth 2.0 в amoCRM REST API на PHP простым языком: получение, хранение и обновление токенов

Когда впервые пишешь собственную интеграцию с amoCRM через REST API, сами запросы к сделкам, контактам или компаниям обычно оказываются не самой сложной частью.
Основной вопрос возникает раньше:
как один раз авторизовать интеграцию, а затем поддерживать доступ к amoCRM без постоянного ручного получения ключей?
1-2 года назад я реализовывал такую интеграцию на PHP. Токены хранили в MySQL, а работу с OAuth разбили на несколько отдельных файлов.
Сейчас решил восстановить общую архитектуру этой реализации.
Если убрать детали, OAuth-интеграция выглядит довольно просто:
Authorization Code → Access Token + Refresh Token → сохранение в БД → запросы к REST API → обновление токенов → повторное сохранение в БД.
Код авторизации берется в AmoCRM:
В общем все эти значения помещаются в data и потом отсылаются через curl:
subdomain = 'https://xxx.amocrm.ru/'; //Поддомен нужного аккаунта
$link = 'https:/xxx.amocrm.ru/oauth2/access_token'; //Формируем URL для запроса
$data = [
'client_id' => '6a2d4741-xxxx-xxxx-xxxx-92277723dcea', // ID интеграции
'client_secret' => 'PZp1wm7VpAUBvpTzADj4CRHjmuvRR4PwVS7EqVxxxNvpMglIWK8uy6KWSgxxvdjY', //секретный ключ
'grant_type' => 'authorization_code',
'code' => 'def50200exxxx8a701f977d7e9151d7b02da0000001d28e4a100000004c2e7350000xxb0f8fcd6b982615f86242baf3eea6ee5cbe282f19e8fd3d973ab858afa6691701f83c61f3e02af1029334772747c6242dc50e6e0bfe31e45666a8be5d93f0d5242300e2006c4bfe8cd4fa7c661f1ed5a4f290b8b283b44c3c22e247aa6abd587d7ce666ee6956ec8cb281ef6813ba6494f4eba97a8b20486cd3dff0ae6d486a195ae50755e13cf00f9608d3e699c5d7baefdbfc22da8ee21d1af69e6183abba81ba2a174341ce1e62c2dd7bbf956aee3624265d5bd9c098ffc24d9071d63b7c6e6db1ba5d55a853483ac77e42d6ed8ca32be8ccb2e7ee488716da9f8c21be27489f7d6ceadebe8d120b22233e053c1659fd872b4eefc756cd4770d23e9a48707d492051e417995193f51e6322c339d669e3298aee671ef2caa8c61b662d34685b7f8186be04a5f495a2123bacebfa7f2f8a9901260f8ba4757ab252849af7920445a8fe8397c78bbb94900e0f7f8984f18efb235bb378d5413c26100338c91d1d21dd15e4641ee85dc2b3187f6bb20a34fb94993302b18afe4ea577fdc9f8bcaf4a550eb7fdc41c206b318f7f210f696787fe0cbfe50843ce86849cf5dc6b834cb752cbe6e86df89b3402b7d3f4c28c3588f5908afac5452ee76a1b1b768aa25a5769cf272fa993101f07de20c28',
'redirect_uri' => 'https://xxx.ru/',
];
$curl = curl_init();
curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0');
curl_setopt($curl,CURLOPT_URL, $link);
curl_setopt($curl,CURLOPT_HTTPHEADER,['Content-Type:application/json']);
curl_setopt($curl,CURLOPT_HEADER, false);
curl_setopt($curl,CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2);
$out = curl_exec($curl);
var_dump($out);
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$code = (int)$code;
$errors = [
400 => 'Bad request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Not found',
500 => 'Internal server error',
502 => 'Bad gateway',
503 => 'Service unavailable',
];
try
{
if ($code < 200 || $code > 204) {
throw new Exception(isset($errors[$code]) ? $errors[$code] : 'Undefined error', $code);
}
}
catch(\Exception $e)
{
die('Ошибка: ' . $e->getMessage() . PHP_EOL . 'Код ошибки: ' . $e->getCode());
}
Схема такая:
Authorization Code
↓
POST /oauth2/access_token
↓
Access Token
Refresh Token
По итогу получает в обратку Access Token и Refresh Token и их записываем в БД себе, мы в MySQL записывали.
function save_token ($access_token, $refresh_token) {
$config = [
'host' => '127.0.0.1',
'db_name' => 'xxx',
'username' => 'xxxx',
'password' => "xxx",
'port' => 3308,
];
$d = "mysql:host={$config ['host']};port={$config["port"]};dbname={$config ["db_name"]};charset=utf8";
try {
$pdo = new PDO($d, $config ["username"], $config ["password"]);
echo('<br> Подключение успешно к БД <br>');
} catch (PDOException $e) {
echo "Connect failed: ". $e -> getMessage();
}
$insert = "INSERT INTO `AmoAuth`(`access_token`, `refresh_token`) VALUES ('$access_token','$refresh_token')";
try {
$pdo -> query($insert);
echo("Токены успешно сохранены");
} catch (\Throwable $th) {
echo("Ошибка записи в БД $th");
}
}Все, $access_token есть. Зачем? Что бы потом его использовать в запросах к AmoCRM, например:
GET /api/v4/leads
GET /api/v4/contacts
POST /api/v4/leads
Потому что если access_token не валиден, то запросы не пройдут, он нужен далее для работы.
Access token amoCRM имеет ограниченный срок действия, поэтому его надо постоянно обновлять и для этого нужен Refresh Token.
Логика получается следующая:
Есть Access Token
↓
Токен ещё действителен?
/ \
да нет
↓ ↓
REST API Refresh Token
↓
новый Access Token
новый Refresh Token
↓
База
Если Access Token истек, то надо переобновить.
А что бы понять - надо проверить на валидность:
public function access_token_valid ($access_token) { //проверка access_token на валидность
$this -> errorslogservice -> errors_log ("access_token_valid запускается");
$url = 'https://xxx.amocrm.ru/api/v4/account';
$headers = [
'Authorization: Bearer ' . $access_token
];
$curl = curl_init();
curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl,CURLOPT_ENCODING, '');
curl_setopt($curl,CURLOPT_MAXREDIRS, 10);
curl_setopt($curl,CURLOPT_TIMEOUT, 0);
curl_setopt($curl,CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0');
curl_setopt($curl,CURLOPT_URL, $url);
curl_setopt($curl,CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl,CURLOPT_HEADER, false);
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2);
$out = curl_exec($curl);
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this -> errorslogservice -> errors_log ($out);
$res = json_decode($out);
return isset($res -> status);
}Если ответ приходит что не валидный - запрашиваем снова:
public function access_token_valid ($access_token) { //проверка access_token на валидность
$this -> errorslogservice -> errors_log ("access_token_valid запускается");
$url = 'https://xxx.amocrm.ru/api/v4/account';
$headers = [
'Authorization: Bearer ' . $access_token
];
$curl = curl_init();
curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl,CURLOPT_ENCODING, '');
curl_setopt($curl,CURLOPT_MAXREDIRS, 10);
curl_setopt($curl,CURLOPT_TIMEOUT, 0);
curl_setopt($curl,CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0');
curl_setopt($curl,CURLOPT_URL, $url);
curl_setopt($curl,CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl,CURLOPT_HEADER, false);
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2);
$out = curl_exec($curl);
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this -> errorslogservice -> errors_log ($out);
$res = json_decode($out);
return isset($res -> status);
}Как взять токен с БД писать не буду и так понятно, обычный SQL запрос внутри PHP с pdo по типу такого $query = $this -> pdo -> query($request).
Если же надо запросить новый Access Token, то формирует код где дата Refresh Token:
private function refresh_amo_token ($refresh_token) { //получение нового access_token и refresh_token через refresh_token
$this -> errorslogservice -> errors_log ("refresh_amo_token запускается");
$url = 'https://xxx.amocrm.ru/oauth2/access_token';
$data = [
'client_id' => 'xxxxxxxx-84f7-4bc9-911c-92277723dcea',
'client_secret' => 'PZp1xxxxxxxxxxxxxxRR4PwVS7EqV7G1NvpMglIWK8uy6KWSg0AvdjY',
'grant_type' => 'refresh_token',
'refresh_token' => $refresh_token,
'redirect_uri' => 'https://xxx.ru/',
];
$headers = ['Content-Type:application/json'];
$curl = curl_init();
curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl,CURLOPT_ENCODING, '');
curl_setopt($curl,CURLOPT_MAXREDIRS, 10);
curl_setopt($curl,CURLOPT_TIMEOUT, 0);
curl_setopt($curl,CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0');
curl_setopt($curl,CURLOPT_URL, $url);
curl_setopt($curl,CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl,CURLOPT_HEADER, false);
curl_setopt($curl,CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2);
$out = curl_exec($curl);
$this -> errorslogservice -> errors_log ($out);
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($code >= 400) {
throw new Exception ("ошибка API AmoCRM $out");
}
$tokens = json_decode($out);
if (!isset ($tokens -> access_token)) {
throw new Exception ("Ошибка получения токена".json_encode($tokens));
}
return $tokens;
}В ответ получаем новую пару Access Token и Refresh Token и перезаписываем их в БД себе.
И так по кругу, Access Token - не валидный - запросили новый - перезаписали в БД - используем в запросах Rest API к Амо.
Как вариант слышал что можно ещё сохранять expires_at что бы каждый раз не проверять валидность. Мы же делали каждый раз в curl проверяя, наверное это не очень оптимально если запросов будет много, но при малом-среднем количестве работает нормально:
$access_token = $this -> tokensService -> get_token();
if ($this -> tokensService -> access_token_valid ($access_token)) {
$access_token = $this -> tokensService -> refresh_token ();
};И погнало по кругу.
Если свести всё к нескольким правилам:
Authorization Code нужен в основном для первоначальной авторизации.
Он обменивается на access_token и refresh_token.
Access_token используется для REST-запросов.
Когда access token заканчивается, используется refresh_token.
После refresh amoCRM выдаёт новую пару Access + Refresh.
Оба новых токена необходимо сохранить.
Старый Refresh token после обмена использовать нельзя.
При колхозном подходе все это можно запихать в 1 файл PHP, но по правильному разбивать на: Authamo.php, AmoAPIService.php, TokenService.php - что бы каждый выполнял свою функцию.
Authamo.php - авторизация первый раз
AmoAPIService.php - это как раз регулярные запросы к API, когда токен уже есть
TokensService.php - это как раз работа с токенами если при проверке IF в AmoAPIService.php вылетело что невалидный.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.