6

I'm trying to create an shiny app that first do the authorization with OAuth (see https://developers.google.com/identity/sign-in/web/sign-in) and then takes a token and use is to access a google sheet using googlesheets4 library. Sign-in process works correct, meaning I can see my email (and other data from googleUser.getBasicProfile(). It seems though that no id_token is returned. Minimal example:

app.R

library(shiny)
library(shinyjs)
library(googlesheets4)

shinyApp(
  ui = fluidPage(
    useShinyjs(),  
    tags$head(tags$script(src="https://apis.google.com/js/platform.js")),
    tags$meta(name = "google-signin-scope", content = "profile email"),
    tags$meta(name = "google-signin-client_id", content = "<CLIENT_ID>.apps.googleusercontent.com"),
    includeScript("signin.js"),
    div(id = "signin", class = "g-signin2", "data-onsuccess" = "onSignIn"),
    actionButton("signout", "Sign Out", onclick="signOut();", class="btn-danger"),

    with(tags, dl(dt("Email"), dd(textOutput("g.email")),
                  dt("Token"), dd(textOutput("g.id_token"))
                  )),

    tableOutput("df")
  ),


  server = function(input, output) {

    output$g.email = renderText({ input$g.email }) # here my email is printed in the app
    output$g.id_token = renderText({ input$g.id_token}) # no id_token available?

    output$df <- renderTable({
      sheets_auth(token = input$g.id_token)
      read_sheet("<GOOGLE_SHEET_ID>")
      })
  }
)

and singin.js

function onSignIn(googleUser) {
  var profile = googleUser.getBasicProfile();
  Shiny.onInputChange("g.email", profile.getEmail());
  var id_token = googleUser.getAuthResponse().id_token;
  Shiny.onInputChange("g.id_token", id_token);
}

function signOut() {
  var auth2 = gapi.auth2.getAuthInstance();
  auth2.signOut();
  Shiny.onInputChange("g.email", null);       
  Shiny.onInputChange("g.id_token", null);
}

How can I access id_token and pass it to sheets_auth function?

jjankowiak
  • 3,010
  • 6
  • 28
  • 45

2 Answers2

3

Execute the below commands in the browser console to verify whether it returns actual token .

  console.log(JSON.stringify(gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse()));

{ "token_type":"Bearer", "login_hint":"<Huge mess of letters>", "expires_in":2112, "id_token":"<insert your ridiculously long string here>",...}

One another log to Verify using true flag :

console.log(JSON.stringify(gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse(true)));

    { "token_type":"Bearer", "access_token":"<an actual access token goes here>", "scope":"<whatever scopes you have authorized>", "login_hint":"<another mess of letters>", "expires_in":2112, "id_token":"<Insert your ridiculously long string here>", ...}

Double Check : Sometimes if we dont send the scope , The Token id Might not be present , You can fix that through

gapi.auth2.init({
  client_id: <googleClientID>,
  'scope': 'https://www.googleapis.com/auth/plus.login'
})

Source : GoogleUser.getAuthResponse() doesn't contain access_token

redhatvicky
  • 1,912
  • 9
  • 8
1

Your main question is: "How can I access id_token and pass it to sheets_auth function?"

I'm pretty sure that the googlesheets4 library is designed to handle the token management in the background (e.g., you shouldn't need to handle it directly). In fact the documentation (?sheets_token) says: "Most users do not need to handle tokens "by hand" or, even if they need some control, sheets_auth() is what they need."

That said, if you want to handle the token, I made a trimmed down version of your app that captures the token for subsequent use (I don't have enough experience with shinyjs to modify that part of app, so I cut that out to make a minimally working app).

The version below "works" for me, but maybe as a consequence of cutting out your JavaScript, I had to respond to some googlesheets4 questions in the console to complete the authorization process (which is obviously undesirable for this UI design).

Once the authorization process completes, at least on my end, the app performs all of the tasks you set out to accomplish in your minimal example. Namely, it: 1) displays the authenticated email, 2) Displays the id_token, and 3) reads and then displays the first spreadsheet it finds in the account.

library(shiny)
library(googlesheets4)

ui = fluidPage(
    actionButton("signin", "Sign In"),
    with(tags, dl(dt("Email: "), dd(textOutput("g.email")),
                  dt("Token: "), dd(textOutput("g.id_token"))
    )),
    tableOutput("df")
)

server = function(input, output) {
    # Listen for input$send changes (i.e. when the button is clicked)
    observe({ 
        input$signin
        sheets_auth()
        # TODO: Add error handling.
        # You don't need to capture the token in order to read a Google Sheet.
        # Just capturing it here to enable the display/renderText portions of the app.
        token = sheets_token() 
    })

    output$g.email <- renderText({
        # Here my email is printed in the app
        if(sheets_has_token()){
            token$auth_token$email   
        }
    }) 

    output$g.id_token <- renderText({
        # Here my id_token is printed in the app
        if(sheets_has_token()){
            token$auth_token$credentials$id_token   
        }
    }) 

    output$df <- renderTable({
        if(sheets_has_token()){
            my_sheets <- sheets_find()
            read_sheet(my_sheets$id[1])
        }
    })
}

shinyApp(ui = ui, server = server)
D. Woods
  • 3,004
  • 3
  • 29
  • 37