Yang Saya Pelajari Ketika Menghadiri “Kopdar useR Indonesia Regional Jogja & Jateng” – Part 2: “User Level Access in Shiny Apps (Free)”

banner kopdar useR regional jogja jateng

“A Shiny application is simply a directory containing an R script called app.R which is made up of a user interface object and a server function. This folder can also contain any any additional data, scripts, or other resources required to support the application.”

~ Shiny from RStudio

hkaLabs – hakim-azizul.com – Di sesi kedua kopdar ini, Mas Muhammad Farkhan Novianto menyampaikan materi “User Level Access in Shiny Apps (Free)”, atau dengan kata lain bagaimana caranya menerapkan user role dan spesific task atau data yang hanya bisa diakses oleh role tersebut, pada Shiny.

Sesi kedua ini juga tidak kalah menarik dari sesi pertama. Karena sebelumnya saya belum pernah menggunakan Shiny, biasanya hasil kerja saya menggunakan R saya tunjukkan dalam bentuk report menggunakan R Markdown atau Jupyter Notebook dengan Kernel R.

Untuk slide presentasi dan script dari kopdar ini dapat diperoleh di Github.

Kopdar useR Jogja Jateng

Kopdar useR Regional Jateng-Jogja. Sumber Gambar: https://t.me/RIndonesia_Jateng.

 

1. Beberapa Insights yang saya dapatkan dari Sesi Kedua Kopdar useR Jogja-Jateng:

  • Shiny adalah package R yang menyediakan web framework untuk membuat web apps menggunakan R. Berkat Shiny, seorang R developer atau Data Scientist/Data Analyst bisa mempresentasikan hasil penelitiannya dalam bentuk aplikasi web interaktif, sekalipun minim pengetahuan HTML, CSS, atau JavaScript.
  • Beberapa hosting option untuk Shiny diantaranya: shinyapps.io (opsi gratis & berbayar), Shiny Server, dan R Studio Connect (berbayar).
  • Untuk membuat Shiny App kita terproteksi dengan autentikasi, terdapat beberapa opsi seperti hosting aplikasi kita di balik reverse proxy server seperti nginx, atau menggunakan ShinyProxy (Docker based. Setelah baca-baca sedikit, ShinyProxy ini sangat menarik; karena memungkinkan kita merasakan fitur-fitur enterprise di Shiny App biarpun tetap open source, dan benefit-benefit lainnya. Mungkin bisa kita coba next time :)).
  • User Level Access adalah penerapkan user role dan spesific task atau data yang hanya bisa diakses oleh role tertentu, atau dengan kata lain: user yang berbeda tingkat hierarki akan melihat konten yang berbeda pula. Hal ini dapat diterapkan juga ke Shiny Apps (contoh: konten yang dilihat oleh user biasa vs admin tentu akan berbeda).
  • Contoh penerapan User Level Access misalnya, ketika kita diminta untuk menyajikan data sales dari setiap staff penjualan dalam bentuk dashboard, dan setiap staf hanya bisa melihat data penjualannya sendiri (tidak bisa melihat data penjualan rekan kerjanya). Admin bisa dianggap sebagai supervisor sales, yang mampu melihat semua data penjualan para stafnya dan memantau kinerjanya.

 

Terdapat 3 demo User Level Access yang ditunjukkan oleh Mas Farkhan, diantaranya:

1.1. Render UI

Untuk mencoba aplikasi ini dan melihat perbedaan antar user role (admin vs user biasa), silakan buka link “Render UI” di atas dan login dengan username dan password sebagai berikut:

  • Admin user name = admin
  • Password = 9999

Sedangkan, untuk user biasa:

  • User name = setosa
  • Password = 1111

atau

  • User name = versicolor
  • Password = 2222

atau

  • User name = virginica
  • Password = 3333

Dan untuk data dari setiap user, diambil dari data set Iris.

Berikut ini screenshot dari penerapan User Level Access (Bandingkan tampilan yang bisa kita lihat jika login sebagai user biasa vs login sebagai admin):

render UI

Tampilan Render UI. Sumber Gambar: https://farkhan.shinyapps.io/render-ui/

render UI Login User

