Проверка подлинности OAuth 2.0 в Google API возвращает & quot; invalid_grant & quot; возможная проблема формата подписи закрытого ключа?

Я пытаюсь подключиться к 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»?

3

Решение

У вас есть 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]'));
0

Другие решения

Вы получаете 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
0

По вопросам рекламы [email protected]