## Authentication
## This is a small app to demonstrate user-managed authentication without encoded passwords.
## Users are stored in a SQL database with passwords along with roles.
## Once a user is logged in the shiny app responds to the user's role.
## In order to use in a real setting, additional code for password management,
## changing and resetting would need to be implemented.
library(shiny)
library(RSQLite)
## create the initial password database
## This code should be run once to create the initial database of users, passwords and roles
##
# db.pw <- data.frame(user = c('Augustin', 'Matt', 'Harvey'), role = c('Manager', 'User', 'User'), password = c('ABC', 'DEF', 'GHI'))
# db = dbConnect(SQLite(), dbname = 'auth_nohash.sqlite')
# dbSendQuery(db, 'CREATE TABLE pw (user TEXT, password TEXT, role TEXT)')
# apply(db.pw, 1, function(x) dbSendQuery(db, paste0('INSERT INTO pw VALUES("', x['user'], '", "', x['password'], '", "', x['role'], '")')))
# dbDisconnect(db)
## Connect to the database (may be a remote connection)
= dbConnect(SQLite(), dbname = 'auth_nohash.sqlite')
db
<- function(input, output, session) {
server
## Initialize - user is not logged in
<- reactiveValues(login = FALSE, name = NULL, role = NULL, header = NULL)
user
## Display login modal
observe({
showModal(modalDialog(
title = "Enter Login Details",
textInput('userInp', 'Login'),
passwordInput('pwInp', 'Password'),
actionButton('butLogin', 'Login', class = 'btn action-button btn-success', icon = icon('sign-in')),
size = 's',
easyClose = FALSE,
footer = NULL
))
})
## Check for user in database
observeEvent(input$butLogin, { ## login button pressed
req(input$userInp, input$pwInp) ## ensure we have inputs
removeModal() ## remove the modal
<- dbGetQuery(db, paste0('SELECT password FROM pw WHERE user = \"', input$userInp, '\"')) ## query database
pw_out if (nrow(pw_out) == 0) { ## user does not exist
$login <- FALSE
user$header <- 'ERROR - UNKNOWN USER'
userelse {
} <- as.character(pw_out$password)[[1]] ## grab password from database
pw <- pw == input$pwInp ## check that it matches user input
passwordVerified if (passwordVerified) { ## match
$login <- TRUE
user$name <- input$userInp
user$role <- db.pw[db.pw$user == input$userInp, 'role']
user$header <- paste0(user$name, ' (', user$role, ')')
userelse { ## no match
} $login <- FALSE
user$header <- 'ERROR - INCORRECT PASSWORD'
user
}
}
})
## close database on exit
$onSessionEnded(function(){
sessiondbDisconnect(db)
})
$data <- renderUI({
outputh4(user$header)
})
$myPlot <- renderPlot({
outputreq(user$login)
if (user$role == 'Manager') { ## If manager role, display iris plot
plot(iris$Sepal.Length, iris$Sepal.Width)
else { ## If user role, display mtcars plot
} plot(mtcars$mpg, mtcars$cyl)
}
})
}
<- fluidPage(
ui uiOutput('data'),
plotOutput('myPlot')
)
shinyApp(ui = ui, server = server)
There are several ways that users can be managed in Shiny apps but they all require access to the config file. User access can be managed within the app itself using a database to hold user information. Two examples are given below: The first utilizes a simple database in which the passwords are not encrypted (not recommended for multiple reasons!) The second is very similar but it uses the sodium library to encode the passwords in the database. These are very simplistic examples and contain no functions for password management (such as users being able to change their passwords or password reset) but these would be straight forward to add.
User login - No encrpytion
https://gist.github.com/harveyl888/a85e17c3048e0da03cf4e6b52d1da3db
User login - With encrpytion
https://gist.github.com/harveyl888/3e5123a6469fbdc3830123e3efb31a2a
## Authentication
## This is a small app to demonstrate user-managed authentication using a hash to encode passwords.
## Users are stored in a SQL database with passwords along with roles.
## Once a user is logged in the shiny app responds to the user's role.
## In order to use in a real setting, additional code for password management,
## changing and resetting would need to be implemented.
library(shiny)
library(RSQLite)
library(sodium)
## create the initial password database
## This code should be run once to create the initial database of users, passwords and roles
##
# db.pw <- data.frame(user = c('Augustin', 'Matt', 'Harvey'), role = c('Manager', 'User', 'User'), password = c('ABC', 'DEF', 'GHI'))
# db.pw$encrypt <- apply(db.pw, 1, function(x) password_store(x['password']))
# db = dbConnect(SQLite(), dbname = 'auth_hash.sqlite')
# dbSendQuery(db, 'CREATE TABLE pw (user TEXT, password TEXT, role TEXT)')
# apply(db.pw, 1, function(x) dbSendQuery(db, paste0('INSERT INTO pw VALUES("', x['user'], '", "', x['encrypt'], '", "', x['role'], '")')))
# dbDisconnect(db)
## Connect to the database (may be a remote connection)
= dbConnect(SQLite(), dbname = 'auth_hash.sqlite')
db
<- function(input, output, session) {
server
## Initialize - user is not logged in
<- reactiveValues(login = FALSE, name = NULL, role = NULL, header = NULL)
user
## Display login modal
observe({
showModal(modalDialog(
title = "Enter Login Details",
textInput('userInp', 'Login'),
passwordInput('pwInp', 'Password'),
actionButton('butLogin', 'Login', class = 'btn action-button btn-success', icon = icon('sign-in')),
size = 's',
easyClose = FALSE,
footer = NULL
))
})
## Check for user in database
observeEvent(input$butLogin, { ## login button pressed
req(input$userInp, input$pwInp) ## ensure we have inputs
removeModal() ## remove the modal
<- dbGetQuery(db, paste0('SELECT password FROM pw WHERE user = \"', input$userInp, '\"')) ## query database
pw_out if (nrow(pw_out) == 0) { ## user does not exist
$login <- FALSE
user$header <- 'ERROR - UNKNOWN USER'
userelse {
} <- as.character(pw_out$password)[[1]] ## grab password from database
pw <- password_verify(pw, input$pwInp) ## check that it matches user input
passwordVerified if (passwordVerified) { ## match
$login <- TRUE
user$name <- input$userInp
user$role <- db.pw[db.pw$user == input$userInp, 'role']
user$header <- paste0(user$name, ' (', user$role, ')')
userelse { ## no match
} $login <- FALSE
user$header <- 'ERROR - INCORRECT PASSWORD'
user
}
}
})
## close database on exit
$onSessionEnded(function(){
sessiondbDisconnect(db)
})
$data <- renderUI({
outputh4(user$header)
})
$myPlot <- renderPlot({
outputreq(user$login)
if (user$role == 'Manager') { ## If manager role, display iris plot
plot(iris$Sepal.Length, iris$Sepal.Width)
else { ## If user role, display mtcars plot
} plot(mtcars$mpg, mtcars$cyl)
}
})
}
<- fluidPage(
ui uiOutput('data'),
plotOutput('myPlot')
)
shinyApp(ui = ui, server = server)