Login dengan User Role Biasa (Non-Admin). Sumber Gambar: https://farkhan.shinyapps.io/render-ui/

render UI Login User Setosa

Data atau Tampilan yang Bisa Diakses oleh User Setosa. Sumber Gambar: https://farkhan.shinyapps.io/render-ui/

render UI Login

Login Kembali dengan User Role Admin. Sumber Gambar: https://farkhan.shinyapps.io/render-ui/

render UI Login Admin

Data atau Tampilan yang Bisa Diakses oleh User Role Admin. Admin Dapat Mengakses Data Semua User. Sumber Gambar: https://farkhan.shinyapps.io/render-ui/

 

1.2. shinyjs Hide

Untuk membuat kualitas UI yang lebih baik, maka diperlukan Javascript juga. Kita dapat membuat dashboard dengan kualitas UI yang baik dengan mudah, menggunakan package shinyjs.

Apa saja yang dapat dibuat menggunakan shinyjs?

  • Hide atau show element.
  • Disable atau enable input.
  • Reset input, lalu mengembalikannya ke original value.
  • Menunda eksekusi kode selama beberapa detik.
  • Memanggil function JavaScript menggunakan R.
  • Dll.

Pada bagian ini, Shiny App dibuat seperti biasa, dengan User Level Access, dan username dan password seperti yang dibahas sebelumnya, namun ditambahkan bagian yang di-hide apabila user belum melakukan login.

Berikut ini screenshot dari penerapannya:

shinyjs1

shinyjs 1: Terdapat Bagian yang Di-hide Apabila User Belum Melakukan Login. Sumber Gambar: https://farkhan.shinyapps.io/shinyjs-hide/

shinyjs2

shinyjs 2: Agar Dapat Melihat Datanya, Masukkan Password. Sumber Gambar: https://farkhan.shinyapps.io/shinyjs-hide/

shinyjs3

shinyjs 3: Semua Data Dapat Ditampilkan, Apabila Login Menggunakan Password Role Admin. Sumber Gambar: https://farkhan.shinyapps.io/shinyjs-hide/

 

1.3. Persistent Login

Aplikasi Render UI dan shinyjs Hide akan log out dengan sendirinya, apabila kita mengakhiri sesi seperti merefresh web page, menutup browser atau tab browser kita, ataupun setelah lama tidak melakukan aktivitas pada kedua halaman aplikasi tersebut.

Berikutnya, kita ingin mempertahankan kondisi login kita sekalipun browser ditutup, web page direfresh, dll, atau dengan kata lain membuat Persistent Login di setiap session-nya.

Berikut ini screenshot dari penerapannya:

persistentLogin1

Persistent Login 1: Sama Seperti Sebelumnya, Kita Perlu Login Terlebih Dahulu Untuk Melihat Data. Sumber Gambar: https://farkhan.shinyapps.io/persistent-login/

persistentLogin2

Persistent Login 2: Jika Login Sebagai Admin, Kita Bisa Melihat Semua Data User, Seperti Sebelumnya. Sumber Gambar: https://farkhan.shinyapps.io/persistent-login/

persistentLogin2

Persistent Login 3: Sekalipun Session Kita Akhiri, Setiap Memulai Session Baru, Kita Tetap Terdeteksi Telah Login, Seperti Screenshot Di Atas. Sumber Gambar: https://farkhan.shinyapps.io/persistent-login/

 

2. Studi Lebih Lanjut

Sekali lagi, saya setuju dengan kata-kata Mas Canggih pada kopdar ini, yaitu: “Kalau bingung; dan pasti bingung ketika berhadapan dengan materi baru seperti ini memang wajar, justru itu tujuannya ada kopdar seperti ini, karena bingung, jadi penasaran.”, sehingga, sepulang dari kopdar, saya buka laptop dan pertama-tama saya download dulu scriptnya Mas Farkhan dari Githubnya.

Lalu, seperti yang saya katakan di awal tadi, saya belum pernah mencoba Shiny sebelumnya. So, i totally lost. 😀

Sehingga, saya perlu googling dan baca-baca dahulu beberapa dasar dari Shiny, untuk setidaknya bisa sekedar running programnya Mas Farkhan, dan paham struktur programnya.

 

