36

The problem

I have a shiny application that requires user to login to access the underlying dataset.

The username and password are collected from form fields and submitted via a RESTful API on another server to get an access token which is used in future requests. I haven't included those details in this example.

enter image description here

I want to get the web browser to remember the login details and login automatically, but can't quite see how.

What I tried

In my first implementation, the <input type="text" name="username"> and <input type="password" name="password"> plus an actionButton were dynamically added to the page. This forced the user to re-login each time the shiny application was restarted, which was complex for development and unpleasant for users.

I then found https://gist.github.com/kostiklv/968927 which details the conditions for having the web browser remember the password and log in.

Shiny works on one page, so I couldn't have a separate login page (this would generate a new shiny session, discarding the login!).

I have tried various combinations of things, here is the closest I have got. This script correctly fills in a past password, but the user still has to manually hit submit.

Example code

Note that I have a custom hack my shiny in to enable type="password" to be treated the same as type="text". See bottom of question for access to this code

save this code as login.R then run R and type source("login.R")

write("","blank.html")
require(shiny)
addResourcePath("login",tools:::file_path_as_absolute("./"))
runApp(list(
  ui=bootstrapPage(
    tags$form(action="#",target="loginframe",
      tags$input(id="username",type="text",placeholder="Username"),
      tags$input(id="password",type="password",placeholder="Password"),
      tags$input(id="loginButton",class="btn btn-primary",type="submit",value="Login")
    ),
    tags$iframe(name="loginframe",src="login/blank.html",style="display:none")
  ),
  server=function(input, output) {
    observe({message(
        "username ",input$username,"\n",
        "password ",input$password,"\n"
    )})
  })
)

Analysis of what happened

Note that the presence of an html input submit button seems to tell shiny that it should only update the inputs once every submit. Without the submit button shiny updates its inputs every keystroke.

The same is true for shiny's submitButton.

Leaving the form but removing the button allows input$username and input$password to reach the server code, but...

To enable both conventional user login and automated login, I need the button to avoid repeated attempts to authenticate (which might cause problems) for partial usernames and passwords.

Log of what happened

Here are the console logs from the tests

Log with a submit button:

username 
password 
<enter username AA and password BB>
*no update*
<hit submit>
*browser requests to save password / yes*
username A
password B
<refresh page>
username 
password
* onscreen form inputs are populated *
<hit submit>
username A
password B

Log without a submit button

Comment out tags$input(id="loginButton",class="btn btn-primary",type="submit",value="Login") and the preceding comma

note you may want to tell your browser to forget localhost web passwords for now.

username
password
<enter username AA and password BB>
username A
password
username AA
password
username AA
password B
password BB
<refresh browser>
* onscreen form inputs are not populated *
* browser requests to save password *
username
password
<refresh browser>
* onscreen form inputs are populated *
username
password
username AA
username BB

Shiny patch to enable password handling:

Install using library(devtools)

install_github("shiny","alexbbrown",ref="password-field")

Or download and install manually:

https://github.com/alexbbrown/shiny.git (branch password-field)

Or patch your shiny manually:

index 9b63c7b..15377d8 100644
--- a/inst/www/shared/shiny.js
+++ b/inst/www/shared/shiny.js
@@ -1336,7 +1336,7 @@
   var textInputBinding = new InputBinding();
   $.extend(textInputBinding, {
     find: function(scope) {
-      return $(scope).find('input[type="text"]');
+      return $(scope).find('input[type="text"],input[type="password"]');
     },
     getId: function(el) {
       return InputBinding.prototype.getId.call(this, el) || el.name;

cf Password field using R Shiny framework

Community
  • 1
  • 1
Alex Brown
  • 41,819
  • 10
  • 94
  • 108
  • It seems like I'm not getting a 'click'. any ideas? – Alex Brown Jan 14 '14 at 20:35
  • 1
    Have you considered adding authentication in top of shiny (using nginx or apache, or node.js) ? It is easy, robust and well tested – Karl Forner Apr 08 '14 at 14:40
  • functionality you are talking about is related to cookie management . You can use javascript for this – igauravsehrawat Jul 14 '14 at 09:52
  • Yeah, so, what Karl Says. When I ran a shiny server, I used authentication via Apache. http://httpd.apache.org/docs/2.2/howto/auth.html. Aside from that, it sounds like you are trail blazing. – mgriebe Aug 13 '14 at 05:19

2 Answers2

7

I was looking for something similar, and trestletech's fantastic package shinyStore worked for me.

It enables HTML5 client-side data storage for any data in your shiny app.

The github page is here, and an example of how it works is here.

You may wish to read the "Security" section on the README and ensure it is secure enough for your purposes.

Hamilton Blake
  • 622
  • 1
  • 6
  • 19
2

I believe that you can use postForm() from RCurl package to perform a regular HTTP form submit request (unless Shiny would interfere with this functionality, which is based on using underlying libcurl library). For an example code, please see function srdaLogin() in my R module "getSourceForgeData.R": https://github.com/abnova/diss-floss/blob/master/import/getSourceForgeData.R.

Aleksandr Blekh
  • 2,462
  • 4
  • 32
  • 64