облачная аутентификация Google с токеном на предъявителя через nodejs - PullRequest
0 голосов
/ 17 февраля 2020

На моем клиенте запущен API-интерфейс GraphQL, работающий в облачном хранилище Google.

Я получил служебную учетную запись для аутентификации, а также доступ к инструменту командной строки gcloud.

При использовании команды gcloud строка так:

gcloud auth print-identity-token

Я могу сгенерировать токен, который можно использовать для отправки запросов к API. Это работает, и я могу делать успешные почтовые запросы к API от почтальона, бессонницы и от моего приложения nodejs.

Однако, когда я использую аутентификацию JWT с "googleapis" или "google-auth" npm библиотеки, например, так:

var { google } = require('googleapis')

let privatekey = require('./auth/google/service-account.json')

let jwtClient = new google.auth.JWT(
  privatekey.client_email,
  null,
  privatekey.private_key,
  ['https://www.googleapis.com/auth/cloud-platform']
)

jwtClient.authorize(function(err, _token) {
  if (err) {
    console.log(err)
    return err
  } else {
    console.log('token obj:', _token)
  }
})

Это выводит токен "на предъявителя":

token obj: {
  access_token: 'ya29.c.Ko8BvQcMD5zU-0raojM_u2FZooWMyhB9Ni0Yv2_dsGdjuIDeL1tftPg0O17uFrdtkCuJrupBBBK2IGfUW0HGtgkYk-DZiS1aKyeY9wpXTwvbinGe9sud0k1POA2vEKiGONRqFBSh9-xms3JhZVdCmpBi5EO5aGjkkJeFI_EBry0E12m2DTm0T_7izJTuGQ9hmyw',
  token_type: 'Bearer',
  expiry_date: 1581954138000,
  id_token: undefined,
  refresh_token: 'jwt-placeholder'
}

, однако этот токен на переносчике не работает, как указано выше, и всегда выдает "неавторизованную ошибку 401" при выполнении тех же запросов, что и с командой gcloud "gcloud auth print-identity-token".

Пожалуйста, помогите, я не уверен, почему работает первый токен-носитель, а тот, сгенерированный с помощью JWT, не работает.

РЕДАКТИРОВАТЬ

Я также попытался получить токен идентификации вместо токена доступа, например так:

let privatekey = require('./auth/google/service-account.json')

let jwtClient = new google.auth.JWT(
  privatekey.client_email,
  null,
  privatekey.private_key,
  []
)

jwtClient
  .fetchIdToken('https://my.audience.url')
  .then((res) => console.log('res:', res))
  .catch((err) => console.log('err', err))

Это печатает токен идентификации, однако, использование этого также просто дает сообщение «401 неавторизован».

Отредактируйте, чтобы показать, как я вызываю конечную точку

Примечание: любой из этих методов ниже работает с токеном идентификации командной строки, однако при генерации d через JWT возвращает 401

метод 1:

 const client = new GraphQLClient(baseUrl, {
        headers: {
          Authorization: 'Bearer ' + _token.id_token
        }
      })
      const query = `{
        ... my graphql query goes here ...
    }`
      client
        .request(query)
        .then((data) => {
          console.log('result from query:', data)
          res.send({ data })
          return 0
        })
        .catch((err) => {
          res.send({ message: 'error ' + err })
          return 0
        })
    }

метод 2 (с использованием «авторизованного» клиента, созданного с помощью google-auth):

  const res = await client.request({
    url: url,
    method: 'post',
    data: `{
        My graphQL query goes here ...
    }`
  })
  console.log(res.data)
}

Ответы [ 2 ]

1 голос
/ 18 февраля 2020

Вот пример в node.js, который правильно создает Identity Token с правильной аудиторией для вызова службы Cloud Run или Cloud Functions.

Измените этот пример, чтобы он соответствовал GraphQLClient. Не забудьте включить заголовок авторизации в каждый вызов.

// This program creates an OIDC Identity Token from a service account
// and calls an HTTP endpoint with the Identity Token as the authorization

var { google } = require('googleapis')
const request = require('request')

// The service account JSON key file to use to create the Identity Token
let privatekey = require('/config/service-account.json')

// The HTTP endpoint to call with an Identity Token for authorization
// Note: This url is using a custom domain. Do not use the same domain for the audience
let url = 'https://example.jhanley.dev'

// The audience that this ID token is intended for (example Google Cloud Run service URL)
// Do not use a custom domain name, use the Assigned by Cloud Run url
let audience = 'https://example-ylabperdfq-uc.a.run.app'

let jwtClient = new google.auth.JWT(
    privatekey.client_email,
    null,
    privatekey.private_key,
    audience
)

