How to build login page in R Shiny App

Deepanshu Bhalla 15 Comments ,

This tutorial covers how you can build a login or authentication page where users need to add a username and password to access a shiny app.

Features of Authentication Functionality

  1. Dashboard will be opened only when user enters correct username and password
  2. You can hide or show functionalities of dashboard (like tabs, widgets etc) based on type of permission
  3. Encrypt password with hashing algorithm which mitigates brute-force attacks
login page in R shiny app

Authentication via shinyauthr package

There is a useful R package named shinyauthr for setting up authentication in shiny. It can be used to build login page along with logout functionality. Please make sure to install the package before using the program below.


library(shiny)
library(shinyauthr)

# dataframe that holds usernames, passwords and other user data
user_base <- tibble::tibble(
  user = c("user1", "user2"),
  password = sapply(c("pass1", "pass2"), sodium::password_store),
  permissions = c("admin", "standard"),
  name = c("User One", "User Two")
)

ui <- fluidPage(
  # logout button
  div(class = "pull-right", shinyauthr::logoutUI(id = "logout")),
  
  # login section
  shinyauthr::loginUI(id = "login"),
  
  # Sidebar to show user info after login
  uiOutput("sidebarpanel"),
  
  # Plot to show user info after login
  plotOutput("distPlot")
  
)

server <- function(input, output, session) {
  
  credentials <- shinyauthr::loginServer(
    id = "login",
    data = user_base,
    user_col = user,
    pwd_col = password,
    sodium_hashed = TRUE,
    log_out = reactive(logout_init())
  )
  
  # Logout to hide
  logout_init <- shinyauthr::logoutServer(
    id = "logout",
    active = reactive(credentials()$user_auth)
  )
  
  
  output$sidebarpanel <- renderUI({
    
    # Show only when authenticated
    req(credentials()$user_auth)
    
    tagList(
    # Sidebar with a slider input
    column(width = 4,
      sliderInput("obs",
                  "Number of observations:",
                  min = 0,
                  max = 1000,
                  value = 500)
    ),
    
    column(width = 4,
           p(paste("You have", credentials()$info[["permissions"]],"permission"))
           )
    )
    
  })
  
    # Plot
    output$distPlot <- renderPlot({
      
      # Show plot only when authenticated
      req(credentials()$user_auth)
      
      if(!is.null(input$obs)) {
        hist(rnorm(input$obs)) 
      }
      
    })
    
    
}

shinyApp(ui = ui, server = server)

You can customize the above program based on permission level using credentials()$info[["permissions"]]. For example if you want to show limited features to the user who has standard permission.

Manual Steps to Build Login Page

In this section we covered step by step instructions to set up authentication functionality in shiny.

Step 1 : Install the following packages by using the command install.packages(package-name)

  • shiny
  • shinydashboard
  • DT
  • shinyjs
  • sodium

Step 2 : Run the program below.


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("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")),
        menuItem("Second Page", tabName = "second", icon = icon("th"))
      )
    }
  })
  
  output$body <- renderUI({
    if (USER$login == TRUE ) {
      tabItems(
      
      # First tab
      tabItem(tabName ="dashboard", class = "active",
              fluidRow(
                box(width = 12, dataTableOutput('results'))
              )),
      
      # Second tab
      tabItem(tabName = "second",
              fluidRow(
                box(width = 12, dataTableOutput('results2'))
              )
      ))
      
    }
    else {
      loginpage
    }
  })
  
  output$results <-  DT::renderDataTable({
    datatable(iris, options = list(autoWidth = TRUE,
                                   searching = FALSE))
  })
  
  output$results2 <-  DT::renderDataTable({
    datatable(mtcars, options = list(autoWidth = TRUE,
                                   searching = FALSE))
  })
  
  
}

runApp(list(ui = ui, server = server), launch.browser = TRUE)

