Google Drive Picker API asking for consent everytime even in the same session in authorization code flow approach

96 views Asked by At

The client side implementation is currently this

 function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: CLIENT_ID,
          scope: SCOPES,
          ux_mode: "popup",
          callback: async (response) => {
            var code_receiver_uri = "http://localhost:5000/oauth2callback";
            try {
              const { code, scope } = response; //
              const fetchResponse = await fetch(code_receiver_uri, {
                method: "POST",
                headers: {
                  "Content-Type": "application/x-www-form-urlencoded",
                },
                body: `code=${code}&scope=${scope}`,
              });
              result = await fetchResponse.json();
              accessToken = result.token;
              console.log(result);

              document.getElementById("signout_button").style.visibility =
                "visible";
              document.getElementById("authorize_button").innerText = "Refresh";
              await createPicker();
            } catch (error) {
              console.error("Error making request: ", error);
            }
          },
        });
        gisInited = true;
        maybeEnableButtons();
      }

      function gapiLoaded() {
        gapi.load("client:picker", initializePicker);
      }

      async function initializePicker() {
        await gapi.client.load(
          "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"
        );
        pickerInited = true;
        maybeEnableButtons();
      }

      function maybeEnableButtons() {
        if (pickerInited && gisInited) {
          document.getElementById("authorize_button").style.visibility =
            "visible";
        }
      }

      function createPicker() {
        // const view = new google.picker.View(google.picker.ViewId.DOCS);
        // view.setMimeTypes("image/png,image/jpeg,image/jpg");
        const picker = new google.picker.PickerBuilder()
          .enableFeature(google.picker.Feature.NAV_HIDDEN)
          .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
          .setDeveloperKey(API_KEY)
          .setAppId(APP_ID)
          .setOAuthToken(accessToken)
          .addView(new google.picker.DocsView()
          .setIncludeFolders(true)
          .setMimeTypes('application/pdf')
          .setOwnedByMe(true))
          .setCallback(pickerCallback)
          .build();
        picker.setVisible(true);
      }

      async function getAccessToken() {
          if (!accessToken || isAccessTokenExpired())
          {
            // Using refresh token to get the new access token 
            accessToken = await refreshAccessToken();
          }
          return accessToken;
      }
      // function to check if access token is expired or not 
      function isAccessTokenExpired()
      { 
        const fs = require('fs');

        // Read the contents of the JSON file
        const filePath = '../credentials.json';
        const fileContent = fs.readFileSync(filePath, 'utf-8');

        // Parse the JSON content
        const credentials = JSON.parse(fileContent);

        // Access the accessTokenExpirationTime from the credentials
        const accessTokenExpirationTime = credentials.expiry;
        // console log expiration time 
        console.log('access token expiration time', accessTokenExpirationTime)

        const currentTimestamp = Math.floor(Date.now()/1000);

        if (accessTokenExpirationTime <= currentTimestamp)
        {
          return True
        }
        else 
        {
          return False
        }
      }

    async function refreshAccessToken(){
        const response = await fetch("http://localhost:5000/get-token", {
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
        body: `refresh_token=${refreshToken}`,
    });

    const result = await response.json();
    return result.token;
      }

      async function pickerCallback(data) {
        console.log("Here in picker callback!");
        if (data.action === google.picker.Action.PICKED) {
          const documents = data[google.picker.Response.DOCUMENTS];
          console.log("here in documentsssss");
          const documentPayload = documents.map((document) => ({
            fileId: document.id,
            fileName: document.name,
          }));
          console.log("Documents: ", documentPayload);
          try {
            console.log(JSON.stringify(documentPayload));
          } catch (error) {
            console.warn(error);
          }

          const download_endpoint = "http://localhost:5000/gdrive/download";
          const response = await fetch(download_endpoint, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(documentPayload),
          });

          console.log("Request sent......");
          const result = await response.json();
          console.log(result);
        }
      }

      function handleAuthClick() {
        if (accessToken)
        {
        client.requestCode();
      }
    </script>
    <script
      async
      defer
      src="https://apis.google.com/js/api.js"
      onload="gapiLoaded()"
    ></script>

    <script
      src="https://accounts.google.com/gsi/client"
      onload="initClient()"
      async
      defer
    ></script>

and server side implementation is currently this

import os

from flask import Flask, request, url_for, render_template
from flask_cors import CORS
from google_auth_oauthlib.flow import Flow
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request


app = Flask(__name__)
CORS(app)

app.secret_key = "c8a8fe7b5ace3e7c662c202ce4910834"


# @app.route("/oauth2callback", methods=["POST"])
# def oauth_endpoint():
#     authorization_code = request.form.get("code")
#     scopes = request.form.get("scope").split()

#     flow = Flow.from_client_secrets_file(
#         "client_secret.json",
#         scopes=scopes,
#         redirect_uri="postmessage",
#     )
# authorization_url, state = flow.authorization_url(
#     # Enable offline access so that you can refresh an access token without
#     # re-prompting the user for permission. Recommended for web server apps.
#     access_type='offline',
#     # Enable incremental authorization. Recommended as a best practice.
#     include_granted_scopes='true')

#     print(url_for("oauth_endpoint", _external=True))
#     flow.fetch_token(code=authorization_code)
#     credentials = flow.credentials
#     with open("../credentials.json", "w") as creds:
#         creds.write(credentials.to_json())
#     print("credentials written, returning")
#     return {
#         "token": credentials.token,
#         "refresh_token": credentials.refresh_token,
#         "token_uri": credentials.token_uri,
#         "client_id": credentials.client_id,
#         "client_secret": credentials.client_secret,
#         "scopes": credentials.scopes,
#     }



@app.route("/oauth2callback", methods=["POST"])
def oauth_endpoint():
    authorization_code = request.form.get("code")
    scopes = request.form.get("scope").split()

    # Load existing credentials or create a new flow if none exist
    credentials_file = "../credentials.json"
    if os.path.exists(credentials_file):
        print('credentials')
        credentials = Credentials.from_authorized_user_file(credentials_file, scopes)
    else:
        flow = Flow.from_client_secrets_file(
        "credentials.json",
        scopes=scopes,redirect_uri="postmessage",
        )
        authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')
        credentials = flow.run_local_server(port=0)

    # Check if the credentials are expired, and refresh if necessary
    if credentials.expired and credentials.refresh_token:
        credentials.refresh(Request())

        # Save the updated credentials back to the file
        with open(credentials_file, "w") as creds:
            creds.write(credentials.to_json())

    return {
        "token": credentials.token,
        "refresh_token": credentials.refresh_token,
        "token_uri": credentials.token_uri,
        "client_id": credentials.client_id,
        "client_secret": credentials.client_secret,
        "scopes": credentials.scopes,
    }

# implement a function to retrieve refresh token 
def retrieve_refresh_token():
    credentials_file = "../credentials.json"
    if os.path.exists(credentials_file):
        credentials = Credentials.from_authorized_user_file(credentials_file)
        return credentials.refresh_token
    else:
        return None

@app.route("/get-token", methods=['POST'])
def refresh_access_token():
    refresh_token=retrieve_refresh_token()
    credentials= Credentials.from_client_secrets_file('client_secret.json', refresh_token=refresh_token)
    if credentials.expired:
        credentials.refresh(Request())
    return credentials.token


def download(id, name):
    credentials = Credentials.from_authorized_user_file("../credentials.json")
    try:
        service = build("drive", "v3", credentials=credentials)
        print("service initialized")
        media_request = service.files().get_media(fileId=id)
        # file = io.BytesIO()
        downloadPath = os.path.join("../downloads/", name)
        print("downloadPath: ", downloadPath)
        with open(downloadPath, "wb") as file:
            downloader = MediaIoBaseDownload(file, media_request)
            done = False
            while done is False:
                status, done = downloader.next_chunk()
                print(f"Download {int(status.progress() * 100)}.")
    except:
        raise


@app.route("/gdrive/download", methods=["POST"])
def gdrive_download():
    print("here in download file")
    request_files = request.get_json()

    failed_files = []
    successful_files = []

    for file in request_files:
        file_id = file.get("fileId")
        file_name = file.get("fileName")
        try:
            download(file_id, file_name)
            successful_files.append(file_name)
        except Exception as e:
            print(e)
            failed_files.append(file_name)
    response_data = {"successful": successful_files, "failed": failed_files}

    return response_data, 201


@app.route("/")
def home():
    return render_template('implicitindex.html')

When I launch the picker it succesfully executes and even selects the files succesfully but the problem is everytime I want to select new file it asks for sign in and consent everytime even in the same session with a valid access token. What could be the solution ??

I have tried several approaches present in GIS platform and could not get actual implementation of both server and client side for authorization code flow approach.

0

There are 0 answers