С помощью библиотеки google oauth2 я могу успешно аутентифицировать пользователя при их первом проходе, получить их токен обновления и первый токен доступа. Пока токен не истечет, все работает как положено.

Однако, когда срок действия токена доступа истекает, мне нужно получить новый токен доступа и сохранить эти токены в моем хранилище данных, используя существующий токен обновления. Я знаю, что в документации указано, что токены должны повторно загружаться, когда срок их действия истекает, но поскольку я создаю нового клиента для каждого вызова (чтобы токены не использовались повторно между пользователями), я думаю, что клиент срывается до того, как токен получает шанс освежиться.

Проверяя, что делает библиотека, вызывая фактический API Google, я должен получить новые токены доступа, вызвав метод client.refreshAccessToken(), ответ на этот вызов дает мне ошибку invalid_grant Bad Request. Я сравнил фактический запрос api, который делает этот метод, с запросом на google oauth2 Playground, и эти два вызова идентичны - хотя их призыв обновить их токены работает, а мой - нет.

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

const { google } = require('googleapis')
const scopes = [
  'https://www.googleapis.com/auth/spreadsheets.readonly',
  'https://www.googleapis.com/auth/userinfo.email',
  'https://www.googleapis.com/auth/drive.readonly'
]

module.exports = (env, mongo) => {
  const getBaseClient = () => {
    const { OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL } = env.credentials
    return new google.auth.OAuth2(
      OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL
    )
  }

  const getNewAccessTokens = async (authId, refreshToken) => {
    const { tokens } = await getBaseClient().getToken(refreshToken)
    await mongo.setAccessTokensForAuthUser(authId, { ...tokens, refresh_token: refreshToken })
    return tokens
  }

  const getAuthedClient = async (authId) => {
    let tokens = await mongo.getAccessTokensForAuthUser(authId)

    if (!tokens.access_token) {
      tokens = await getNewAccessTokens(authId, tokens.refresh_token)
    }

    const client = getBaseClient()
    client.setCredentials(tokens)

    if (client.isTokenExpiring()) {
      const { credentials } = await client.refreshAccessToken()
      tokens = { ...credentials, refresh_token: tokens.refreshToken }
      await mongo.setAccessTokensForAuthUser(authId, tokens)
      client.setCredentials(tokens)
    }

    return client
  }

  const generateAuthUrl = (userId) => {
    return getBaseClient().generateAuthUrl({
      access_type: 'offline',
      scope: scopes,
      state: `userId=${userId}`
    })
  }

  const getUserInfo = async (authId) => {
    const auth = await getAuthedClient(authId)
    return google.oauth2({ version: 'v2', auth }).userinfo.get({})
  }

  const listSheets = async (authId) => {
    const auth = await getAuthedClient(authId)
    let nextPageToken = null
    let results = []
    do {
      const { data } = await google
        .drive({ version: 'v3', auth })
        .files.list({
          q: 'mimeType = \'application/vnd.google-apps.spreadsheet\'',
          includeItemsFromAllDrives: true,
          supportsAllDrives: true,
          corpora: 'user',
          orderBy: 'name',
          pageToken: nextPageToken
        })
      nextPageToken = data.nextPageToken
      results = results.concat(data.files)
    } while (nextPageToken)
    return results
  }

  return {
    generateAuthUrl,
    getUserInfo,
    listSheets
  }
}

1
Kristian Epps 18 Июн 2020 в 21:43

1 ответ

Лучший ответ

Я решил свою проблему.

Я объединял access_codes с refresh_tokens и полагал, что код, который вы получаете от URL-адреса аутентификации, был refresh_token, сохранял его и пытался повторно использовать, чтобы получить больше access_tokens. Это не верно. Не делай этого.

Вы получаете access_code из URL-адреса аутентификации, и при первом использовании его с методом client.getToken(code) вы получаете refresh_token и access_token.

Я приложил обновленный и рабочий код, если кто-то захочет его использовать.

Я также должен упомянуть, что я добавил prompt: 'consent' к URL-адресу аутентификации, чтобы вы всегда получали access_code, которое можно использовать для получения refresh_token, когда кто-то повторно аутентифицируется (как если бы вы не , то вызов client.getToken() не возвращает refresh_token (часть того, что меня изначально сбивало с толку).

const { google } = require('googleapis')
const scopes = [
  'https://www.googleapis.com/auth/spreadsheets.readonly',
  'https://www.googleapis.com/auth/userinfo.email',
  'https://www.googleapis.com/auth/drive.readonly'
]

module.exports = (env, mongo) => {
  const getBaseClient = () => {
    const { OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL } = env.credentials
    return new google.auth.OAuth2(
      OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL
    )
  }

  const getAuthedClient = async (authId) => {
    let tokens = await mongo.getAccessTokensForAuthUser(authId)

    const client = getBaseClient()
    client.setCredentials(tokens)

    if (client.isTokenExpiring()) {
      const { credentials } = await client.refreshAccessToken()
      tokens = { ...credentials, refresh_token: tokens.refresh_token }
      await mongo.setAccessTokensForAuthUser(authId, tokens)
      client.setCredentials(tokens)
    }

    return client
  }

  const generateAuthUrl = (userId) => {
    return getBaseClient().generateAuthUrl({
      access_type: 'offline',
      prompt: 'consent',
      scope: scopes,
      state: `userId=${userId}`
    })
  }

  const getUserInfo = async (accessCode) => {
    const auth = getBaseClient()
    const { tokens } = await auth.getToken(accessCode)
    auth.setCredentials(tokens)
    const { data } = await google.oauth2({ version: 'v2', auth }).userinfo.get({})
    return { ...data, tokens }
  }

  const listSheets = async (authId) => {
    const auth = await getAuthedClient(authId)
    let nextPageToken = null
    let results = []
    do {
      const { data } = await google
        .drive({ version: 'v3', auth })
        .files.list({
          q: 'mimeType = \'application/vnd.google-apps.spreadsheet\'',
          includeItemsFromAllDrives: true,
          supportsAllDrives: true,
          corpora: 'user',
          orderBy: 'name',
          pageToken: nextPageToken
        })
      nextPageToken = data.nextPageToken
      results = results.concat(data.files)
    } while (nextPageToken)
    return results
  }

  return {
    generateAuthUrl,
    getUserInfo,
    listSheets
  }
}
0
Kristian Epps 19 Июн 2020 в 20:41