How to customize the program
  1. In the above program, two user names and passwords are defined Username : myuser , Password : mypass Username : myuser1 , Password : mypass1. To change them, you can edit the following code in R program.
     
    credentials = data.frame(
      username_id = c("myuser", "myuser1"),
      passod   = sapply(c("mypass", "mypass1"),password_store),
      permission  = c("basic", "advanced"), 
      stringsAsFactors = F
      )
    
  2. In order to modify sidebar section, you can edit the following section of code.
        if (USER$login == TRUE ){ 
          sidebarMenu(
            menuItem("Main Page", tabName = "dashboard", icon = icon("dashboard"))
          )
        }
    
    In order to edit main body of the app, you can make modification in the following section of code.
      if (USER$login == TRUE ) {
          tabItem(tabName ="dashboard", class = "active",
                  fluidRow(
                    box(width = 12, dataTableOutput('results'))
                  ))
        }
        else {
          loginpage
        }
    
  3. Suppose you want to show multiple tabs if permission level is set "advanced". Otherwise show a single tab. If you login with credentials Username : myuser1 Password : mypass1, you would find two tabs. Else it would show only one tab named "Main Page". Replace renderUI function of output$sidebarpanel and output$body with the following script.
      output$sidebarpanel <- renderUI({
        if (USER$login == TRUE ){ 
          if (credentials[,"permission"][which(credentials$username_id==input$userName)]=="advanced") {
            sidebarMenu(
            menuItem("Main Page", tabName = "dashboard", icon = icon("dashboard")),
            menuItem("About Page", tabName = "About", icon = icon("th"))
            )
          }
          else{
            sidebarMenu(
              menuItem("Main Page", tabName = "dashboard", icon = icon("dashboard"))
            )
            
          }
        }
      })
      
      
      output$body <- renderUI({
        if (USER$login == TRUE ) {
        if (credentials[,"permission"][which(credentials$username_id==input$userName)]=="advanced") {
        tabItems(
                  tabItem(
                   tabName ="dashboard", class = "active",
                   fluidRow(
                    box(width = 12, dataTableOutput('results'))
                  ))
            ,
              tabItem(
                tabName ="About",
                h2("This is second tab")
                  )
        )
        } 
          else {
            tabItem(
              tabName ="dashboard", class = "active",
              fluidRow(
                box(width = 12, dataTableOutput('results'))
              ))
            
          }
        
        }
        else {
          loginpage
        }
      })
    
Other Shiny Packages for Authentication

Docker-based shinyproxy package is available for free which has an authentication feature along with some other great enterprise features. But you need to know docker to use this package and many users find it complicated.

Related Posts
Spread the Word!
Share
About Author:
Deepanshu Bhalla

Deepanshu founded ListenData with a simple objective - Make analytics easy to understand and follow. He has over 10 years of experience in data science. During his tenure, he worked with global clients in various domains like Banking, Insurance, Private Equity, Telecom and HR.

15 Responses to "How to build login page in R Shiny App"
  1. how do i use this code and link it with my dashboard i Just need to create alogin page without the iris data

    ReplyDelete
  2. Hi, I've been using this code to create a login page on my app. Within output$body in the server I have been adding features to the body of my app (which you'd usually put in the ui of your app), and the outputs outside this, in just the server (which you'd normally put in the server). I can add things which require no input with no problems. However, if I try to add a feature which relies on an input to decide what to show I get an error. For example, if I want to use radioButtons to be able to choose which graph to show out of 2 options, the app will not load as it seems to try and create the output before there has been an opportunity to select the input. The error I'm getting is: Warning: Error in if: argument is of length zero. How did you get past this?

    ReplyDelete
    Replies
    1. Hi,

      I got same problem. Did you find any solution to that ?
      Thanks

      Delete
  3. Hi!
    Suppose I add the id of a country in credentials country = c ("23", "100", "45"); username_id = ("Japan", "Chile", "USA"). How I use username (input $ userName) to filter a database by country, i.e. that when I automatically log in, I filter by the country associated with the user in credentials.

    ReplyDelete
  4. Super Mega recontra agradecido.
    Very Very grateful from Venezuela bro . Fantastic !!!!

    ReplyDelete
  5. Hi,
    Nice article.
    I implemented this logic. However, I want to setup a default tab/screen to appear after login. According to this login, after successful login, blank screen appears and I have to manually select a tab to see the contents.
    I have tried selecting the tab from various options available but unable to do so.
    Any idea how to achieve this?

    ReplyDelete
    Replies
    1. It works fine at my end. I added one more tab in the above post. It automatically selects the content of the first tab.

      Delete
    2. Thank you. I will check out.

      Delete
    3. Add class = "active" to the tab you want to appear after login

      Delete
  6. how i can custom the login password and user name , which the data from my database?

    ReplyDelete
    Replies
    1. You can use RSQLite to create a data frame that is assigned with your database login information.

      Delete
  7. Thank you very much...is it possible or not to add some logic if the input password get 3 times mistakes in a row, the user account will be lock and need to be reset

    ReplyDelete
  8. Is it possible to get the session user information from the custom method?

    ReplyDelete
Next → ← Prev