2.1. Struktur Shiny Apps:

Bagian 2.1 ini bertujuan agar kita memahami bagaimana Shiny App bekerja. Untuk yang sudah memahami dan terbiasa menggunakan Shiny App, bagian ini bisa dilewat saja, dan silakan langsung lanjutkan membaca ke bagian 2.2. 🙂

Shiny Apps dimuat dalam satu script bernama “app.R”. Script tersebut disimpan di suatu directory, anggap saja namanya “01_hello” (kita mengambil contoh dari Hello Shiny). Kita dapat menjalankan aplikasinya dengan menjalankan runApp(“01_hello”).

app.R terdiri dari tiga komponen, yaitu:

  • Objek user interface (terkadang dipisah dari app.R, dan disimpan menjadi satu file tersendiri yang bernama ui.R).
  • Function server (server.R, jika filenya dipisah).
  • Call function shinyApp.

Dibawah ini adalah contoh yang diambil dari “Hello Shiny” (yang merupakan contoh dasar dari dashboard dinamis yang dibuat menggunakan Shiny App):

2.1.1. ui.R

Berikut ini adalah contoh bagian UI dari file 01_hello atau Hello Shiny:

library(shiny)

#Definisikan UI untuk aplikasi yang akan membuat plot histogram ----
ui <- fluidPage(

  #Judul Aplikasi ----
  titlePanel("Hello Shiny!"),

  #Layout sidebar layout dengan definisi input dan output ----
  sidebarLayout(

    #Panel sidebar untuk input ----
    sidebarPanel(

      #Input: Slider untuk jumlah bins ----
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)

    ),

    #Panel utama untuk menampilkan output ----
    mainPanel(

      #Output: Histogram ----
      plotOutput(outputId = "distPlot")

    )
  )
)

2.1.2. server.R

Berikut ini adalah contoh bagian server dari file 01_hello atau Hello Shiny:

#Define server logic yang diperlukan untuk membuat plot histogram ----
server <- function(input, output) {

  #Histogram dari data "Old Faithful Geyser", dengan jumlah bin yang di-request oleh user
  #
  #Ekspresi yang akan men-generate histogram berada di dalam call atau function renderPlot, untuk menunjukkan bahwa: 
  #
  #1. Sifatnya "reactive", sehingga akan selalu di-re-execute secara otomatis, kapanpun input (input$bins) berubah
  #2. Tipe outputnya merupakan sebuah plot histogram
  output$distPlot <- renderPlot({

    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)

    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")

    })

}

2.1.3. shinyApp()

Untuk menjalankan Shiny Apps, app.R diakhiri dengan call shinyApp sebagai berikut:

#Membuat/menjalankan Shiny App ----
shinyApp(ui = ui, server = server)

Selanjutnya, untuk menjalankan aplikasi Shiny, kita bisa menjalankan perintah runApp(“direktori”); misalnya runApp(“01_hello”), atau bisa juga dengan klik button run pada RStudio:

run app pada rstudio

Run App pada RStudio. Sumber Gambar: Dokumentasi Pribadi.

Penampakkan Hello Shiny di web browser:

hello Shiny Di Browser

Penampakkan Hello Shiny di Web Browser. Sumber Gambar: Dokumentasi Pribadi.

Untuk contoh-contoh lain dari penggunaan Shiny App, bisa di lihat di Github, contoh-contohnya banyak dan bagus sekali, dapat dipilih sesuai case yang kita butuhkan. 🙂

 

2.2. Packages yang Diperlukan

Berikut ini adalah packages yang diperlukan untuk menjalankan aplikasi Render UI, shinyjs Hide, dan Persistent Login:

 

2.3. How to Run the Apps:

Untuk menjalankan aplikasi-aplikasi tersebut menggunakan RStudio, pertama-tama, silakan buka file setup.R.

2.3.1. Render UI

Lalu blok script berikut dan klik button “Run App”, untuk load data yang dibutuhkan, dan menjalankan aplikasi Render UI:

library(dplyr)

# Data user yang digunakan untuk sampel
df_user <- tibble(username = c('setosa','versicolor','virginica','admin'),
                  password = c('1111','2222','3333','9999'))

