NextcloudでBasic認証とクライアントアプリを両立する


## はじめに

※本投稿において、**クライアント認証**と記載があった場合はクライアント認証を、それ以外の**クライアント**という記載についてはNextcloudのクライアントアプリを指します。
まぎらわしいのでなるべく**クライアントアプリ**と書くようにしていたつもりですが、漏れがあったらすみません。

## 経緯
一年前からスマホやノートPCで写真やメモを共有するのにVPS上で[Nextcloud](https://nextcloud.com/)を使っていた。DropboxやGoogleDriveと比べるとマイナーなので手さぐり感はあったが、結果としては非常に便利で、とくに問題もなかったので、そろそろこれを自宅のファイルサーバへ統合したい。

これまで一時的なファイル置き場だったのが母艦への格上げとなれば、当然セキュリティにも気を遣う。Nextcloudを信用していないわけではないが、いくら戸締りが万全だからといって、ドアにアクセスし放題というのは気味がわるいので、Basic認証やクライアント認証で自分以外のアクセスはWebアプリの手前でシャットアウトしたい。

そこで試しにBasic認証を設定したところクライアントアプリが接続できなくなってしまったので、なんとか両立できないか試行錯誤してみた。



## 構成
- サーバ側
    - AWS Lightsail
    - Nginx
    - Nextcloud 19.0.2
- クライアント側
    - Nextcloud デスクトップクライアント 2.6.5 (Windows)
    - Nextcloud Androidアプリ 3.13.0

後述の解決策（妥協案）に関係する点について補足すると、

- 今回はAndroidとWindowsの2種類しか試していない。とはいえ後述のやり方自体はほかのOSでも通用すると思う。
- 自分一人しか利用しない想定。とくにAndroidクライアントについては本記事のやり方だと機種ごとに例外を設定する必要が出てくるため、複数人での利用は難しいと思われる。
   
## おおまかな結論
クライアントアプリからのBasic認証は結局できなかった。
そこでNextcloudのクライアントアプリからのアクセスのみBasic認証の対象外にすることで妥協した。。
クライアントアプリからのアクセスはユーザーエージェントで判別する。

条件分けとしては以下の通り

- Basic認証対象
    - 条件: なし、デフォルト
        - 例1: ブラウザからのアクセス
        - 例2: Windows用のクライアントのログインなど
- Basic認証から除外する項目
    - 条件1: UserAgentにNextcloudを含むアクセス。
        - 例：クライアントアプリからのアクセス（windows, android)
    - 条件2: UserAgentにAndroidの端末名を含む（？）アクセス
        - 例：androidクライアントの認証

これでクライアントアプリを使いながら（ブラウザを含む）それ以外のアクセスはBasic認証を要求できるようにできた。
後述のようにBasic認証対象外のユーザーエージェントはかなり限られるので、個人的には妥協案としては十分だと思っているがどうだろうか。

## 解説 
### 概要
ブラウザであれば当たり前にできるBasic認証だが、http通信をするからといって必ずしも実装しなければいけないわけではないらしく、事実クライアントアプリは対応していなかった。
今回の方針としてはクライアントアプリのアクセスをどうにかして特定し、Basic認証の対象外としたい。

Nextcloudを動かしているWebサーバのログを見たところ、以下のようにクライアントアプリと思わしきユーザーエージェントが見られた。

```
Mozilla/5.0 (Windows) mirall/3.0.1stable-Win64 (build 20200828) (Nextcloud)
Mozilla/5.0 (Android) Nextcloud-android/3.13.0
```

ということで判定の条件としてはユーザーエージェントを利用する

### クライアントアプリからのアクセス（Android、Windows共通）
ユーザーエージェントの例: 

- Mozilla/5.0 (Windows) mirall/3.0.1stable-Win64 (build 20200828) (Nextcloud)
- Mozilla/5.0 (Android) Nextcloud-android/3.13.0

これらのアクセスは**Basic認証から除外**する。

後述するがこれは認証後の通常のアクセスであり、これだけだとログインができない。その意味では安全だともいえる。

### ログイン（Windowsクライアント）
ユーザーエージェントの例

- Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0

Windowsのクライアントの場合、ログインの際は既定のブラウザへリダイレクトされる。（自分の環境ではFirefox）そのためログインの際は**問題なくBasic認証を行える**。安心。
ちなみにあくまでもログイン時のフォーム入力を別ブラウザで行うというだけで裏でクライアントとのやり取りも発生してるらしく、上記のクライアントアプリからのアクセスで認証を無効にしていないと認証は完了しない。

