69

I know that in Shiny Server Pro there is a function of password control. The question is that Shiny has function passwordInput(), which is like textInput() Has anybody thought about how to do the following:

1) Launching the application only after correct password input 2) Launching the part of application after correct password input (for example, I have some tabs in shinydashboard, and I want to make an acces to one of them only by password)

Thanks!

  • 1
    You might want to have a look at the open source solution [shinyproxy](https://www.shinyproxy.io/) Using shinyproxy you will have to wrap your apps in a docker container to host them. There are different authentication and scaling methods available. – ismirsehregal Dec 06 '18 at 08:24

6 Answers6

107

EDIT 2019: We can now use the package shinymanager to do this: the invactivity script is to timeout the login page after 2 mins of inactivity so you dont waste resources:

library(shiny)
library(shinymanager)

inactivity <- "function idleTimer() {
var t = setTimeout(logout, 120000);
window.onmousemove = resetTimer; // catches mouse movements
window.onmousedown = resetTimer; // catches mouse movements
window.onclick = resetTimer;     // catches mouse clicks
window.onscroll = resetTimer;    // catches scrolling
window.onkeypress = resetTimer;  //catches keyboard actions

function logout() {
window.close();  //close the window
}

function resetTimer() {
clearTimeout(t);
t = setTimeout(logout, 120000);  // time is in milliseconds (1000 is 1 second)
}
}
idleTimer();"


# data.frame with credentials info
credentials <- data.frame(
  user = c("1", "fanny", "victor", "benoit"),
  password = c("1", "azerty", "12345", "azerty"),
  # comment = c("alsace", "auvergne", "bretagne"), %>% 
  stringsAsFactors = FALSE
)

ui <- secure_app(head_auth = tags$script(inactivity),
                 fluidPage(
                   # classic app
                   headerPanel('Iris k-means clustering'),
                   sidebarPanel(
                     selectInput('xcol', 'X Variable', names(iris)),
                     selectInput('ycol', 'Y Variable', names(iris),
                                 selected=names(iris)[[2]]),
                     numericInput('clusters', 'Cluster count', 3,
                                  min = 1, max = 9)
                   ),
                   mainPanel(
                     plotOutput('plot1'),
                     verbatimTextOutput("res_auth")
                   )
                   
                 ))

server <- function(input, output, session) {
  
  result_auth <- secure_server(check_credentials = check_credentials(credentials))
  
  output$res_auth <- renderPrint({
    reactiveValuesToList(result_auth)
  })
  
  # classic app
  selectedData <- reactive({
    iris[, c(input$xcol, input$ycol)]
  })
  
  clusters <- reactive({
    kmeans(selectedData(), input$clusters)
  })
  
  output$plot1 <- renderPlot({
    palette(c("#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",
              "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"))
    
    par(mar = c(5.1, 4.1, 0, 1))
    plot(selectedData(),
         col = clusters()$cluster,
         pch = 20, cex = 3)
    points(clusters()$centers, pch = 4, cex = 4, lwd = 4)
  })
  
}


shinyApp(ui = ui, server = server)

enter image description here

Original Post: I am going to answer #1 and for #2 you can simply expand on my example. Following this example Encrypt password with md5 for Shiny-app. you can do the following:

  1. Create 2 pages and if the user inputs the correct username and password you can renderUI and use htmlOutput to output your page
  2. You can style the position of the box with username and password with tagsas I did and color them if you want also using tags$style

You can then further look into the actual page and specify what should be created as a result of different users. You can also look into JavaScript Popup Boxes

EDIT 2018: Also have a look at the example here https://shiny.rstudio.com/gallery/authentication-and-database.html

Example of front page

rm(list = ls())
library(shiny)

Logged = FALSE;
my_username <- "test"
my_password <- "test"

ui1 <- function(){
  tagList(
    div(id = "login",
        wellPanel(textInput("userName", "Username"),
                  passwordInput("passwd", "Password"),
                  br(),actionButton("Login", "Log in"))),
    tags$style(type="text/css", "#login {font-size:10px;   text-align: left;position:absolute;top: 40%;left: 50%;margin-top: -100px;margin-left: -150px;}")
  )}