jwtClient.authorize(function(err, _token) {
    if (err) {
        console.log(err)
        return err
    } else {
        // console.log('token obj:', _token)

        request(
            {
                url: url,
                headers: {
                    "Authorization": "Bearer " + _token.id_token
                }
            },
            function(err, response, body) {
                if (err) {
                    console.log(err)
                    return err
                } else {
                    // console.log('Response:', response)
                    console.log(body)
                }
            }
        );
    }
})
0 голосов
/ 17 февраля 2020

Вы можете найти официальную документацию для узла OAuth2

A complete OAuth2 example:

const {OAuth2Client} = require('google-auth-library');
const http = require('http');
const url = require('url');
const open = require('open');
const destroyer = require('server-destroy');

// Download your OAuth2 configuration from the Google
const keys = require('./oauth2.keys.json');

/**
 * Start by acquiring a pre-authenticated oAuth2 client.
 */
async function main() {
  const oAuth2Client = await getAuthenticatedClient();
  // Make a simple request to the People API using our pre-authenticated client. The `request()` method
  // takes an GaxiosOptions object.  Visit https://github.com/JustinBeckwith/gaxios.
  const url = 'https://people.googleapis.com/v1/people/me?personFields=names';
  const res = await oAuth2Client.request({url});
  console.log(res.data);

  // After acquiring an access_token, you may want to check on the audience, expiration,
  // or original scopes requested.  You can do that with the `getTokenInfo` method.
  const tokenInfo = await oAuth2Client.getTokenInfo(
    oAuth2Client.credentials.access_token
  );
  console.log(tokenInfo);
}


/**
 * Create a new OAuth2Client, and go through the OAuth2 content
 * workflow.  Return the full client to the callback.
 */
function getAuthenticatedClient() {
  return new Promise((resolve, reject) => {
    // create an oAuth client to authorize the API call.  Secrets are kept in a `keys.json` file,
    // which should be downloaded from the Google Developers Console.
    const oAuth2Client = new OAuth2Client(
      keys.web.client_id,
      keys.web.client_secret,
      keys.web.redirect_uris[0]
    );

    // Generate the url that will be used for the consent dialog.
    const authorizeUrl = oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: 'https://www.googleapis.com/auth/userinfo.profile',
    });

    // Open an http server to accept the oauth callback. In this simple example, the
    // only request to our webserver is to /oauth2callback?code=<code>
    const server = http
      .createServer(async (req, res) => {
        try {
          if (req.url.indexOf('/oauth2callback') > -1) {
            // acquire the code from the querystring, and close the web server.
            const qs = new url.URL(req.url, 'http://localhost:3000')
              .searchParams;
            const code = qs.get('code');
            console.log(`Code is ${code}`);
            res.end('Authentication successful! Please return to the console.');
            server.destroy();

            // Now that we have the code, use that to acquire tokens.
            const r = await oAuth2Client.getToken(code);
            // Make sure to set the credentials on the OAuth2 client.
            oAuth2Client.setCredentials(r.tokens);
            console.info('Tokens acquired.');
            resolve(oAuth2Client);
          }
        } catch (e) {
          reject(e);
        }
      })
      .listen(3000, () => {
        // open the browser to the authorize url to start the workflow
        open(authorizeUrl, {wait: false}).then(cp => cp.unref());
      });
    destroyer(server);
  });
}

main().catch(console.error);

Редактировать

Еще один пример запуска в облаке.

// sample-metadata:
//   title: ID Tokens for Cloud Run
//   description: Requests a Cloud Run URL with an ID Token.
//   usage: node idtokens-cloudrun.js <url> [<target-audience>]

'use strict';

function main(
  url = 'https://service-1234-uc.a.run.app',
  targetAudience = null
) {
  // [START google_auth_idtoken_cloudrun]
  /**
   * TODO(developer): Uncomment these variables before running the sample.
   */
  // const url = 'https://YOUR_CLOUD_RUN_URL.run.app';
  const {GoogleAuth} = require('google-auth-library');
  const auth = new GoogleAuth();

  async function request() {
    if (!targetAudience) {
      // Use the request URL hostname as the target audience for Cloud Run requests
      const {URL} = require('url');
      targetAudience = new URL(url).origin;
    }
    console.info(
      `request Cloud Run ${url} with target audience ${targetAudience}`
    );
    const client = await auth.getIdTokenClient(targetAudience);
    const res = await client.request({url});
    console.info(res.data);
  }

  request().catch(err => {
    console.error(err.message);
    process.exitCode = 1;
  });
  // [END google_auth_idtoken_cloudrun]
}

const args = process.argv.slice(2);
main(...args);
...