shinymanagerパッケージを使ってログインページを持つアプリを作成する

[`shinymanager`](https://datastorm-open.github.io/shinymanager/)パッケージはshinyアプリにログインの仕組みを組み込むためのライブラリです。

`shinymanager`をデータベースを利用して用いる場合には、`keyring`パッケージが必要となりますので、`keyring`パッケージもインストールします。

```r
install.packages("shinymanager")
install.packages("keyring")
```

`shinymanager`でパスワードを設定する方法は主に2つあります。

1. Rスクリプト上にデータフレームとしてパスワードを設定して用いる
2. データベースを用いてパスワードを管理する

まずは1について説明し、次に2について説明します。

## データフレームでパスワードを設定する

データフレームでパスワードを設定する場合には、`user`、`password`、`start`、`expire`、`admin`、`comment`を`admin`を除いてそれぞれ文字列としたデータフレームを作成します。必須なのは`user`と`password`です。このデータフレームを後ほど`server`側で呼び出し、パスワードとして設定します。

```r
credentials <- data.frame(
  user = c("shiny", "shinymanager"), # ユーザー名
  password = c("azerty", "12345"), # パスワード
  start = c("2019-04-15"),
  expire = c(NA, "2026-12-31"),
  admin = c(FALSE, TRUE),
  comment = "Simple and secure authentification mechanism 
  for single ‘Shiny’ applications.",
  stringsAsFactors = FALSE
)

credentials
##           user password      start     expire admin                                                                             comment
## 1        shiny   azerty 2019-04-15       <NA> FALSE Simple and secure authentification mechanism \n  for single ‘Shiny’ applications.
## 2 shinymanager    12345 2019-04-15 2026-12-31  TRUE Simple and secure authentification mechanism \n  for single ‘Shiny’ applications.
```

次に、`ui`と`server`にログインページに必要となるものを付け加えます。このプロセスは簡単で、`ui`は`sequre_app`の引数とするだけ、`server`には`res_auth`という変数を設定するだけです。

```r:app.R
ui <- fluidPage(...)

ui <- secure_app(ui, language="ja") # ログインページの表示

server <- function(input, output, session) {
  res_auth <- secure_server( # 認証をおこなう部分
    check_credentials = check_credentials(credentials)
  )
  
  ...

}

shinyApp(ui, server)
```

設定して`app.R`を起動すると、以下のようにログインページが表示されます。`sequre_app`のヘルプには日本語が使えるような記載はありませんが、ver.1.0.410から日本語が使用できるようになっています。フォントと記載がやや不自然なような気もしますので、英語のままでよければ`language`を引数として設定しなければよいでしょう。

![shinymanager_loginimage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80714/d7ba0445-d5a4-1a94-b080-592aedaeb2ed.png)

## データベースを用いてパスワードを設定する

上記のように、パスワードを直書きすると簡単にログイン機構をshinyに追加することができます。また、データベースを用いてパスワードを管理することもできます。データベースを用いてパスワードを管理するためには、まずsqliteを用いてデータベースファイルを作成します（sqlite以外のデータベースを用いることもできます）。

```r
credentials <- data.frame(
  user = c("shiny", "shinymanager"),
  password = c("azerty", "12345"),
  admin = c(FALSE, TRUE),
  stringsAsFactors = FALSE
)

# データベースのキーを設定する
library(keyring)
if(keyring_list() |> _$keyring |> str_detect("foo") |> sum() < 1){
  keyring_create("foo", password="obiwankenobi")
}
key_set_with_value("R-shinymanager-key", "obiwankenobi", password = "obiwankenobi", keyring="foo")

# データベースを作成する
create_db(
  credentials_data = credentials,
  sqlite_path = "password.sqlite",
  passphrase = key_get("R-shinymanager-key", "obiwankenobi")
  # passphrase = "passphrase_wihtout_keyring"
)
```

データベースのキーとパスワードの設定には`keyring`パッケージを用います。`keyring`については以下のページに説明があります。OS側の仕組みを用いてパスワードを隠蔽することができるパッケージです。

[keyringパッケージで認証情報を管理する](https://qiita.com/nozma/items/9f4a638906471d125e96)

事前に`keyring_create`関数でキーとして保存する`keyring`というものを設定しておきます。次に、`keyring`でキーとパスワードを設定するには、`key_set`関数を用います。`key_set`関数ではポップアップするUIでパスワードを設定することになります。UIではなくパスワードを直書きする場合には、`key_set_with_value`関数を用います。パスワードを管理するデータベースは`create_db`関数で作成します。`create_db`関数の引数として、パスワードをデータフレームとして設定した`credentials_data`、データベースのパスである`sqlite_path`（ファイル名だけの時はワーキングディレクトリに作成）、キーとパスワードを設定する`passphrase`の3つです。`passphrase = "passphrase_wihtout_keyring"`を設定すると`keyring`無しでのデータベースも利用できます。

データベースが準備できたら、app.Rに認証機構を付け加えます。`sequre_app`関数内で`enable_admin=TRUE`と設定すると、管理者権限を与えたユーザーではAdministration modeを利用することができます。server側では、`sequre_server`の引数を以下のように設定します。データベースのパスは相対パスではなく、絶対パスで書く必要があります。

```r:app.R
ui <- fluidPage(...)

ui <- secure_app(ui, enable_admin = TRUE, language="ja") # ログインページの表示

server <- function(input, output, session) {
  res_auth <- secure_server(
    check_credentials = check_credentials(
      "path/password.sqlite", # データベースまでの絶対パス
      passphrase = key_get("R-shinymanager-key", "obiwankenobi", keyring = "foo")
      # passphrase = "passphrase_wihtout_keyring"
    )
  )

  ...

}
```

上の`app.R`を実行すると以下のイメージのように、アプリの右下から管理者ページに入ることができます。管理者ページではユーザー・パスワードの管理ができます。

![adminmode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80714/2474573e-fbdb-364f-c8e0-ebdbd8f6e741.png)

![adminmode_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80714/b9557d95-2946-7ecb-e347-f865e4443dd8.png)


## Dockerでshinymanagerを動かす

shinyアプリをデプロイするため、Dockerで`shinymanager`が動くようにします。`keyring`のインストールには[上記のkeyringの記事](https://qiita.com/nozma/items/9f4a638906471d125e96)に記載の通り、`libssl-dev`、`libsodium-dev`、`libsecret-1-dev`の3つが必要です。この3つを加えて`Dockerfile`を準備します。

```Dockerfile:Dockerfile
# get shiny server plus tidyverse packages image
FROM rocker/shiny-verse:latest

# system libraries of general use
RUN apt-get update && apt-get install -y \
    curl \
    sudo \
    pandoc \
    pandoc-citeproc \
    libcurl4-gnutls-dev \
    libcairo2-dev \
    libxt-dev \
    libssl-dev \
    libssh2-1-dev\
    libssl3 \
    libsecret-1-dev \
    libsodium-dev \
    ## clean up
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/ \
    && rm -rf /tmp/downloaded_packages/ /tmp/*.rds

# install R packages required 
# (change it depending on the packages you need)
RUN R -e "install.packages('shiny')"
RUN R -e "install.packages('shinymanager')"
RUN R -e "install.packages('keyring')"

# clean up
RUN rm -rf /tmp/downloaded_packages/ /tmp/*.rds

# Copy configuration files into the Docker image
COPY shiny-server.conf  /etc/shiny-server/shiny-server.conf

# Copy shiny app into the Docker image
COPY app /srv/shiny-server/

RUN rm /srv/shiny-server/index.html

# Make the ShinyApp available at port 3838
EXPOSE 5000

# Copy shiny app execution file into the Docker image
COPY shiny-server.sh /usr/bin/shiny-server.sh

RUN ["chmod", "+x", "/usr/bin/shiny-server.sh"]

USER shiny

CMD ["/usr/bin/shiny-server"]
```

パスワードを保存したデータベースファイルは`app`フォルダ内に保存し、`/srv/shiny-server/`にコピーしています。ですので、`app.R`の`server`内のデータベースの絶対パスは`/srv/shiny-server/password.sqlite`とします。

```r:app.R
library(shiny)
library(tidyverse)
library(shinymanager)
library(keyring)

if(keyring_list() |> _$keyring |> str_detect("foo") |> sum() < 1){
  keyring_create("foo", password="obiwankenobi")
}

key_set_with_value("R-shinymanager-key", "obiwankenobi", "obiwankenobi", keyring="foo")

...

server <- function(input, output, session) {
  res_auth <- secure_server(
    check_credentials = check_credentials(
      "/srv/shiny-server/password.sqlite", # データベースまでの絶対パス
      passphrase = key_get("R-shinymanager-key", "obiwankenobi", keyring = "foo")
      # passphrase = "passphrase_wihtout_keyring"
    )
  )

  ...

}
```

`app.R`内で`keyring`を設定しているのはイケてないので、別途Rファイルを準備し、`Dockerfile`内で実行後、Rファイルを削除するような形の方が良いでしょう。フォルダ構造は以下のような形とします。

shinymanager_trial
|- app
| |- app.R
| |- password.sqlite
| - Dockerfile
| - shiny-server.conf
| - shiny-server.sh

この形で`docker build` → `docker run`とすることで、ログインページ付きのshinyをDockerから走らせることができます。