reactjsgoogle-apigoogle-oauthgoogle-cloud-print

Error: User credentials required in Google Cloud Print API


I'm trying to use Google Cloud Print(GCP) API, but I can't make it works. Maybe I've understood bad the workflow because is the first time I'm using the google api, please help me to understand how to make it works.

Initial considerations:

What exactly I want:

To make my first test, I am looking to get all information about my printer.

What I did:

  1. I created a project in: https://console.developers.google.com
  2. Inside the project created, I created a credential:
    • create credentials -> OAuth client ID

And I chose Application type: Web, and also configure the restrictions to source and redirection to my localhost.

  1. Manually in https://www.google.com/cloudprint, I added my printer, I made a test printing a PDF and was OK.

  2. I created a project in reactJS to get the information of my printer I've added.

Component:

Explanation:

code:

import React, { Component } from 'react'
import GoogleLogin from 'react-google-login';
import { connect } from 'react-redux'

import { getPrinters } from '../actions/settings'

class Setting extends Component {

  responseGoogle(response) {
    const accessToken = response.accessToken
    localStorage.setItem('googleToken', accessToken)
  }

  render() {
    return (
      <div>
        <GoogleLogin
          clientId="CLIENT_ID_REMOVED_INTENTIONALLY.apps.googleusercontent.com"
          buttonText="Login"
          onSuccess={this.responseGoogle}
          onFailure={this.responseGoogle}
        />
        <button
          onClick = {() => {
            this.props.getPrinters()
          }}
        >test printer</button>
      </div>
    )
  }

}

const mapStateToProps = state => {
  return {
    state: state
  }
}

const mapDispatchToProps = dispatch => {
  return {
    getPrinters() {
      dispatch(getPrinters())
    }
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Setting)

Action or Function to get information printer:

Explanation:

Google Cloud Print API: User credentials required

Google Cloud Print User credentials required

code:

import axios from 'axios'

axios.defaults.headers.common['Authorization'] = 'OAuth ' +    localStorage.getItem('googleToken')
axios.defaults.headers.common['scope'] =   'https://www.googleapis.com/auth/cloudprint'
axios.defaults.headers.common['X-CloudPrint-Proxy'] = 'printingTest'

const getPrinters = () => {

  return () => {
    return axios.get('https://www.google.com/cloudprint/printer'
      , {
          params: {
            printeid: 'PRINTER_ID_REMOVED_INTENTIONALLY'
          }
        }
      )
      .then(response => {
        console.log('response of google cloud print')
        console.log(response)
      })
  }

}

export { getPrinters }

Error:

After all explained before, I got the next error:

User credentials required

Error 403

Note:

I'm using CORS plugin by recommendation of:

Chrome extensions for silent print?

because initially, I had cors error.

Any suggestion or recommendation would be very useful, thanks.


Solution

  • I've resolved my problem, my main problem about User Credential required were because I was using the incorrect access token and It was because I was getting the access token incorrectly.

    I'm going to explain my whole solution because there are few examples of codes with this API.

    Solutions:

    1. The steps described were Ok until the fourth step where I used the external component react-google-login to trying to get the access token, instead I used googleapis module: Link Github googleapis

    2. Also to avoid CORS problem(and not use CORS chrome plugin) I wrote the requests to Google API in server side.(NODEJS)

    3. I had also a problem in the frontend when I tried to generate a popup to give permission for printer(problems about CORS), my solution was to use this very simple module for authentication: Link Github oauth-open

    General scheme:

    enter image description here

    Explanation:

    Knowing I have all data described in my question post(until the third step).

    Authentication:

    Print:

    Recover tokens and expiration date from database, with the expiration date check if the token has expired, and if It has already expired then gets another token and replace the old access token with the new one, replacing also with the new expiration date, to obtain this new data only is necessary call to method refreshAccessToken of module googleapis.Note: the refresh token never expires.

    After having the access token updated, use it to send data to the printer with Google route(.../submit)

    Code:

    Route get URL authentication.

    const express = require('express');
    const google = require('googleapis');
    const router = express.Router();
    
    var OAuth2 = google.auth.OAuth2;
    const redirect_url = 'http://localhost:3001/setting';  //Your redirect URL
    var oauth2Client = new OAuth2(
      'CLIENT ID',      //Replace it with your client id
      'CLIEND SECRET',  //Replace it with your client secret
       redirect_url
    );
    
    var url = oauth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: 'https://www.googleapis.com/auth/cloudprint'
    });
    
    router.get('/googleurl', (req, res) => {
        return res.status(200).send({
          result: { googleURLToken: url }
        });
     });
    

    To get tokens using the authentication code and save these in the database.

    const Setting = require('../models/setting'); // My model(Mongoose)
    
    router.post('/googletoken', (req, res) => {
        oauth2Client.getToken(req.body.code, function (err, tokens) {
          oauth2Client.credentials = tokens;
          // If refresh token exits save it
          // because the refresh token it returned only 1 time! IMPORTANT
          if (tokens.hasOwnProperty('refresh_token')) {
    
            let setting = new Setting();
            setting.refreshTokenGoogle = tokens.refresh_token;
            setting.expirationTokenGoogle = tokens.expiry_date;
            setting.tokenGoogle = tokens.access_token;
    
            setting.save()
              .then((settingCreated) => {
                  return res.status(200).send({
                      message: 'OK'
                  });
               })
          }
        });
    });
    

    To print

    const axios = require('axios');
    const moment = require('moment');
    
    router.post('/print',async (req, res) => {
    
        const tickeProperties = {
          'version': '1.0',
          'print': {
            'vendor_ticket_item': [],
            'color': { 'type': 'STANDARD_MONOCHROME' },
            'copies': { 'copies': 1 }
          }
        };
    
        const accessToken = await getTokenGoogleUpdated();
        axios.get(
          'https://www.google.com/cloudprint/submit',
          {
            params: {
              printerid : printerID, // Replace by your printer ID
              title: 'title printer',
              ticket: tickeProperties,
              content : 'print this text of example!!!',
              contentType: 'text/plain'
            },
            headers: {
              'Authorization': 'Bearer ' + accessToken
            }
          }
        )
          .then(response => {
            return res.status(200).send({
              result: response.data
            });
          })
      }
    );
    
    async function getTokenGoogleUpdated() {
    
      return await Setting.find({})
        .then(async setting => {
          const refreshTokenGoogle = setting[0].refreshTokenGoogle;
          const expirationTokenGoogle = setting[0].expirationTokenGoogle;
          const tokenGoogle = setting[0].tokenGoogle;
    
          const dateToday = new Date();
          // 1 minute forward to avoid exact time
          const dateTodayPlus1Minute = moment(dateToday).add(1, 'm').toDate();
          const dateExpiration = new Date(expirationTokenGoogle);
    
          // Case date expiration, get new token
          if (dateExpiration < dateTodayPlus1Minute) {
            console.log('Updating access token');
            oauth2Client.credentials['refresh_token'] = refreshTokenGoogle;
            return await oauth2Client.refreshAccessToken( async function(err, tokens) {
              // Save new token and new expiration
              setting[0].expirationTokenGoogle = tokens.expiry_date;
              setting[0].tokenGoogle = tokens.access_token;
    
              await setting[0].save();
    
              return tokens.access_token;
            });
    
          } else {
            console.log('Using old access token');
            return tokenGoogle;
          }
    
        })
        .catch(err => {
          console.log(err);
        });
    
    }
    

    I hope It helps you if you want to use Google Cloud Print to not waste a lot of time as I did.