dk.
dk.

Reputation: 2080

How do you implement Google authentication in a shiny R app?

I wanted to provide basic authentication for my shiny app and ideally I didn't want to deal with the login protocol or password management. I wanted my app to provide a public view of data, but if a user authenticated then personalized data sets would be available. In the best case, I wanted to use a federated login such as Google.

One solution is to use a proxy (auth0, shinyProxy). This is a heavyweight solution that requires running additional services. In addition, there is no easy way to communicate the user login with the shiny server, which was my primary goal. And the proxy does not allow for the app to run in non-authenticated mode, which I require.

Another solution is to hand-craft a relatively simple username/password interface using javascript. Here is an example. But I'd rather not manage username/password authentication manually. In my app, I want to allow anybody to use the app, but I just need a unique ID for each user for a personalized experience.

So if I want to use Google's authentication, then a third approach is to use GoogleAuthR. But I found that the library was problematic because I couldn't get it to support persistent login. Like other apps that use a federated login, I wanted the user to return to the URL later and still be connected.

Upvotes: 7

Views: 3618

Answers (2)

Brunox13
Brunox13

Reputation: 843

For me, the GoogleAuthR package actually worked just great. I used the combination of googleAuth (server) and googleAuthUI (ui) functions. For more options, see the following updated article:

Google authentication types for R

This will help you obtain a token, from which you can extract user information using functions from the gargle library:

Get info from a token

You can also use this token for other Google-related functions, such as accessing Google Drive (using googledrive library) or editing Google Sheets (using googlesheets4 library), for example.

Upvotes: 1

dk.
dk.

Reputation: 2080

My solution was to use the Google Sign-In API, write a very small amount of javascript, and use the js function Shiny.onInputChange to create reactive variables from the user data.

The Sign-In API provides a button, does not require a secret, and allows the client ID and scope to be specified in meta tags in the HTML HEAD, so it's very easy to use.

In app.R I simply add the Google API code, scope, client ID, and a login button like this.

ui <- tagList(
  tags$head(
    tags$meta(name="google-signin-scope",content="profile email"),
    tags$meta(name="google-signin-client_id", content="YOURCLIENTID.apps.googleusercontent.com"),
    HTML('<script src="https://apis.google.com/js/platform.js?onload=init"></script>'),
    includeScript("signin.js"),
  ),
  fluidPage(

    titlePanel("Sample Google Sign-In"),

    sidebarLayout(
      sidebarPanel(
        div(id="signin", class="g-signin2", "data-onsuccess"="onSignIn"),
        actionButton("signout", "Sign Out", onclick="signOut();", class="btn-danger")

Note that Google's API will turn the signin div into a button and the data-onsuccess parameter names the function onSignIn to call upon successful authentication. Conveniently this is called whether the user is automatically logged in or actually going through the Google approval process.

There is also a signOut function which invalidates local cookies and also nullifies the profile data.

In a separate file signin.js I defined the callback function onSignIn that sends the user profile info to the shiny server from the client.

function onSignIn(googleUser) {
  var profile = googleUser.getBasicProfile();
  Shiny.onInputChange("g.id", profile.getId());
  Shiny.onInputChange("g.name", profile.getName());
  Shiny.onInputChange("g.image", profile.getImageUrl());
  Shiny.onInputChange("g.email", profile.getEmail());
}
function signOut() {
  var auth2 = gapi.auth2.getAuthInstance();
  auth2.signOut();
  Shiny.onInputChange("g.id", null);
  Shiny.onInputChange("g.name", null);
  Shiny.onInputChange("g.image", null);
  Shiny.onInputChange("g.email", null);
}

That's about it. Your UI and server just need to add code to access the user profile reactives. Here's an example in the UI:

 mainPanel(
        with(tags, dl(dt("Name"), dd(textOutput("g.name")),
                      dt("Email"), dd(textOutput("g.email")),
                      dt("Image"), dd(uiOutput("g.image")) ))
      )

And in the server:

server <- function(input, output) {
  output$g.name = renderText({ input$g.name })
  output$g.email = renderText({ input$g.email })
  output$g.image = renderUI({ img(src=input$g.image) })

I put together a working example that you can run (with a very small bit of contortion - you must specify port 7445 and use localhost). Please read the README for more details.

Upvotes: 9

Related Questions