# Render UI -----
saveRDS(df_user,here::here('render-ui','data','credentials.rds'))
shiny::runApp('render-ui',display.mode = 'showcase')

Atau, cara yang lebih mudah adalah dengan menjalankan perintah berikut di RConsole:

runApp("render-ui")

Dengan “render-ui” adalah folder direktori tempat kita menyimpan scripts yang dibutuhkan.

2.3.2. shinyjs Hide

Lalu blok script berikut dan klik button “Run App”, untuk load data yang dibutuhkan, dan menjalankan aplikasi shinyjs Hide:

library(dplyr)

# Data user yang digunakan untuk sampel
df_user <- tibble(username = c('setosa','versicolor','virginica','admin'),
                  password = c('1111','2222','3333','9999'))

# Shinyjs Hide -----
saveRDS(df_user,here::here('shinyjs-hide','data','credentials.rds'))
shiny::runApp('shinyjs-hide',display.mode = 'showcase')

Atau, cara yang lebih mudah adalah dengan menjalankan perintah berikut di RConsole:

runApp("shinyjs-hide")

2.3.3. Persistent Login

Lalu blok script berikut dan klik button “Run App”, untuk load data yang dibutuhkan, dan menjalankan aplikasi Persistent Login:

library(dplyr)

# Data user yang digunakan untuk sampel
df_user <- tibble(username = c('setosa','versicolor','virginica','admin'),
                  password = c('1111','2222','3333','9999'))

# Persistent Login ----

# Cookies js to save get and remove cookies
folder_www <- here::here('persistent-login','www')
if (!dir.exists(folder_www)) {
  dir.create(folder_www)
}

download.file(
  url = 'https://raw.githubusercontent.com/js-cookie/js-cookie/master/src/js.cookie.js',
  destfile = paste0(folder_www,'/js.cookie.js')
)

df_user %>% 
  mutate(password = purrr::map_chr(password, bcrypt::hashpw)) %>% 
  mutate(session = "") %>% 
  saveRDS(here::here('persistent-login','data','credentials.rds'))

shiny::runApp('persistent-login',display.mode = 'showcase')

Atau, cara yang lebih mudah adalah dengan menjalankan perintah berikut di RConsole:

runApp("persistent-login")

 

2.4. Struktur Render UI

  1. UI hanya berisikan renderUI
  2. Membuat Reactive Values untuk menyimpan data login user.
  3. Menunjukkan halaman login ketika tidak login.
  4. Memproses login ketika button “Login” diklik, dan menyimpan user name oleh user yang login.
  5. Memproses data sesuai dari user dan role yang login.
  6. Menampilkan UI untuk user yang telah login.
  7. Menghapus Reactive Values setelah log out.

Berikut ini adalah script untuk setiap tahapannya:

renderUI (lihat file ui.R):

library(shiny)

shinyUI(
    fluidPage(
        title = judul,
        uiOutput("ui")
    ))

Reactive Values untuk menyimpan data login (lihat file server.R):

# Reactive value untuk menyimpan jika sudah login dan data user yang login
sudah_login <- reactiveVal(value = FALSE)
data_user <- reactiveVal(value = NULL)

Menampilkan halaman login ketika tidak login (lihat file server.R):

if (!sudah_login()) {
    # Skrip UI login page, akan muncul ketika belum login
    fluidRow(
        column(width = 12, align='center',
               br(), br(), br(), br(), br(),
               h3(judul),
               br(), br(),
               textInput('username', '', placeholder = 'user'),
               passwordInput('password', '', placeholder = 'pass'),
               br(),
               actionButton('login', 'Login',
                            class = "btn-primary")
        )
    )
}

Memproses login (lihat file server.R):

# Skrip untuk login dan logout ketika menekan tombol
observeEvent(input$login, {
    cred_user <- df_credentials %>%
        filter(username == input$username)
    if(nrow(cred_user)) {
        if (input$password == cred_user$password) {
            sudah_login(TRUE)
            data_user(cred_user)
        }}
})

Memproses data sesuai user name (lihat file server.R):

output$contohData <- renderTable({
    req(data_user()$username)
    df_data <- iris
    nama = data_user()$username
        
    if(nama!='admin') {
        df_data <- df_data %>% 
            filter(Species == nama)
    }
    df_data %>% group_by(Species) %>% summarise_all(list(Total = sum))
})