ui2 <- function(){tagList(tabPanel("Test"))}

ui = (htmlOutput("page"))
server = (function(input, output,session) {
  
  USER <- reactiveValues(Logged = Logged)
  
  observe({ 
    if (USER$Logged == FALSE) {
      if (!is.null(input$Login)) {
        if (input$Login > 0) {
          Username <- isolate(input$userName)
          Password <- isolate(input$passwd)
          Id.username <- which(my_username == Username)
          Id.password <- which(my_password == Password)
          if (length(Id.username) > 0 & length(Id.password) > 0) {
            if (Id.username == Id.password) {
              USER$Logged <- TRUE
            } 
          }
        } 
      }
    }    
  })
  observe({
    if (USER$Logged == FALSE) {
      
      output$page <- renderUI({
        div(class="outer",do.call(bootstrapPage,c("",ui1())))
      })
    }
    if (USER$Logged == TRUE) 
    {
      output$page <- renderUI({
        div(class="outer",do.call(navbarPage,c(inverse=TRUE,title = "Contratulations you got in!",ui2())))
      })
      print(ui)
    }
  })
})

runApp(list(ui = ui, server = server))
Pork Chop
  • 28,528
  • 5
  • 63
  • 77
  • 1
    Why you use `<<-` for `USER <<- reactiveValues(Logged = Logged)` ? i think for reactive enought `<-` – Batanichek Feb 16 '16 at 11:13
  • 1
    @Batanichek I think at the time I followed some example, I changed it, thanks – Pork Chop Feb 16 '16 at 11:33
  • Hello @Pork Chop, thanks for this awesome example! I got this working very well. I would like to ask you if there is a way to print the ID when the users shuts the application off. I thought you might know this :)? Thank you very much. – M. Kooi Aug 23 '16 at 12:01
  • @M.Kooi the ID of what the user? you already have the username `input$userName` so just store that – Pork Chop Aug 23 '16 at 12:42
  • @PorkChop Yes a user. When a user passes the login screen I got this command: `print(paste(isolate(input$userName), Sys.time()))`. Now I would like to know when the user shut the application down, but before he shuts the application down I would like to print once again `print(paste(isolate(input$userName), Sys.time()))` – M. Kooi Aug 23 '16 at 12:48
  • try: `session$onSessionEnded(function() { print(paste(isolate(input$userName), Sys.time())) })` – Pork Chop Aug 23 '16 at 13:07
  • @PorkChop Thanks for the great code. I'm just a little confused. Where do I write my usual 'server' code to fill the "Test" Page? After the 'print(ui)' and before the closing '}' of the if-statement? Or in the 'renderUI' statement? I admit my experience with shiny is limited. – PhiSeu Oct 14 '16 at 16:02
  • 2
    @PhiSeu, u write it in `server`, the idea here is that a different `ui` will be shown to users, you can still code anything you want within the `server = (function(input, output,session) {` block – Pork Chop Oct 14 '16 at 16:14
  • 1
    @PorkChop Thank you. I think I was shortly stuck in a thought. It works now. – PhiSeu Oct 16 '16 at 16:45
  • so how can add my current ui and server script in this script ? ui2 <- function(){shinyUI(fluidpage(...my code...)} like this ? and where can i add the server script in it ? please advise – Peter Chung Jan 08 '18 at 08:58
  • @PeterChung both `server` and `ui` are in one script as above, if you need specifics please create a question – Pork Chop Jan 08 '18 at 09:35
  • @PorkChop thanks. But I mean how can I put my script inside this login script ? – Peter Chung Jan 08 '18 at 09:37
  • Hi @PorkChop! This piece of code doesn't work when I want to embed ggvis plots in it, but works for ggplot2 plots. I have to embed ggvis plots though, any pointers? – Parisa Rai Jan 11 '18 at 07:05
  • @PorkChop, looks like you updated the answer with a Github link, I tried working on that example and it did not work. Can you look into it ? – user5249203 Feb 21 '18 at 19:24
  • @PorkChop Hi, great example. How to make this one for `shinydashboard`? – jyjek May 07 '18 at 14:19
  • The above link provided to the [gallery example](https://shiny.rstudio.com/gallery/authentication-and-database.html) is not fully reproducible. But in the GitHub [comments](https://gist.github.com/trestletech/9793754) library([shinyauthr](https://github.com/paulc91/shinyauthr)) is mentioned. Here is an [example](https://cultureofinsight.shinyapps.io/shinyauthr/) using shinydashboard. – ismirsehregal Dec 06 '18 at 08:06
  • @ismirsehregal, the example doesnt show clean login page, its still a part of the `shinydashboard` which isnt really desirable, there needs to be a separate page for login – Pork Chop Dec 06 '18 at 08:47
  • Thats why I suggested using https://www.shinyproxy.io/ in another comment right below the question (above my comment here it was asked for a shinydashboard solution). – ismirsehregal Dec 06 '18 at 08:58
  • yeah shinyproxy is better – Pork Chop Dec 06 '18 at 09:38
  • @PorkChop This seems to work except that it does not tell the user if they inputted an incorrect user name or password. How would you go about including that? – road_to_quantdom Jan 28 '19 at 23:47
  • @road_to_quantdom, You can add the validations yourself using `need` and `validate` – Pork Chop Jan 29 '19 at 08:16
  • @PorkChop. I have a shiny app embedded in another website ( built with angular js/Java), which has its own Oauth authentication. Now, When a user logs in into the main application, my shiny app needs identify the user authenticate him and then display the dashboard with the data he is allowed to view. Is this possible to do, if so can you give some leads on how to approach this – user5249203 Feb 12 '21 at 15:33
  • @user5249203, yes you can do that, but u will need to have or setup a Single Sign On across your infrastructure, using nginx or apache. Then you can vlidate the authentication of the users for the app with a simple text file, to be located in the `ShinyApps` directory such as `.app1.auth' then you will have daemon which runs and parses that file and sees who should be accessing the app – Pork Chop Feb 12 '21 at 19:14
  • Thank you for your response. My app is hosted on the Rstudio Connect, not a custom shiny server. Would the same steps apply ? – user5249203 Feb 12 '21 at 20:15
  • @PorkChop Is it possible to use `shinymanager` admin module when using any other kind of databases and not only SQLlite? – David Díaz May 20 '21 at 17:48
  • 1
    @DavidDíaz, I didnt write the package, maybe you can request the feature on github https://github.com/datastorm-open/shinymanager – Pork Chop May 20 '21 at 17:55
4

I had to same question, stumbled on the intial answer above (using two different UIs) and found it too difficult for me to implement. Apparently, there have been other users on SO with similar problems to implement the above solution.

I have built a much simpler workaround using append/remove tab and {shinyjs}. Here is how it works. It might help those, who do not want to work with two separate UI functions.

  1. Create a login tab where users can log in. All other tabs are not displayed yet, neither is the sidebar.
  2. When login is successful: Append the tabs you actually want to show, remove the login tab (it's no longer needed) and show sidebar with {shinyjs}.

I provide a simple example below. I further added some features which are not necessary like a user history counting and limiting the number of login attempts, a user log and a message handler etc. I commented those features out to keep things simple, but if you are interested have a look. Please note that the additional features have to be run on a server.

The only downside of not using shiny server pro is the missing https connection, which needs to be added with another work-around if really necessary.

I documented a simple example and an approach with additional features on GitHub. A working version of the latter can be found on shinyapps.io.

Below I post the code of the simpler version of the app focussing on the login itself.

User names and passwords required for the login are as follows:

    username   password
    user123    loginpassword1
    user456    loginpassword2

In a real app they should be stored as hashes on the server.

library("shiny")
library("shinyjs")
library("stringr")


# in case you want to send error messages when login is wrong
# add this to the /www folder in your shiny app (shiny server) as message-handler.js file
#
# // This recieves messages of type "testmessage" from the server.
# Shiny.addCustomMessageHandler("testmessage",
#                               function(message) {
#                                   alert(JSON.stringify(message));
#                               }
# );

shinyApp(

ui = fluidPage(
    
    useShinyjs(),  # Set up shinyjs
    
    # Layout mit Sidebar
    sidebarLayout(
        
        ## Sidebar -----
        shinyjs::hidden(
            div(id = "Sidebar", sidebarPanel(
                
                # > some example input on sidebar -----
                conditionalPanel(
                    condition = "input.tabselected > 1",
                    dateRangeInput(inputId = "date",
                                   label = "Choose date range",
                                   start = "2018-06-25", end = "2019-01-01",
                                   min = "2018-06-25", max = "2019-01-01",
                                   startview = "year")) 
                
            ))), # closes Sidebar-Panel
        
        # Main-Panel ------
        mainPanel(
            
            tabsetPanel(
                
                # > Login -------
                tabPanel("Login",
                         value = 1,
                         br(),
                         textInput("username", "Username"),
                         passwordInput("password", label = "Passwort"),
                         # If you want to add custom javascript messages
                         # tags$head(tags$script(src = "message-handler.js")),
                         actionButton("login", "Login"),
                         textOutput("pwd")
                         
                ), # closes tabPanel
                
                id = "tabselected", type = "pills"
                
            )  # closes tabsetPanel      
            
        )  # closes mainPanel                      
        
    ) # closes sidebarLayout
    
), # closes fluidPage


# Server ------
server = function(input, output, session){
    
    user_vec <- c("user123" = "loginpassword1",
                  "user456" = "loginpassword2")
    
    # I usually do run the code below on a real app  on a server
    # user <- reactiveValues(his = readRDS(file = "logs/user_his.rds"),
    #                        log = readRDS(file = "logs/user_log.rds"),
    #                        vec = readRDS(file = "logs/user_vec.rds"))
    #
    # where user_his is defined as follows
    # user_his <- vector(mode = "integer", length = length(user_vec))
    # names(user_his) <- names(user_vec)
    
    
    observeEvent(input$login, {
        
        if (str_to_lower(input$username) %in% names(user_vec)) { # is username in user_vec?
        
        # Alternatively if you want to limit login attempts to "3" using the user_his file
        # if (str_to_lower(input$username) %in% names(user$vec[user$his < 3])) {
            
            if (input$password == unname(user_vec[str_to_lower(input$username)])) {
            
                # nulls the user_his login attempts and saves this on server
                # user$his[str_to_lower(input$username)] <- 0
                # saveRDS(user$his, file = "logs/user_his.rds")
                
                # Saves a temp log file
                # user_log_temp <- data.frame(username = str_to_lower(input$username),
                #                            timestamp = Sys.time())
                
                # saves temp log in reactive value
                # user$log <- rbind(user$log, user_log_temp)
                
                # saves reactive value on server
                # saveRDS(user$log, file = "logs/user_log.rds")
                
                
                # > Add MainPanel and Sidebar----------
                shinyjs::show(id = "Sidebar")
                
                appendTab(inputId = "tabselected",
                          
                          tabPanel("Tab 1",
                                   value = 2
                                   
                          ) # closes tabPanel,
                          
                )
                
                appendTab(inputId = "tabselected",
                          
                          tabPanel("Tab 2",
                                   value = 3

                          ) # closes tabPanel,
                )
                
                appendTab(inputId = "tabselected",

                          tabPanel("Tab 3",
                                   
                                   value = 4
                                   
                          ) # closes tabPanel         
                )
                
                removeTab(inputId = "tabselected",
                          target = "1")
                
            } else { # username correct, password wrong
                
                # adds a login attempt to user_his 
                # user$his[str_to_lower(input$username)] <- user$his[str_to_lower(input$username)] + 1
                
                # saves user_his on server
                # saveRDS(user$his, file = "logs/user_his.rds")
                
                # Messge which shows how many log-in tries are left
                #
                # session$sendCustomMessage(type = 'testmessage',
                #                           message = paste0('Password not correct. ',
                #                                            'Remaining log-in tries: ',
                #                                            3 - user$his[str_to_lower(input$username)]
                #                           )
                # )
                
                
            } # closes if-clause
            
        } else { #  username name wrong or more than 3 log-in failures 
            
            # Send error messages with javascript message handler
            #
            # session$sendCustomMessage(type = 'testmessage',
            #                           message = paste0('Wrong user name or user blocked.')                          
            # )
            
        } # closes second if-clause
        
    }) # closes observeEvent
    
    
} # Closes server
) # Closes ShinyApp
TimTeaFan
  • 17,549
  • 4
  • 18
  • 39
  • Do you have this implemented on an example app in github to give it a quick try? – entropy Jul 19 '19 at 18:55
  • I don’t have it on github, but it should work if you copy it into RStudio, at least in the simple form without user_log and user_his. Once I have this example somewhere running I will link it here. – TimTeaFan Jul 19 '19 at 19:45
  • 1
    @entropy: I know added links to an example app on GitHub and shinyapp.io. There you can test the app. – TimTeaFan Jul 27 '19 at 23:13
  • 1
    Hey @TimTeaFan, I have a question. **How secure is this method?** I am quite new at this and I know little or nothing about security and autentification. I am looking for a way of adding autentification for shiny apps that will be deployed at shiny server open source. The mentioned package at the first answer `shinymanager` seems quite right but it has a mayor issue, the admin module only works for databases in SQLlite. I had the intention of making able an admin module no matter which kind of database is use and as I stayed before your answer seems pretty good. – David Díaz May 20 '21 at 17:39
  • @DavidDíaz: It depends how you define 'security'. This workaround does not come with a https connection, for this other workarounds exist. Further, the sidebar is "only" hidden and its content can be seen in the DOM inspector. However, the actual tabs should not be visible in the DOM inspector and are only available on the server side. From what I read, shiny's server side is considered to be rather secure, but I am no security expert neither :) I use this approach with server that are not connected to the internet, so security was less of a concern to me. – TimTeaFan May 23 '21 at 20:47
4

Adding onto the first answer, you can also encrypt your password easily by combining shinymanager with the scrypt package. To do so, you can first generate a password and hash it:

library(scrypt)
password <- hashPassword("ice")
password #copy this hashed output and then paste it in your app's code

Now, if we take the earlier example, all you need to do to modify it is make an object that has your hashed value (not the raw) and set the is_hashed_password argument within credentials to TRUE.

You can access the app (user name: 1) without having your raw password stored in the script.

library(shiny)
library(shinymanager)
library(scrypt)

inactivity <- "function idleTimer() {
var t = setTimeout(logout, 120000);
window.onmousemove = resetTimer; // catches mouse movements
window.onmousedown = resetTimer; // catches mouse movements
window.onclick = resetTimer;     // catches mouse clicks
window.onscroll = resetTimer;    // catches scrolling
window.onkeypress = resetTimer;  //catches keyboard actions

function logout() {
window.close();  //close the window
}

function resetTimer() {
clearTimeout(t);
t = setTimeout(logout, 120000);  // time is in milliseconds (1000 is 1 second)
}
}
idleTimer();"

password <- "c2NyeXB0ABAAAAAIAAAAAVYhtzTyvRJ9e3hYVOOk63KUzmu7rdoycf3MDQ2jKLDQUkpCpweMU3xCvI3C6suJbKss4jrNBxaEdT/fBzxJitY3vGABhpPahksMpNu/Jou5"

# data.frame with credentials info
credentials <- data.frame(
  user = c("1", "fanny", "victor", "benoit"),
  password = password,
  is_hashed_password = TRUE,
  # comment = c("alsace", "auvergne", "bretagne"), %>% 
  stringsAsFactors = FALSE
)

ui <- secure_app(head_auth = tags$script(inactivity),
                 fluidPage(
                   # classic app
                   headerPanel('Iris k-means clustering'),
                   sidebarPanel(
                     selectInput('xcol', 'X Variable', names(iris)),
                     selectInput('ycol', 'Y Variable', names(iris),
                                 selected=names(iris)[[2]]),
                     numericInput('clusters', 'Cluster count', 3,
                                  min = 1, max = 9)
                   ),
                   mainPanel(
                     plotOutput('plot1'),
                     verbatimTextOutput("res_auth")
                   )
                   
                 ))

server <- function(input, output, session) {
  
  result_auth <- secure_server(check_credentials = check_credentials(credentials))
  
  output$res_auth <- renderPrint({
    reactiveValuesToList(result_auth)
  })
  
  # classic app
  selectedData <- reactive({
    iris[, c(input$xcol, input$ycol)]
  })
  
  clusters <- reactive({
    kmeans(selectedData(), input$clusters)
  })
  
  output$plot1 <- renderPlot({
    palette(c("#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",
              "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"))
    
    par(mar = c(5.1, 4.1, 0, 1))
    plot(selectedData(),
         col = clusters()$cluster,
         pch = 20, cex = 3)
    points(clusters()$centers, pch = 4, cex = 4, lwd = 4)
  })
  
}


shinyApp(ui = ui, server = server)
J.Sabree
  • 2,280
  • 19
  • 48
3

for Shiny dashboard this may help too

library(shiny)
library(shinydashboard)
library(DT)
library(shinyjs)
library(sodium)

# Main login screen
loginpage <- div(id = "loginpage", style = "width: 500px; max-width: 100%; margin: 0 auto; padding: 20px;",
                 wellPanel(
                   tags$h2("LOG IN", class = "text-center", style = "padding-top: 0;color:#333; font-weight:600;"),
                   textInput("userName", placeholder="Username", label = tagList(icon("user"), "Username")),
                   passwordInput("passwd", placeholder="Password", label = tagList(icon("unlock-alt"), "Password")),
                   br(),
                   div(
                     style = "text-align: center;",
                     actionButton("login", "SIGN IN", style = "color: white; background-color:#3c8dbc;
                                 padding: 10px 15px; width: 150px; cursor: pointer;
                                 font-size: 18px; font-weight: 600;"),
                     shinyjs::hidden(
                       div(id = "nomatch",
                           tags$p("Oops! Incorrect username or password!",
                                  style = "color: red; font-weight: 600; 
                                            padding-top: 5px;font-size:16px;", 
                                  class = "text-center"))),
                     br(),
                     br(),
                     tags$code("Username: myuser  Password: mypass"),
                     br(),
                     tags$code("Username: myuser1  Password: mypass1")
                   ))
)

credentials = data.frame(
  username_id = c("myuser", "myuser1"),
  passod   = sapply(c("mypass", "mypass1"),password_store),
  permission  = c("basic", "advanced"), 
  stringsAsFactors = F
)

header <- dashboardHeader( title = "Simple Dashboard", uiOutput("logoutbtn"))

sidebar <- dashboardSidebar(uiOutput("sidebarpanel")) 
body <- dashboardBody(shinyjs::useShinyjs(), uiOutput("body"))
ui<-dashboardPage(header, sidebar, body, skin = "blue")

server <- function(input, output, session) {

  login = FALSE
  USER <- reactiveValues(login = login)

  observe({ 
    if (USER$login == FALSE) {
      if (!is.null(input$login)) {
        if (input$login > 0) {
          Username <- isolate(input$userName)
          Password <- isolate(input$passwd)
          if(length(which(credentials$username_id==Username))==1) { 
            pasmatch  <- credentials["passod"][which(credentials$username_id==Username),]
            pasverify <- password_verify(pasmatch, Password)
            if(pasverify) {
              USER$login <- TRUE
            } else {
              shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade")
              shinyjs::delay(3000, shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade"))
            }
          } else {
            shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade")
            shinyjs::delay(3000, shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade"))
          }
        } 
      }
    }    
  })

  output$logoutbtn <- renderUI({
    req(USER$login)
    tags$li(a(icon("fa fa-sign-out"), "Logout", 
              href="javascript:window.location.reload(true)"),
            class = "dropdown", 
            style = "background-color: #eee !important; border: 0;
                    font-weight: bold; margin:5px; padding: 10px;")
  })

  output$sidebarpanel <- renderUI({
    if (USER$login == TRUE ){ 
      sidebarMenu(
        menuItem("Main Page", tabName = "dashboard", icon = icon("dashboard"))
      )
    }
  })

  output$body <- renderUI({
    if (USER$login == TRUE ) {
      tabItem(tabName ="dashboard", class = "active",
              fluidRow(
                box(width = 12, dataTableOutput('results'))
              ))
    }
    else {
      loginpage
    }
  })

  output$results <-  DT::renderDataTable({
    datatable(iris, options = list(autoWidth = TRUE,
                                   searching = FALSE))
  })

}

runApp(list(ui = ui, server = server), launch.browser = TRUE)
Jochem
  • 3,295
  • 4
  • 30
  • 55
Seyma Kalay
  • 2,037
  • 10
  • 22
1

Another easy way to set up a password is just to use req() and set a specific password value:

library(shiny)
if (interactive()) {
  
  ui <- fluidPage(
    passwordInput("password", "Password:"),
    actionButton("go", "Go"),
    verbatimTextOutput("value")
  )
  server <- function(input, output) {
    output$value <- renderText({
      req(input$password == "hi")
      req(input$go)
      isolate(input$password)
    })
  }
  shinyApp(ui, server)
}

J.Sabree
  • 2,280
  • 19
  • 48
1

To answer the additional question on how to implement one of the existing approaches above to {flexdashboard}:

We can make {shinymanager} work with {flexdashboard} following this discussion here. All we need to do is to add some custom css, which is easy in Rmarkdown, since we can just add a css chunk after the setup chunk.

However, the documentation explicitly warns that:

[using {shinymanager} with {flexdashboard}] is not a really secure way because user can overpass the authentification using developper console… Prefer use shiny application with secure_app function.

---
title: "Old Faithful Eruptions"
output: 
  flexdashboard::flex_dashboard
runtime: shiny
---
  
```{r global, include=FALSE}
# load data in 'global' chunk so it can be shared by all users of the dashboard
library(datasets)
library(shinymanager)
data(faithful)

# define credentials 
credentials <- data.frame(
  user = c("shiny", "shinymanager"),
  password = c("123", "12345"),
  stringsAsFactors = FALSE
)
```

```{css}
/* without this css chunk shinymanager wont work */  
  
.panel-auth {
  position: fixed;
  top:0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #FFF;
  opacity: 1;
  z-index: 99997;
  overflow-x: hidden;
  overflow-y: scroll;
}
```

Column {.sidebar}
-----------------------------------------------------------------------
  
Waiting time between eruptions and the duration of the eruption for the
Old Faithful geyser in Yellowstone National Park, Wyoming, USA.

```{r}
selectInput("n_breaks", label = "Number of bins:",
            choices = c(10, 20, 35, 50), selected = 20)

sliderInput("bw_adjust", label = "Bandwidth adjustment:",
            min = 0.2, max = 2, value = 1, step = 0.2)
```

Column
-----------------------------------------------------------------------
  
### Geyser Eruption Duration

```{r}

renderPlot({
  hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks),
       xlab = "Duration (minutes)", main = "Geyser Eruption Duration")
  
  dens <- density(faithful$eruptions, adjust = input$bw_adjust)
  lines(dens, col = "blue")
})


