通过 Node.js 快速启动身份验证错误的 Youtube API

Posted

技术标签:

【中文标题】通过 Node.js 快速启动身份验证错误的 Youtube API【英文标题】:Youtube API via Node.js QuickStart Authentication Error 【发布时间】:2021-12-03 21:15:51 【问题描述】:

Google 提供了一个在线教程,介绍如何通过 node.js 连接到他们的 API,我将其用于 Discord 机器人。主要问题是它似乎已经过时了。

https://developers.google.com/youtube/v3/quickstart/nodejs

第一个问题是他们要求您选择一个“其他”选项,该选项不存在并已被“桌面应用程序”取代。不管怎样,很容易解决。

我遇到的主要问题是他们的 quickstart.js 代码:

var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;

// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
    process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json';

// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) 
  if (err) 
    console.log('Error loading client secret file: ' + err);
    return;
  
  // Authorize a client with the loaded credentials, then call the YouTube API.
  authorize(JSON.parse(content), getChannel);
);

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 *
 * @param Object credentials The authorization client credentials.
 * @param function callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) 
  var clientSecret = credentials.installed.client_secret;
  var clientId = credentials.installed.client_id;
  var redirectUrl = credentials.installed.redirect_uris[0];
  var oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, function(err, token) 
    if (err) 
      getNewToken(oauth2Client, callback);
     else 
      oauth2Client.credentials = JSON.parse(token);
      callback(oauth2Client);
    
  );


/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 *
 * @param google.auth.OAuth2 oauth2Client The OAuth2 client to get token for.
 * @param getEventsCallback callback The callback to call with the authorized
 *     client.
 */
function getNewToken(oauth2Client, callback) 
  var authUrl = oauth2Client.generateAuthUrl(
    access_type: 'offline',
    scope: SCOPES
  );
  console.log('Authorize this app by visiting this url: ', authUrl);
  var rl = readline.createInterface(
    input: process.stdin,
    output: process.stdout
  );
  rl.question('Enter the code from that page here: ', function(code) 
    rl.close();
    oauth2Client.getToken(code, function(err, token) 
      if (err) 
        console.log('Error while trying to retrieve access token', err);
        return;
      
      oauth2Client.credentials = token;
      storeToken(token);
      callback(oauth2Client);
    );
  );


/**
 * Store token to disk be used in later program executions.
 *
 * @param Object token The token to store to disk.
 */
function storeToken(token) 
  try 
    fs.mkdirSync(TOKEN_DIR);
   catch (err) 
    if (err.code != 'EEXIST') 
      throw err;
    
  
  fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => 
    if (err) throw err;
    console.log('Token stored to ' + TOKEN_PATH);
  );


/**
 * Lists the names and IDs of up to 10 files.
 *
 * @param google.auth.OAuth2 auth An authorized OAuth2 client.
 */
function getChannel(auth) 
  var service = google.youtube('v3');
  service.channels.list(
    auth: auth,
    part: 'snippet,contentDetails,statistics',
    forUsername: 'GoogleDevelopers'
  , function(err, response) 
    if (err) 
      console.log('The API returned an error: ' + err);
      return;
    
    var channels = response.data.items;
    if (channels.length == 0) 
      console.log('No channel found.');
     else 
      console.log('This channel\'s ID is %s. Its title is \'%s\', and ' +
                  'it has %s views.',
                  channels[0].id,
                  channels[0].snippet.title,
                  channels[0].statistics.viewCount);
    
  );

您可以在下面看到控制台要求您按照 URL 获取代码。使用我用来创建 OAuth 的开发人员电子邮件执行此操作会使我进入失败屏幕。

node quickstart.js
Authorize this app by visiting this url:  https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly&response_type=code&client_id=645691957923-bsnq8pdtt4dcoprqolb5tovjp92ma718.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob
Enter the code from that page here:

所以我必须将我的开发人员电子邮件添加为测试用户,这样我就可以生成代码。正确,然后我将其输入控制台仅收到以下错误:

