Я пытаюсь подключиться к Google API, который требует аутентификации OAuth 2.0. Первый шаг требует от меня создания веб-токена JSON (JWT): https://developers.google.com/accounts/docs/OAuth2ServiceAccount?hl=ru#creatingjwt в следующем формате:
{Base64url encoded header}.
{Base64url encoded claim set}.
{Base64url encoded signature}
Я следую всем указаниям согласно документации, но продолжаю получать ошибку «invalid_grant». Я подозреваю, что это связано с последней (подписью) порцией. Согласно документации:
«Алгоритм подписи в заголовке JWT должен использоваться при вычислении подписи. Единственный алгоритм подписи, поддерживаемый сервером авторизации Google OAuth 2.0, — это RSA, использующий алгоритм хеширования SHA-256. Это выражается как RS256 в поле alg в заголовке JWT «.
«Подпишите представление ввода UTF-8, используя SHA256 с RSA (также известный как RSASSA-PKCS1-V1_5-SIGN с хэш-функцией SHA-256), с помощью закрытого ключа, полученного из консоли разработчиков Google. Выходными данными будет байтовый массив. «
«Затем подпись должна быть закодирована в Base64url».
Я скопировал закрытый ключ непосредственно из консоли разработчика Google (включая часть «BEGIN PRIVATE KEY») и закодировал его в base64. Это код, который я использую:
$time = time();
$url = 'https://accounts.google.com/o/oauth2/token';
$assertion = base64_encode('{"alg":"RS256","typ":"JWT"}').'.';
$assertion .= base64_encode('{
"iss":"'.$this->configs['client_id'].'",
"scope":"https://www.googleapis.com/auth/youtube",
"aud":"'.$url.'",
"exp":'.($time+3600).',
"iat":'.$time.'
}').'.';
$assertion .= base64_encode('[-----BEGIN PRIVATE KEY-----privateKeyHere-----END PRIVATE KEY-----\n]');
$headers = array(
'Host: accounts.google.com',
'Content-Type: application/x-www-form-urlencoded'
);
$fields = array(
'grant_type' => urlencode('urn:ietf:params:oauth:grant-type:jwt-bearer'),
'assertion' => urlencode($assertion)
);
$fields_string = '';
foreach($fields as $key => $value) $fields_string .= '&'.$key.'='.$value;
$fields_string = substr($fields_string, 1);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url );
curl_setopt($ch, CURLOPT_HEADER, TRUE );
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers );
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1 );
$result = curl_exec($ch);
curl_close($ch);
var_dump($result);
Любые идеи относительно того, почему это возвращает ошибку «invalid_grant»?
У вас есть 2 проблемы:
Первый: функция PHP base64_encode не безопасна для URL, как того требует спецификация JWT.
Чтобы получить строку Base64Url, используйте следующую функцию:
function base64url_encode($data)
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
Замените свой base64_encode
звонки с base64url_encode
Второй: Вы никогда не подписываете свои данные! Вы просто добавляете свой закрытый ключ, а не подпись заголовка и набора требований.
Используйте следующую функцию:
function sign($input, $private_key)
{
$signature = null;
openssl_sign($input, $signature, $private_key, "SHA256");
return $signature;
}
И заменить
$assertion .= base64_encode('{
"iss":"'.$this->configs['client_id'].'",
"scope":"https://www.googleapis.com/auth/youtube",
"aud":"'.$url.'",
"exp":'.($time+3600).',
"iat":'.$time.'
}').'.';
$assertion .= base64_encode('[-----BEGIN PRIVATE KEY-----privateKeyHere-----END PRIVATE KEY-----\n]');
с
$assertion .= base64_encode('{
"iss":"'.$this->configs['client_id'].'",
"scope":"https://www.googleapis.com/auth/youtube",
"aud":"'.$url.'",
"exp":'.($time+3600).',
"iat":'.$time.'
}');
$assertion .= '.'.base64url_encode(sign($assertion, '[-----BEGIN PRIVATE KEY-----privateKeyHere-----END PRIVATE KEY-----\n]'));
Вы получаете invalid_grant, потому что вы сделали
1.
'grant_type' => urlencode('urn:ietf:params:oauth:grant-type:jwt-bearer')
измените это просто
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'
2.
Поле подтверждения iss не является ClientID из учетных данных. Это адрес электронной почты.
3.
Время должно быть в UTC, поэтому вы должны сделать
$time = gmmktime();
4.
Как уже упоминалось @ florent-morselli вам нужно подписать подпись. Это правда.
5.
Проверьте время вашей машины. Важно, чтобы время было синхронизировано
После этого все будет хорошо, и Google предоставит вам access_token.
КСТАТИ:
privateKey should be without []\n characters (In my case this is lines of 64 symbols)
CURLOPT_POST takes only true or false
http_build_query is better way to build queryString
'Host: accounts.google.com' in $headers is useless, because you set CURLOPT_URL