0

I am developing for a personal embedded device (the Visionect e-ink display) that can run some node.js code to display tasks from my Google Tasks. It works fine but the token expires every so often. How do I handle the token expiry? I cannot show the login screen ofcourse on this e-ink display. How can I permanently have code that works till the user (in this case there is only 1 user - me) revokes the access token?

I looked at this answer but I am not sure it answers my question: Google API Authentication Tasks API

Here is my code:

/*
credentials.json:

{
  "installed": {
    "client_id": "XXXXXXX.apps.googleusercontent.com",
    "project_id": "XXXXXXX",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "XXXXXXX",
    "redirect_uris": [
      "http://localhost"
    ]
  }
}
*/

import fs from 'fs'
import path from 'path'
import process from 'process'
import {authenticate} from '@google-cloud/local-auth'

const SCOPES = ['https://www.googleapis.com/auth/tasks.readonly']
const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json')

let myauth = undefined

if (!myauth) {
await authenticate({scopes: SCOPES, keyfilePath: CREDENTIALS_PATH})
  .then(client => {
    const keys = JSON.parse(fs.readFileSync(CREDENTIALS_PATH))
    const key = keys.installed || keys.web
    myauth = {
      type: 'authorized_user',
      client_id: key.client_id,
      client_secret: key.client_secret,
      refresh_token: client.credentials.refresh_token,
    }
    console.log(myauth)
  })
}

import {google} from 'googleapis'

google.options({auth: google.auth.fromJSON({
      type: "authorized_user",
      client_id: myauth.client_id
      client_secret: myauth.client_secret,
      refresh_token: myauth.refresh_token
 
)})


// Method to fetch tasks from last n days
const tasks = (maxDueDays) => {
  const fetchTaskList = (taskList) => taskApi.tasks
    .list({
      tasklist: taskList.id,
      showCompleted: false,
      dueMax: dayjs().add(maxDueDays, 'days').format()
    })
    .then(res => res.data.items.map(task => Object.assign(task, {taskList: taskList.title})))

  return taskApi
    .tasklists.list()
    .then(res => Promise.all(res.data.items.map(fetchTaskList)))
    .then(tasks => tasks.flat())
}

tasks(7).then(res => console.log(res))

After a bit (I poll once a hour), I often get:

ERROR 400: {
  error: 'invalid_grant',
  error_description: 'Token has been expired or revoked.'
}

Here are some other Github issues where people encountered my problem:

Does Google provide a "personal API key" which let's me just read my own data bypassing the oauth dance since this app is only intended for my personal use? If not, is there a programmatic way to handle token expiry which does not involve opening a browser since this is running on an embedded headless device? Google's own sample code tutorial does not really handle token expiry!

pathikrit
  • 32,469
  • 37
  • 142
  • 221

1 Answers1

1

Set your app to production in google cloud console under the oauth2 consent screen and your refresh token will stop expiring

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • But, I don't want it to be available to other people. This for my personal tasks only. Plus publishing it requires ALL THIS: Your app will be available to any user with a Google Account. You've configured your app in a way that requires verification . To complete verification, you will need to provide: An official link to your app's Privacy Policy A YouTube video showing how you plan to use the Google user data you get from scopes A written explanation telling Google why you need access to sensitive and/or restricted user data All your domains verified in Google Search Console – pathikrit Jun 07 '23 at 17:56
  • Actually, are you even sure your assertion is correct? I don't see any documentation which says refresh tokens don't expire in production mode. Do you have a link? – pathikrit Jun 07 '23 at 18:10
  • I said production not verification its two different things [experation](https://developers.google.com/identity/protocols/oauth2#expiration) **publishing status of "Testing" is issued a refresh token expiring in 7 days,** – Linda Lawton - DaImTo Jun 07 '23 at 18:47
  • Yes, when I clicked on "go to production" - I got that message. See screenshot: https://imgur.com/gallery/sNRXEMn Also, your link is about handling expired refresh tokens. My question is to handle expired access tokens - not expired refresh tokens per se. – pathikrit Jun 07 '23 at 19:15
  • The code should automatically refresh the access token using the refresh token as needed. If its longer than an hour the issue is probably the refresh token – Linda Lawton - DaImTo Jun 07 '23 at 19:28
  • That's what I thought - it should auto refresh but I get `400: Token has been expired or revoked` every once in a while. So maybe you are right - its the refresh token expiring? Its not clear from error message which token they are talking about access_token or refresh_token? In that case, as I mentioned earlier, "changing to prod" means I have to do all the steps from here: imgur.com/gallery/sNRXEMn? – pathikrit Jun 08 '23 at 13:19
  • Yes that is the error message that the refresh token has expired. You dont need to verify it unless you want others to use this. If its just for you to use then you dont need to verify the application. – Linda Lawton - DaImTo Jun 08 '23 at 14:08
  • But it says in the screenshot (https://imgur.com/gallery/sNRXEMn) that I do need verification because I think it uses a sensitive scope like the Google Tasks API. – pathikrit Jun 08 '23 at 18:48