### ログイン（Androidクライアント）
既定のブラウザでログイン画面を開くPCと違って、AndroidのクライアントはChromeでもFirefoxでもない謎のブラウザ画面が出る。この際のアクセスについてUserAgentを調べたところ、以下のようになっていた

- Samsung SM-N9700 (Android)

Samsungとなっているのでもしやと思って調べたら、やはり自分のスマホ（[Galaxy Note 10](https://www.samsung.com/hk_en/smartphones/galaxy-note10/)）の型番だった。

謎ブラウザ画面の正体はともかく、残念ながらBasic認証には対応していないため、これも**Basic認証の対象から外す**必要がある。

このUserAgentについては

- ChromeやFirefoxのような一般的なブラウザとは違う
- クライアントアプリ自体のユーザーエージェントと異なっている。
- 機種の型番に由来している

ということで普通のログとしても出現頻度としてはかなり珍しいうえ、（FirefoxやChromeならまだしも）このAndroid機種固有のユーザーエージェント名をピンポイントに使っての不正アクセスというのも考えにくい。
ということで、これを認証の対象外にすることについてもセキュリティ上のリスクは低いと判断。

## 具体的なNginxの設定例

```
map $http_user_agent $authentication {
    default         "Access Restricted";
    "~Nextcloud" "off"; 
    "~Samsung SM-N9700" "off";
}   
server {
    （略）
	server_name nextcloud.example.com;
    auth_basic             $authentication;                                                             
    auth_basic_user_file   /etc/nginx/htpasswd;
    （略）
}
```

`map`ディレクティブで`$http_user_agent`によって`auth_basic`のオンオフを切り替えるように設定している。
この設定だとユーザーエージェントに`Nextcloud`と含まれると認証が無効になる。
今回はAndroidとWindowsで設定分けるのが面倒でこうしたが、より安全にやるなら上記のユーザーエージェントに完全一致するようにすればいい。
そこまでするとクライアントアプリのバージョンアップの度に修正が必要になるが自分一人の分であればまあなんとかなるか。

手探りでやった割には妥協案としてはいい塩梅になったと思う。
処理自体はシンプルなのでほかの認証でも応用出来そうだし、個人的にはクライアント認証にしたいのでそのうち試す。


## その他試した（そしてうまくいかなかった）こと

※以降にGithub issueなど英語のページを参考にした個所がありますが、筆者の英語力が半端なうえ流し読みなので正確性は保証しません、悪しからず…

### URLにBasic認証情報をつけて設定する
Basic認証は`https://username:password@nextcloud.example.com`という形式でアドレスを指定することで、ダイアログなしに認証できるという構文（？）がある。Nextcloudサーバをこれで指定すればWebクライアントでBasic認証できるのではないかとおもったが、だめだった。

以下を見る限り以前はこの書き方で要Basic認証のドメインでもクライアントアプリで対応していたらしいが、最新のではできなくなった模様。
[Basic auth not working with client · Issue #2046 · nextcloud/desktop](https://github.com/nextcloud/desktop/issues/2046)

以下はIEについての記事だが、これによると以前は対応していたがこのURLの構文が悪用された結果対応しなくなったらしい。
[URL 構文にはユーザー名とパスワードが含まれていません | Microsoft Docs](https://docs.microsoft.com/ja-jp/troubleshoot/browsers/name-and-password-not-supported-in-website-address)


### status.phpをBasic認証対象から外す
`status.php`へのアクセスは認証を外す必要があるという情報があったが、自分の環境ではそれだけではうまくいかなかった。
[HTTP Basic Authentication · Issue #371 · nextcloud/android](https://github.com/nextcloud/android/issues/371)
このページの下の方に新しい認証フローがBasic認証に非対応との旨があったので、やはり以前はできたが今はできないという感じか...。


### Nginxでif文によって認証の有無を変更する

Nginxでは`if`文が使えるので以下のようにBasic認証を設定しようとしたらエラーになった。

```
server {
    if ( $http_user_agent  !~ Nextcloud ){
        auth_basic   "Secret site";
        auth_basic_user_file   /etc/nginx/htpasswd;
    }
}
```

以下によると`if`文内では`auth_basic`は設定できないらしい。その代わり`map`で代用できるらしいということで試してうまくいったのは上記の通り。
[Basic Authentication for All Except Listed User Agents in nginx - Stack Overflow](https://stackoverflow.com/questions/45264521/basic-authentication-for-all-except-listed-user-agents-in-nginx)


## その他参考資料
[8.2.3 if](http://www2.matsue-ct.ac.jp/home/kanayama/text/nginx/node73.html)
[Login Flow — Nextcloud latest Developer Manual latest documentation](https://docs.nextcloud.com/server/19/developer_manual/client_apis/LoginFlow/index.html)
[ユーザーエージェント - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88)