shinymanager

repository·master·Indexed 19 days ago

https://github.com/datastorm-open/shinymanager

A secure authentication mechanism for single Shiny applications. It allows developers to protect application source code by requiring authentication before the UI is rendered. Supported backends include in-memory data frames, encrypted SQLite databases using AES and scrypt, and external SQL databases (PostgreSQL, MySQL, MS SQL Server) via the DBI interface. It features an administration interface for user management, custom authentication logic support, and configurable security policies for password validity and failure limits.

Tokens
6.3K
Snippets
16
Records
20
Agent score
63%

What's inside shinymanager

  1. Configure cross-application access

    master

    To restrict users to specific applications, add an applications column to your credentials database (SQLite or SQL).

    • The column should contain application names separated by a semicolon (e.g., "app1;app2").
    • The application name is determined by the directory name or can be explicitly set using: options("shinymanager.application" = "my-app").
  2. Secure a Shiny application with an external SQL database

    master

    You can use external SQL databases (PostgreSQL, MySQL, MS SQL Server, etc.) via the DBI interface.

    1. Configure: Edit a YAML configuration file (templates available in the package) to define your database connection.
    2. Initialize: Use create_sql_db() with your credentials and the config path.
    3. Implement: In the Shiny server, use check_credentials(db = "path/to/config.yml") inside secure_server().
    library(shiny)
    library(shinymanager)
    
    # 1. Init the SQL Database (requires a .yml config file)
    # Template: system.file("sql_config/pg_template.yml", package = "shinymanager")
    
    credentials <- data.frame(
      user = c("shiny", "shinymanager"),
      password = c("azerty", "12345"),
      stringsAsFactors = FALSE
    )
    
    create_sql_db(
      credentials_data = credentials,
      config_path = "path/to/your_sql_configuration.yml"
    )
    
    # 2. Shiny UI
    ui <- fluidPage(
      tags$h2("My secure application"),
      verbatimTextOutput("auth_output")
    )
    ui <- secure_app(ui, choose_language = TRUE)
    
    # 3. Shiny Server
    server <- function(input, output, session) {
      res_auth <- secure_server(
        check_credentials = check_credentials(db = "path/to/your_sql_configuration.yml")
      )
      
      output$auth_output <- renderPrint({
        reactiveValuesToList(res_auth)
      })
    }
    
    shinyApp(ui, server)
  3. Integrate shinymanager into a Shiny app

    master

    To secure a Shiny application, you must wrap your UI with secure_app() and initialize the authentication logic at the top of your server function using secure_server().

    1. UI: Call secure_app(ui) at the end of your UI definition.
    2. Server: Call secure_server() at the beginning of your server function. This function returns an authentication result object (res_auth) which contains user information and creates specific inputs like input$shinymanager_where and input$shinymanager_language.
    ui <- fluidPage(
      # your ui code
    )
    secure_app(ui)
    
    server <- function(input, output, session) {
      res_auth <- secure_server(
        check_credentials = check_credentials(credentials)
      )
    
      # Access user info
      output$auth_output <- renderPrint({
        reactiveValuesToList(res_auth)
      })
    
      # Access shinymanager inputs
      observe({
        print(input$shinymanager_where)
        print(input$shinymanager_language)
      })
    
      # Your existing server logic...
    }
  4. Initialize and use a secure SQLite database

    master

    Using a SQLite database enables the built-in administration interface, allowing admins to manage users, reset passwords, and view connection logs. The database is encrypted using openssl and passwords are hashed with scrypt.

    1. Initialize the database

    Use create_db() to create the encrypted SQLite file. You can provide a data.frame of initial credentials. It is recommended to use the keyring package to manage the encryption passphrase securely.

    2. Connect the app to the database

    Pass the SQLite path and passphrase to check_credentials() within secure_server(). To enable the administration tab in the UI, set enable_admin = TRUE in secure_app().

    library(keyring)
    
    # 1. Setup credentials
    credentials <- data.frame(
      user = c("shiny", "shinymanager"),
      password = c("azerty", "12345"),
      admin = c(FALSE, TRUE),
      stringsAsFactors = FALSE
    )
    
    # 2. Secure the passphrase in keyring
    key_set("R-shinymanager-key", "your_secret_passphrase")
    
    # 3. Create the database
    create_db(
      credentials_data = credentials,
      sqlite_path = "path/to/database.sqlite",
      passphrase = key_get("R-shinymanager-key")
    )
    
    # 4. Use in Shiny
    ui <- secure_app(ui, enable_admin = TRUE)
    
    server <- function(input, output, session) {
      res_auth <- secure_server(
        check_credentials = check_credentials(
          "path/to/database.sqlite",
          passphrase = key_get("R-shinymanager-key")
        )
      )
      # ...
    }
  5. Initialize a SQLite database for shinymanager

    master

    To set up a persistent authentication system, use create_db() to initialize a SQLite database. You must provide a data frame containing user credentials. Note that the password field in your input data frame will be automatically hashed by the function for security.

    Required parameters for create_db():

    • credentials_data: A data frame with columns user, password, and admin (logical).
    • sqlite_path: The file path where the SQLite database will be created.
    • passphrase: A string used for encryption (use a passphrase without keyring if not using a system keyring).
    require(shinymanager)
    
    credentials <- data.frame(
      user = c("shiny", "shinymanager"),
      password = c("shiny", "shinymanager"),
      # password will automatically be hashed
      admin = c(FALSE, TRUE), # utilisateurs avec droits d'admin ?
      stringsAsFactors = FALSE
    )
    
    create_db(
      credentials_data = credentials,
      sqlite_path = "database.sqlite", # elle sera crée
      passphrase = "passphrase_wihtout_keyring"
    )
  6. Implement authentication with secure_server()

    master

    To protect a Shiny application using shinymanager, wrap your server logic within the secure_server() function. You must provide a check_credentials object to secure_server to define how user identities are verified.

    To use a SQLite database for credential storage, pass the database file path to check_credentials(). If you are not using a system keyring to manage secrets, you must provide a passphrase to allow the application to access the database.

    server <- function(input, output, session) {
    
        res_auth <- secure_server(
            check_credentials = check_credentials(
                "database.sqlite",
                passphrase = "passphrase_wihtout_keyring"
            )
        )
        
        # Your application logic goes here
    }
  7. Secure a Shiny application with an encrypted SQLite database

    master

    For persistent and secure storage, use an encrypted SQLite database. Credentials are protected with AES encryption (via openssl) and passwords are hashed using scrypt.

    1. Initialize the database using create_db().
    2. Wrap the UI with secure_app(ui, enable_admin = TRUE) to allow admin access.
    3. Initialize the server using secure_server() passing the SQLite path and the passphrase.
    library(keyring)
    library(shinymanager)
    
    # 1. Init DB using credentials data
    credentials <- data.frame(
      user = c("shiny", "shinymanager"),
      password = c("azerty", "12345"),
      admin = c(FALSE, TRUE),
      stringsAsFactors = FALSE
    )
    
    # Set a key in keyring
    key_set("R-shinymanager-key", "obiwankenobi")
    
    # Create the database
    create_db(
      credentials_data = credentials,
      sqlite_path = "path/to/database.sqlite",
      passphrase = key_get("R-shinymanager-key", "obiwankenobi")
    )
    
    # 2. UI Setup
    ui <- secure_app(ui, enable_admin = TRUE)
    
    # 3. Server Setup
    server <- function(input, output, session) {
      res_auth <- secure_server(
        check_credentials = check_credentials(
            "path/to/database.sqlite",
            passphrase = key_get("R-shinymanager-key", "obiwankenobi")
        )
      )
      
      output$auth_output <- renderPrint({
        reactiveValuesToList(res_auth)
      })
      
      # your classic server logic
    }
    
    shinyApp(ui, server)
  8. Customize the authentication UI

    master

    The secure_app() function provides several arguments to customize the appearance and behavior of the login screen:

    • status: Bootstrap status for the UI ("default", "primary", "success", "warning", "danger").
    • tags_top: HTML elements (div, img, etc.) to display at the top of the module.
    • tags_bottom: HTML elements to display at the bottom of the module.
    • background: CSS string for the background (e.g., gradients or images).
    • choose_language: A character vector of allowed languages (e.g., c("fr", "en")).
    • language: The default language for the UI.
    ui <- secure_app(ui, 
                     status = "danger",
                     tags_top = tags$div(tags$h4("My App")),
                     background = "linear-gradient(to bottom, #ffffff, #000000);", 
                     choose_language = c("fr", "en"), 
                     language = "fr"
    )
  9. Configure password validity and failure limits

    master

    You can control security policies using R options().

    • Password Validity Period: Set how many days a password remains valid. Defaults to Inf (never expires). Use options("shinymanager.pwd_validity" = 90) to force changes every 90 days.
    • Failure Limit: Set the number of allowed failed login attempts before an account is locked. Defaults to Inf. Use options("shinymanager.pwd_failure_limit" = 5) to lock accounts after 5 failures.
  10. Troubleshoot shinymanager input errors

    master

    Because shinymanager hides the UI until authentication is successful, there is a temporal lag where input elements might not yet be defined (they may be NULL). This can cause errors in reactive expressions or observers.

    Recommended Solution: Use the req() function in all reactive/observer functions to validate that the required inputs exist before executing logic.

    Alternative Solution: Check for the existence of the shinymanager_where input manually:

    observe({
      if(is.null(input$shinymanager_where) || (!is.null(input$shinymanager_where) && input$shinymanager_where %in% "application")){
        # your server app code
      }
    })
  11. Troubleshooting: Handling NULL inputs during authentication lag

    master

    When integrating shinymanager, you may encounter errors where reactive elements or inputs are NULL. This happens because there is a delay between the UI rendering and the server completing the authentication process.

    Solution: Always use the req() function in your server.R to validate the presence of inputs before using them in observers or render functions.