Disadvantages of Using Basic Server-Side Authorization in Shiny

shiny
authentication
security

#1

I have created very basic and primitive authentication on Shiny. It just checks the userid-password pair in the server side and conditionally shows the hidden interface.

What could be the downsides of using such primitive authorization?

library(shiny)

ui <- fluidPage(uiOutput("auth"))

server <- function(input, output) {

  output$auth <- renderUI({
    tagList(
      textInput(inputId = "username",label = "Username"),
      passwordInput(inputId  = "userpassword",label = "Password"),
      actionButton("userlogin", "Login")
    )})

  observeEvent(input$userlogin, {
    if(input$username=="demo" & input$userpassword=="demo") {
      output$auth <- renderUI({
        tagList(
          textInput(inputId = "Secret",label = NULL, placeholder = "Secret Data...")
        )})
    }
  })

}

shinyApp(ui = ui, server = server)

Note: I asked the same question in Stack Overflow. However, I had to repost here after three days since it is not replied yet. I'll update both posts after I get a solution.


#2

I am not a security professional, but potential problems that jump to mind are the reactive nature of the hidden section. I believe you would need to preface most reactives with a shiny::validate statement to prevent manipulation of the input object triggering underlying actions.

In this case, suppose something was depending on that input$Secret input. They can create an input just like that on their client page and submit whatever they want to you.

If you had this section:

# Insert Secret into database
observeEvent(input$Secret, {
    # INSERT RECORD
})

They can trigger that without ever having access to the textInput section you created.

A smart actor can make the input object whatever they want, so you need to write every part of your app with that in mind.

That's not to say this is impossible, but there are many pitfalss

authorized <- eventReactive(input$submitpass, {
    input$username == 'demo' & input$password == 'demo'
})
# Insert Secret into database
observeEvent(input$Secret, {
    shiny::validate(need(authorized()), 'You are not authorized')
    # INSERT RECORD
})

EDIT:

Also worth noting that writing authorization is hard, I'd be tempted to use something off the shelf, rather than trying to brew my own. Especially once you get in to having multiple users and storing/hashing passwords... it's very not easy