auth_ui(id = "auth")

auth <- callModule(
  module = auth_server,
  id = "auth",
  check_credentials = check_credentials(credentials) # credentials from above 
)
```

To address the add-on question from the comments: It is also possible to use {shinymanager} on different pages. We can also allow different users and passwords per page. The login will only be asked the first time a page is accessed, afterwards it is "unlocked". The trick to make it work is to use different ids when calling the module.

---
title: "Old Faithful Eruptions"
output: 
  flexdashboard::flex_dashboard
runtime: shiny
---
  
```{r global, include=FALSE}
# load data in 'global' chunk so it can be shared by all users of the dashboard
library(datasets)
library(shinymanager)
data(faithful)

# define credentials 
credentials <- data.frame(
  user = c("shiny", "shinymanager"),
  password = c("123", "12345"),
  stringsAsFactors = FALSE
)

credentials2 <- data.frame(
  user = c("shiny", "manager"),
  password = c("123", "45678"),
  stringsAsFactors = FALSE
)
```

```{css}
/* without this css chunk shinymanager wont work */  
  
.panel-auth {
  position: fixed;
  top:0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #FFF;
  opacity: 1;
  z-index: 99997;
  overflow-x: hidden;
  overflow-y: scroll;
}
```

Page 1
===================================== 

Column {.sidebar}
-----------------------------------------------------------------------
  
Waiting time between eruptions and the duration of the eruption for the
Old Faithful geyser in Yellowstone National Park, Wyoming, USA.

```{r}
selectInput("n_breaks", label = "Number of bins:",
            choices = c(10, 20, 35, 50), selected = 20)

