R ShinyでWebアプリを作成すると、利用者によって表示する画面を変えたくなることがあります。
例えば、大学の授業でShinyアプリを使用する場合、学生には教材や問題を表示し、教員には回答の集計結果や管理機能を表示するといった使い方が考えられます。
今回は、学生用画面と教員用管理画面を分け、パスワードを入力した場合だけ管理画面を表示するShinyアプリを作成します。
初心者でも試しやすいように、今回は app.R という1つのファイルだけで動作する構成にしました。
コードをコピーして保存し、RStudioの「Run App」をクリックするだけで起動できます。
今回のアプリには、次の機能を実装します。
- 学生用画面はパスワードなしで利用できる
- 教員用画面にはパスワード入力欄を設ける
- 正しいパスワードを入力すると管理画面が表示される
- 管理画面には架空の集計データを表示する
- ログアウトすると再びパスワード入力画面に戻る
- ブラウザのセッションごとにログイン状態を管理する
なお、今回は仕組みを理解するためのサンプルです。本格的な利用者認証システムを構築するものではありません。
R Shinyの基本的な使い方については、以前の記事も参考にしてください。
1. 学生用画面と教員用画面を分ける理由
例えば、Shinyを使って薬理学の学習アプリを作成した場合を考えてみます。
学生には、薬物投与量を変更するスライダーや、計算結果を確認するボタンを表示します。
一方、教員には、学生の回答結果や進捗状況を確認する画面を表示したいかもしれません。
このような場合、すべての利用者に同じ画面を表示する必要はありません。
R Shinyアプリ
│
┌─────────┴─────────┐
│ │
学生用画面 教員用画面
│ │
パスワード不要 パスワード認証
│ │
教材を操作 管理データを表示
結果を確認 ログアウト
ただし、ここで注意したいことがあります。
管理画面を見えなくすることと、管理機能へのアクセスを禁止することは別です。
例えば、画面上のタブを非表示にしただけでは、サーバー側で管理者用のデータを提供する処理が残っている可能性があります。
そのため、管理機能を保護する場合は、画面の表示だけでなく、データを取得する処理でも認証状態を確認する必要があります。
2. 今回作成するアプリ
今回のアプリは、学生用画面と教員用画面の2つのタブで構成します。
学生用画面では、名前を入力し、スライダーで数値を選択して、結果を表示できます。
教員用画面では、最初にパスワード入力欄が表示されます。
正しいパスワードを入力すると、管理用のサンプルデータが表示されます。
今回は動作確認用として、次のパスワードを使用します。
初期パスワード:demo1234
このパスワードはサンプルコードに含まれており、公開されているため、実際の認証には使用しないでください。
今回のアプリは、自分のPC上で認証機能の仕組みを学ぶことを目的としています。
なお、管理画面に表示するデータは架空のものであり、学生が入力した情報を保存する機能は実装しません。
3. 必要な環境
今回使用するのは、R、RStudio、Shinyパッケージです。
Shinyをまだインストールしていない場合は、RStudioのConsoleで次のコードを実行してください。
# Shinyパッケージをインストールする
# 最初の1回だけ実行すればよい
install.packages("shiny")
すでにShinyをインストールしている場合は、この操作を省略できます。
今回のサンプルアプリを起動するために、パスワードをConsoleへ事前入力する必要はありません。
4. サンプルアプリのコード
RStudioで「File」→「New File」→「R Script」を選択し、新しいファイルを作成します。
以下のコードをすべてコピーして貼り付けてください。
# ==================================================
# R Shiny 学生用・教員用画面の切り替えサンプル
# ==================================================
# Shinyパッケージを読み込む
library(shiny)
# --------------------------------------------------
# 1. 管理者パスワードを設定する
# --------------------------------------------------
# 今回は動作確認用の初期パスワードを用意する
# このままコピーしてもアプリを起動できる
# Consoleで別のパスワードを設定した場合は、
# そちらを優先して使用する
admin_password <- Sys.getenv(
"SHINY_ADMIN_PASSWORD",
unset = "demo1234"
)
# 空のパスワードは使用しない
if (!nzchar(admin_password)) {
stop("管理者パスワードが空欄です。")
}
# --------------------------------------------------
# 2. 管理画面用の架空データを作成する
# --------------------------------------------------
# 実際の学生データは保存しない
# 動作確認用のデータを用意する
sample_data <- data.frame(
student = c("学生A", "学生B", "学生C"),
score = c(80, 90, 70),
completed = c(TRUE, TRUE, FALSE)
)
# --------------------------------------------------
# 3. アプリの画面を作成する
# --------------------------------------------------
ui <- fluidPage(
# アプリのタイトル
titlePanel("R Shiny 学生・教員画面テスト"),
# 説明文
p(
"学生用画面は誰でも利用できます。",
"教員用画面にはパスワードが必要です。"
),
# 学生用と教員用のタブを作成する
tabsetPanel(
# ----------------------------------------------
# 学生用画面
# ----------------------------------------------
tabPanel(
title = "学生用画面",
h3("学生用の操作画面"),
# 名前の入力欄
textInput(
inputId = "student_name",
label = "名前を入力してください",
value = ""
),
# 数値を選択するスライダー
sliderInput(
inputId = "dose",
label = "数値を選択してください",
min = 0,
max = 100,
value = 50
),
# 結果表示ボタン
actionButton(
inputId = "student_submit",
label = "結果を表示"
),
hr(),
# 結果を表示する場所
verbatimTextOutput("student_result")
),
# ----------------------------------------------
# 教員用画面
# ----------------------------------------------
tabPanel(
title = "教員用画面",
h3("教員用管理画面"),
# ログイン状態に応じて表示を変更する
uiOutput("admin_ui")
)
)
)
# --------------------------------------------------
# 4. サーバー側の処理
# --------------------------------------------------
server <- function(input, output, session) {
# ----------------------------------------------
# 4-1. ログイン状態を管理する
# ----------------------------------------------
# FALSE:未認証
# TRUE :認証済み
#
# server関数内に置くことで、
# ブラウザのセッションごとに独立して管理する
authenticated <- reactiveVal(FALSE)
# ログイン時のメッセージ
login_message <- reactiveVal("")
# ----------------------------------------------
# 4-2. 学生用画面の処理
# ----------------------------------------------
observeEvent(input$student_submit, {
# ボタンを押した時点の入力値を取得する
student_name <- input$student_name
student_dose <- input$dose
# 名前が空欄なら「ゲスト」とする
if (trimws(student_name) == "") {
student_name <- "ゲスト"
}
# 入力内容を表示する
output$student_result <- renderText({
paste0(
"こんにちは、", student_name, "さん。\n",
"選択した数値は ",
student_dose,
" です。"
)
})
})
# ----------------------------------------------
# 4-3. 教員用画面の表示を切り替える
# ----------------------------------------------
output$admin_ui <- renderUI({
if (!authenticated()) {
# 未認証の場合はログイン画面を表示する
tagList(
p("管理者パスワードを入力してください。"),
passwordInput(
inputId = "admin_input",
label = "管理者パスワード",
value = ""
),
actionButton(
inputId = "login",
label = "ログイン"
),
br(),
textOutput("login_message")
)
} else {
# 認証済みの場合は管理画面を表示する
tagList(
p("管理者としてログインしています。"),
actionButton(
inputId = "logout",
label = "ログアウト"
),
hr(),
h4("サンプル集計データ"),
tableOutput("admin_table"),
p(
"※表示しているのは架空のデータです。",
"実際の学生の回答は保存していません。"
)
)
}
})
# ----------------------------------------------
# 4-4. ログインメッセージの表示
# ----------------------------------------------
output$login_message <- renderText({
# メッセージの内容を表示する
login_message()
})
# ----------------------------------------------
# 4-5. ログイン処理
# ----------------------------------------------
observeEvent(input$login, {
# 入力されたパスワードを取得する
entered_password <- input$admin_input
# 空欄の場合
if (is.null(entered_password) ||
!nzchar(entered_password)) {
login_message("パスワードを入力してください。")
return()
}
# パスワードを照合する
if (identical(entered_password, admin_password)) {
# 正しい場合は認証済みにする
authenticated(TRUE)
# エラーメッセージを消去する
login_message("")
} else {
# 間違っている場合は未認証のまま
authenticated(FALSE)
# エラーメッセージを表示する
login_message("パスワードが正しくありません。")
}
})
# ----------------------------------------------
# 4-6. ログアウト処理
# ----------------------------------------------
observeEvent(input$logout, {
# 認証状態を解除する
authenticated(FALSE)
# メッセージを初期化する
login_message("")
})
# ----------------------------------------------
# 4-7. 管理者用データの表示
# ----------------------------------------------
output$admin_table <- renderTable({
# 未認証ならデータを返さない
req(authenticated())
# 認証済みの場合だけデータを表示する
sample_data
})
}
# --------------------------------------------------
# 5. アプリを起動する
# --------------------------------------------------
shinyApp(
ui = ui,
server = server
)
app.Rという名前で保存する
作成したコードを app.R という名前で保存してください。
例えば、デスクトップに Shiny_Admin_Test というフォルダを作成し、その中に保存します。
Shiny_Admin_Test
└── app.R
今回のサンプルアプリは、この1ファイルだけで動作します。
5. アプリを実行する
今回のアプリは、パスワードの事前設定を行わなくても起動できます。
① Run Appをクリックする
RStudioで保存した app.R を開き、「Run App」をクリックしてください。
正常に起動すると、学生用画面と教員用画面の2つのタブが表示されます。
② 学生用画面を確認する
まず「学生用画面」を開いてください。
名前を入力し、スライダーで数値を選択して「結果を表示」をクリックします。
入力内容が表示されれば、学生用画面は正常に動作しています。
③ 教員用画面を確認する
次に「教員用画面」をクリックします。
最初は、管理者パスワードの入力欄とログインボタンが表示されます。
今回は初期パスワードとして、次の文字列を使用します。
demo1234
パスワードを入力して「ログイン」をクリックしてください。
正しく認証されると、管理画面が表示されます。
ここでは、学生A・学生B・学生Cの架空の集計データを確認できます。
④ ログアウトする
管理画面の「ログアウト」をクリックしてください。
管理用の表が消え、再びパスワード入力画面が表示されます。
ログアウト後は、管理者用データを表示する処理でも認証が必要になります。
⑤ 別のブラウザで確認する
例えば、Chromeで管理者としてログインした状態で、Edgeから同じアプリを開いてみます。
Edge側では、新しいShinyセッションが作成されるため、通常は未認証の状態から始まります。
つまり、あるブラウザでログインしても、別のブラウザが自動的に管理者になるわけではありません。
なお、別のブラウザでアクセスする場合も、ShinyアプリをRStudio上で起動したままにしておく必要があります。
6. 管理者パスワードを変更する方法
今回のアプリでは、初期パスワードとして demo1234 を使用しています。
しかし、毎回同じパスワードを使う必要はありません。
自分で決めたパスワードに変更したい場合は、アプリを起動する前にRStudioのConsoleで設定します。
① Consoleでパスワードを設定する
RStudioのConsoleで、次のコードを実行してください。
# 管理者パスワードを設定する
# 文字列を自分のパスワードに変更する
Sys.setenv(
SHINY_ADMIN_PASSWORD = "ここに自分のパスワード"
)
"ここに自分のパスワード" の部分を、自分で決めたパスワードに変更します。
② パスワードが設定されたか確認する
次のコードを実行してください。
# パスワードが設定されているか確認する
# パスワード自体は表示しない
nzchar(Sys.getenv("SHINY_ADMIN_PASSWORD"))
TRUE と表示されれば、パスワードが設定されています。
③ Run Appをクリックする
RStudioで app.R を開き、「Run App」をクリックしてください。
これで、Consoleで設定したパスワードが使用されます。
初期パスワードの demo1234 ではログインできなくなり、自分で設定したパスワードを入力する必要があります。
④ 設定したパスワードを解除する
再び初期パスワードで試したい場合は、Shinyアプリを停止した後、Consoleで次のコードを実行してください。
# 設定した環境変数を解除する
Sys.unsetenv("SHINY_ADMIN_PASSWORD")
その後、アプリを再起動すると、初期パスワードの demo1234 が使用されます。
注意点
Consoleで設定した環境変数は、RStudioを終了したりRセッションを再起動したりすると、通常は失われます。
また、Consoleに直接入力したパスワードは、Rのコマンド履歴などに残る可能性があります。
そのため、今回の方法はローカルPCでの動作確認を目的としています。
7. コードの重要な部分を解説
今回のコードで特に重要なのは、Sys.getenv()、reactiveVal()、renderUI()、observeEvent()、req() の5つです。
Sys.getenv():管理者パスワードを読み込む
admin_password <- Sys.getenv(
"SHINY_ADMIN_PASSWORD",
unset = "demo1234"
)
Sys.getenv() は、Rの環境変数を読み取る関数です。
今回のコードでは、SHINY_ADMIN_PASSWORD という環境変数が設定されていれば、その値を使用します。
環境変数が設定されていない場合は、unset = "demo1234" によって、初期パスワードを使用します。
この仕組みにより、コードをコピーしただけでもアプリを起動できます。
また、必要に応じてConsoleからパスワードを変更できます。
reactiveVal():ログイン状態を記録する
authenticated <- reactiveVal(FALSE)
reactiveVal() は、Shinyの中で値を保持し、その値が変更されたときに関連する処理を更新するための関数です。
今回は、管理者としてログインしているかどうかを TRUE または FALSE で記録しています。
初期状態では FALSE なので、管理機能は利用できません。
正しいパスワードが入力された場合に、
authenticated(TRUE)
とすることで、認証済みの状態に変更します。
renderUI():ログイン状態に応じて画面を変更する
output$admin_ui <- renderUI({
if (!authenticated()) {
# ログイン画面を表示する
} else {
# 管理画面を表示する
}
})
renderUI() は、サーバー側の処理によって画面の部品を動的に作成する関数です。
今回は、認証されていない場合にパスワード入力欄を表示し、認証された場合に管理画面を表示しています。
これにより、学生と教員で異なる画面を利用できます。
observeEvent():ボタン操作を検出する
observeEvent(input$login, {
# ログインボタンを押したときの処理
})
observeEvent() は、ボタン操作などのイベントを検出して処理を実行するための関数です。
今回のアプリでは、ログイン、ログアウト、学生用の結果表示に使用しています。
この仕組みを応用すれば、回答確定ボタンを押した時刻や、問題を進めた順序なども記録できます。
ただし、記録を残すには、別途ファイルやデータベースへの保存処理が必要です。
req():認証されていない場合は処理を進めない
今回のコードで、特に重要なのが次の部分です。
output$admin_table <- renderTable({
req(authenticated())
sample_data
})
req() は、指定した条件が満たされていない場合に、Shinyの処理を中断するための関数です。
今回は、authenticated() が TRUE の場合だけ、管理者用のデータを表示するようにしています。
つまり、管理画面を隠すだけでなく、サーバー側でも認証状態を確認しています。
この考え方は、実際の管理用アプリを作成する際にも重要です。
8. 管理画面を非表示にするだけでは不十分
Shinyには、タブを非表示にしたり、画面の一部を隠したりする機能があります。
これらは、画面を分かりやすく整理するうえでは便利です。
しかし、表示を隠すこと自体は、アクセス制御ではありません。
例えば、管理者用のデータをすべての利用者に送信しておき、画面上だけで非表示にしている場合、データを適切に保護できていません。
そのため、管理者用の処理には、サーバー側で認証状態を確認する仕組みが必要です。
今回のサンプルでは、管理用の表を生成する処理で req(authenticated()) を使用しています。
実際にアプリを拡張する場合も、管理者専用のデータ取得、ファイルのダウンロード、データの削除・変更など、それぞれの処理で権限を確認する必要があります。
9. このパスワード認証は安全なのか?
ここは重要な注意点です。
今回のコードは、Shinyでログイン状態を管理する仕組みを学ぶための簡易的な認証サンプルです。
実際の大学の授業や研究システムに、そのまま導入することを推奨するものではありません。
初期パスワードは公開されている
今回のコードには、動作確認用の初期パスワード demo1234 が含まれています。
このパスワードは記事を読んだ人なら誰でも知ることができるため、実際の管理者認証には使用できません。
また、Consoleで別のパスワードを設定しても、それだけで本格的な認証システムになるわけではありません。
HTTP通信ではパスワードが暗号化されない
前回の記事では、LAN内の別PCからShinyアプリにアクセスする方法を紹介しました。
しかし、そこで使用したHTTP通信は、HTTPSのように通信経路を暗号化しません。
今回のパスワード入力機能をHTTP通信で利用すると、パスワードが通信経路上で保護されません。
したがって、このサンプルは、まず自分のPCの 127.0.0.1 で動作確認してください。
実際にネットワーク経由で利用する場合は、HTTPS対応の環境を用意する必要があります。
パスワードの総当たり攻撃への対策は未実装
今回のコードには、ログイン試行回数の制限や、一定回数失敗した場合のロック機能はありません。
そのため、外部に公開する本格的な認証システムとしては不十分です。
また、複数の管理者アカウントや、利用者ごとの権限設定にも対応していません。
本格的に運用する場合は、適切な認証基盤や、認証機能を備えたホスティング環境を利用することを検討してください。
10. 学生の回答や進捗管理にも応用できる?
今回のアプリでは、管理画面に架空の集計データを表示しました。
実際の学生の回答を収集する場合には、Shinyのイベント処理とデータ保存機能を組み合わせます。
例えば、学生が問題に回答したときに、回答内容や時刻を記録することができます。
また、教材のページ移動やボタン操作を記録すれば、学習の進捗を確認する仕組みも作れます。
ただし、今回のサンプルアプリには、学生の回答や操作履歴を保存する機能は実装していません。
アプリの再起動後もデータを残すには、ファイルやデータベースなどへの保存が必要になります。
さらに、学生の個人情報や成績を扱う場合は、HTTPS、利用者認証、アクセス権限、データの保存場所などを適切に設計する必要があります。
これらについては、今後の記事で段階的に紹介します。
11. まとめ
今回は、R Shinyで学生用画面と教員用管理画面を分ける方法を紹介しました。
今回のサンプルでは、app.R をコピーして保存し、「Run App」をクリックするだけで動作を確認できます。
初期パスワードは demo1234 です。
必要に応じて、Consoleで Sys.setenv() を実行することで、別のパスワードに変更することもできます。
重要なのは、次の2点です。
1つ目は、reactiveVal() と renderUI() を組み合わせることで、ログイン状態に応じて表示内容を変更できることです。
2つ目は、管理者用の機能を保護するためには、画面の表示だけでなく、サーバー側でも認証状態を確認する必要があることです。
今回のサンプルでは、req(authenticated()) を使用して、未認証の利用者に管理用データを提供しないようにしました。
ただし、今回の認証方式は学習用の簡易的なものであり、実際の学生データを安全に管理するための完成された仕組みではありません。
まずはローカルPCで、学生用画面、ログイン、管理画面、ログアウトの動作を確認してみてください。
次回は、R ShinyにHTMLで作成した教材を組み込む方法を紹介します。
例えば、文章や図を使った学習教材をShinyアプリ内に表示し、教材を読みながら問題に回答できる画面を作成する方法です。


コメント