node quickstart.js
Authorize this app by visiting this url:  https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly&response_type=code&client_id=645691957923-bsnq8pdtt4dcoprqolb5tovjp92ma718.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob
Enter the code from that page here: <code went here>
Error while trying to retrieve access token GaxiosError: invalid_grant
    at Gaxios._request (E:\Carleton\Year4\Honors\node_modules\gaxios\build\src\gaxios.js:129:23)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async OAuth2Client.getTokenAsync (E:\Carleton\Year4\Honors\node_modules\google-auth-library\build\src\auth\oauth2client.js:124:21) 
  response: 
    config: 
      method: 'POST',
      url: 'https://oauth2.googleapis.com/token',
      data: 'code=4%2F1AX4XfWgjMdTQj5gcKCf76-HZowlr4Ohhz9Zn3Zg6B3i-MkCFVCP1qfV9F88&client_id=645691957923-bsnq8pdtt4dcoprqolb5tovjp92ma718.apps.googleusercontent.com&client_secret=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&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=authorization_code&code_verifier=',
      headers: [Object],
      paramsSerializer: [Function: paramsSerializer],
      body: 'code=4%2F1AX4XfWgjMdTQj5gcKCf76-HZowlr4Ohhz9Zn3Zg6B3i-MkCFVCP1qfV9F88&client_id=645691957923-bsnq8pdtt4dcoprqolb5tovjp92ma718.apps.googleusercontent.com&client_secret=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&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=authorization_code&code_verifier=',
      validateStatus: [Function: validateStatus],
      responseType: 'json'
    ,
    data:  error: 'invalid_grant', error_description: 'Bad Request' ,
    headers: 
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      connection: 'close',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=utf-8',
      date: 'Fri, 15 Oct 2021 17:18:58 GMT',
      expires: 'Mon, 01 Jan 1990 00:00:00 GMT',
      pragma: 'no-cache',
      server: 'scaffolding on HTTPServer2',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    ,
    status: 400,
    statusText: 'Bad Request',
    request:  responseURL: 'https://oauth2.googleapis.com/token' 
  ,
  config: 
    method: 'POST',
    url: 'https://oauth2.googleapis.com/token',
    data: 'code=4%2F1AX4XfWgjMdTQj5gcKCf76-HZowlr4Ohhz9Zn3Zg6B3i-MkCFVCP1qfV9F88&client_id=645691957923-bsnq8pdtt4dcoprqolb5tovjp92ma718.apps.googleusercontent.com&client_secret=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&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=authorization_code&code_verifier=',
    headers: 
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/7.10.1',
      'x-goog-api-client': 'gl-node/16.9.1 auth/7.10.1',
      Accept: 'application/json'
    ,
    paramsSerializer: [Function: paramsSerializer],
    body: 'code=4%2F1AX4XfWgjMdTQj5gcKCf76-HZowlr4Ohhz9Zn3Zg6B3i-MkCFVCP1qfV9F88&client_id=645691957923-bsnq8pdtt4dcoprqolb5tovjp92ma718.apps.googleusercontent.com&client_secret=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&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=authorization_code&code_verifier=',
    validateStatus: [Function: validateStatus],
    responseType: 'json'
  ,
  code: '400'

有没有人知道是什么导致了这个身份验证问题?我是与 api 交互的新手。我想我需要一些 YouTube/Google 没有在教程中向我解释的附加凭据,因为它似乎已经过时了。

【问题讨论】:

【参考方案1】:

invalid_grant 可能是一个很难确定的错误,让我们按照最常见的原因开始。

    仔细检查您是否创建了已安装的应用程序凭据。是的,应该是桌面凭据。 检查您机器上的时间,它必须与NTP 同步。 确保您没有尝试使用授权码两次。它只会工作一次,不到五分钟就会过期。 在具有访问权限的第三方应用程序下的用户谷歌帐户中将其完全删除。然后再试一次。

除此之外。我会仔细检查你复制的内容

code=4%2F1AX4XfWgjMdTQj5gcKCf76-HZowlr4Ohhz9Zn3Zg6B3i-MkCFVCP1qfV9F88

开头那 4% 让我觉得很奇怪。

【讨论】:

【参考方案2】:

实际问题有点傻。主要问题是令牌的路径名无效导致的 mkdir 错误。

var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
    process.env.USERPROFILE) + '/.credentials/';

如果我在收到错误后再次运行程序,它会给我在原始问题上发布的新错误。这是因为重新运行程序需要您从 Google 获取新代码,我不明白。所以无效的授权来自过时的代码。解决方法是将路径名更改为可以轻松为 .credentials 创建目录的位置。基本上只要确保每次新运行都获取一个代码,直到您存储了您的凭据/令牌。

【讨论】:

以上是关于通过 Node.js 快速启动身份验证错误的 Youtube API的主要内容,如果未能解决你的问题,请参考以下文章

Node.js E00007 Authorize.net 错误 - 由于身份验证值无效,用户身份验证失败

Node.js 和 socket.io 的 Redis 身份验证错误

如何使用 node.js 对外部 webapp 的用户进行身份验证?

Node.js & MySQL - 错误:1251 - 客户端不支持服务器请求的身份验证协议;考虑升级 MySQL 客户端

node.js 的用户身份验证库?

Node.js 双重身份验证