Google Tasks Authentication: Handle refresh tokens for Embedded Devices

179 Views Asked by At

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!

1

There are 1 best solutions below

8
Linda Lawton - DaImTo On

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