sliderInput("bw_adjust", label = "Bandwidth adjustment:",
            min = 0.2, max = 2, value = 1, step = 0.2)
```

Column
-----------------------------------------------------------------------
  
### Geyser Eruption Duration

```{r}

renderPlot({
  hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks),
       xlab = "Duration (minutes)", main = "Geyser Eruption Duration")
  
  dens <- density(faithful$eruptions, adjust = input$bw_adjust)
  lines(dens, col = "blue")
})


auth_ui(id = "auth")

auth <- callModule(
  module = auth_server,
  id = "auth",
  check_credentials = check_credentials(credentials) # credentials from above 
)
```

Page 2
===================================== 

Column {.sidebar}
-----------------------------------------------------------------------
  
Waiting time between eruptions and the duration of the eruption for the
Old Faithful geyser in Yellowstone National Park, Wyoming, USA.

```{r}
selectInput("n_breaks2", label = "Number of bins:",
            choices = c(10, 20, 35, 50), selected = 20)

sliderInput("bw_adjust2", label = "Bandwidth adjustment:",
            min = 0.2, max = 2, value = 1, step = 0.2)
```

Column
-----------------------------------------------------------------------
  
### Geyser Eruption Duration

```{r}

renderPlot({
  hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks2),
       xlab = "Duration (minutes)", main = "Geyser Eruption Duration")
  
  dens <- density(faithful$eruptions, adjust = input$bw_adjust2)
  lines(dens, col = "blue")
})


auth_ui(id = "auth2")

auth <- callModule(
  module = auth_server,
  id = "auth2",
  check_credentials = check_credentials(credentials2) # credentials from above
)
```
TimTeaFan
  • 17,549
  • 4
  • 18
  • 39
  • 1
    @neves: I'm not sure if this is the answer you are looking for. – TimTeaFan Sep 08 '21 at 09:21
  • yes, I was looking for that answer anyway. I thank you immensely for that. Just a question, how to do this authentication when `flexdashboard` has multiple pages, as shown [here](https://github.com/datastorm-open/shinymanager/issues/51#issuecomment-688150605)? I tried to add the code to each page `auth_ui(id = "auth") auth <- callModule(...)` but the authentication didn't work. Do you have any idea what to do? Thank you very much. – neves Sep 08 '21 at 12:17
  • 1
    @neves: Yes that is possbile. See my updated answer. – TimTeaFan Sep 08 '21 at 12:32