Menampilkan UI untuk user yang telah login (lihat file server.R):

if (!sudah_login()) {
# Skrip UI login page, akan muncul ketika belum login (Sudah dibahas di atas ("Menampilkan halaman login ketika tidak login (lihat file server.R)")
} else {
    # Skrip UI ketika sudah login
    fluidRow(
        fluidRow(
            column(11, align = 'right', sprintf('Hi, %s',data_user()$username)),
            column(1, actionButton('logout',' ',class = "btn-danger",
                                   style = "color: white;",icon("power-off")))
        ),
        br(),
        fluidRow(
            column(12,
                   h3(judul), align='center',
                   br(),
                   tableOutput("contohData")
            )))}
})

Menghapus Reactive Values setelah log out (lihat file server.R):

observeEvent(input$logout, {
    sudah_login(FALSE)
    data_user(NULL)
})

 

2.5. Struktur shinyjs Hide

  1. UI-nya seperti biasa, namun ada sebagian atau seluruh bagian yang di-hide jika belum login.
  2. Menunjukkan halaman login ketika user belum login.
  3. Memproses login setelah klik button, menyimpan user name untuk yang sudah login, lalu menampilkan Hidden UI.
  4. Memproses data sesuai user name dan user role.
  5.  Menghapus Reactive Values dan kembali menyembunyikan konten setelah logout.

Berikut ini adalah script untuk setiap tahapannya:

UI-nya seperti biasa, namun menyembunyikan konten sensitif (lihat file ui.R):

shinyjs::hidden(
    div(
        id = "div_data",
        box(width = 12,
            h3(textOutput('text_user')),
            br(),
            tableOutput("contohData")
        )))

Menunjukkan halaman login ketika user belum login (UI untuk mengisi password (lihat file ui.R)):

div(id = "div_password",
    align = 'center',
    h3("Masukkan Password untuk melihat datanya"),
    passwordInput("password","", placeholder = "pass"),
    actionButton("login", strong("Login"),
                 style="color: #fff; background-color: #337ab7; border-color: #2e6da4"),
    shinyjs::hidden(
        div(id = "login-error",
            div(icon("exclamation-circle"),                  
                shiny::tags$b("Password Salah", style = "margin-top: 10px; color: red;")
            )))
),

Reactive Values untuk menyimpan data login (lihat file server.R):

# Reactive value untuk menyimpan data user yang login
data_user <- reactiveVal(value = NULL)

Memproses login (lihat file server.R):

# Skrip untuk login dan logout ketika menekan tombol
observeEvent(input$login, {
    cred_user <- df_credentials %>%
        filter(password == input$password)
    if(nrow(cred_user)==1) {
        data_user(cred_user)
        shinyjs::hide("div_password")
        shinyjs::show("div_data")
        shinyjs::show("logout")
    } else {
        shinyjs::show(id = "login-error", anim = TRUE, animType = "fade")
        shinyjs::delay(2000, shinyjs::hide(id = "login-error", anim = TRUE, animType = "fade", time = 0.5))
    }
})

Memproses data sesuai user name dan user role (lihat file server.R):

# Output yang baru akan dibuat ketika user sudah login
output$text_user <- renderText({
    req(data_user())
    sprintf('Hi, %s',data_user()$username)
})
    
output$contohData <- renderTable({
    req(data_user()$username)
    df_data <- iris
    nama = data_user()$username
        
    if(nama!='admin') {
        df_data <- df_data %>% 
            filter(Species == nama)
    }
    df_data %>% group_by(Species) %>% summarise_all(list(Total = sum))
})

Menghapus Reactive Values dan kembali menyembunyikan konten setelah logout (lihat file server.R):

observeEvent(input$logout, {
    data_user(NULL)
    shinyjs::hide("logout")
    shinyjs::hide("div_data")
    shinyjs::show("div_password")
    shinyjs::reset('div_password')
})

 

2.6. Struktur Persistent Login

  1. Cek apakah user sudah login.

Mencari access token pada data cookies dan mengecek apakah cocok dengan user credential.

  1. Jika belum login (tidak ada cookies, atau cookies-nya tidak cocok).

-> User Login: User menginput user name dan password, lalu dikirimkan ke server.

-> Validasi Login: Mengecek apakah user sudah ada dan password sudah cocok dengan hashed password.

-> Generate Access Token: Jika user sudah cocok, lalu generate token yang secara unik mengidentifikasi session si user. Lalu, menyimpannya di user credential dan mengirimkannya ke cookies (dengan expiration time).

  1. User akan membuat request menggunakan token yang available di user credential.

Berikut ini adalah script untuk setiap tahapannya:

Setup Shiny untuk berinteraksi dengan cookies (buat direktori “www”, lalu download file “cookie.js”):

if (!dir.exists('www/')) {
    dir.create('www')
}

download.file(
    url = 'https://raw.githubusercontent.com/js-cookie/js-cookie/master/src/js.cookie.js',
    destfile = 'www/js.cookie.js'
)

Script js untuk mencari dan menghapus cookies (lihat file helper.R):

#Set Get and Remove cookies --------

jsCode <- '
shinyjs.getcookie = function(params) {
var cookie = Cookies.get("shiny_user_session");
if (typeof cookie !== "undefined") {
Shiny.onInputChange("jscookie", cookie);
} else {
var cookie = "";
Shiny.onInputChange("jscookie", cookie);
}
}

shinyjs.setcookie = function(params) {
/* expires after 12 hours  */
Cookies.set("shiny_user_session", escape(params), { expires: 0.5 });  
Shiny.onInputChange("jscookie", params);
}

shinyjs.rmcookie = function(params) {
Cookies.remove("shiny_user_session");
Shiny.onInputChange("jscookie", "");
}
'

Source js di Shiny menggunakan shinyjs (lihat file ui.R):

dashboardBody(
    shinyjs::useShinyjs(),
    tags$script(src = "js.cookie.js"),
    extendShinyjs(text = jsCode),
    uiOutput("ui")
)

Reactive Values untuk menyimpan data login (lihat file server.R):

# Reactive value untuk menyimpan jika sudah login dan data user yang login
sudah_login <- reactiveVal(value = FALSE)
data_user <- reactiveVal(value = NULL)

Cek apakah user sudah login (lihat file server.R):

# Cek apakah ada cookie yang tersimpan dan sesuai dengan data yang tersimpan
observe({
    js$getcookie()
    credentials <- readRDS("data/credentials.rds")
    session_cred <- credentials$session
        
    if (!is.null(input$jscookie) && input$jscookie!="" && input$jscookie %in% session_cred) {
        sudah_login(TRUE)
        row_email <- which(session_cred==input$jscookie)
        data_user(credentials[row_email,])
        shinyjs::show("logout")
    }
        
})

Validasi login dan generate access token (lihat file server.R):

cred_user <- df_credentials %>%
    filter(username == input$username)
            
if(nrow(cred_user)==0) {
    stop("Wrong username or password")
}
            
if (bcrypt::checkpw(input$password, hash = cred_user$password)) {
    sudah_login(TRUE)
    data_user(cred_user)
                
    sessionid <- paste(collapse = '', sample(x = c(letters, LETTERS, 0:9), size = 64, replace = TRUE))
    js$setcookie(sessionid)
    update_user_session(user = input$username,sessionid)
                
    shinyjs::show("logout")
                
}

Sekian field report dari kopdar yang sangat insightful ini, dan sampai jumpa di field reports berikutnya!

Stay tuned and enjoy! 🙂

 

References & Further Reading

Contoh Implementasi Shiny dengan Password.

Materi Kopi Darat useR! Indonesia.

Shiny Examples.

Using Cookie Based Authentication with Shiny.

Follow and like us:

1 tanggapan pada “Yang Saya Pelajari Ketika Menghadiri “Kopdar useR Indonesia Regional Jogja & Jateng” – Part 2: “User Level Access in Shiny Apps (Free)””

  1. Pingback: Yang Saya Pelajari Ketika Menghadiri “Kopdar useR Indonesia Regional Jogja & Jateng” - Part 1: "Graph Visualization and Network Analysis" - hakim-azizul.com

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *