Webアプリケーションの特定のパスに対し、NGINXでBasic認証を行う
はじめに
こんにちは。SHIFTの片山です。
PoC で開発していた Web アプリを社内で公開することになった際、運営・運用を行う管理者のみが開ける画面を保護したいという要求が出てきた。
アプリケーション側で制御することも考えたが、アプリケーションがユーザー認証なく利用できるタイプであったという点と、PoC であるという点で NGINX をリバースプロキシとして挟んで、NGINX で Basic 認証を行うことを考えた。
今回はどのようにすれば NGINX で Basic 認証を行えるか?についてまとめてみたいと思う。
Web アプリケーションの構成と Deploy 先について
まず今回、Basic 認証を設けようとしているアプリについて軽く紹介する。アプリは SveltKit で実装されたユーザーからの質問に回答する Bot。社内からの問い合わせを過去の Q&A データや FAQ サイトのデータと ChatGPT を組み合わせて、人間への問い合わせを減らす目的で作成したものになる。
以下の図の通り、SvelteKit のアプリは MySQL・Redis に依存するサービスとして実装されている。ただ、PoC という事で低コストで運用できる AWS EC2 上に Deploy し、かつ雑に docker-compose で各依存するサービスを立ち上げる方法を取ることにした。
NGINX で Basic 認証を設定する
NGINX で Basic 認証を行うには、auth_basic ディレクティブを利用する。詳細は公式のリファレンスに記載がある通り。
今回は"/admin"のパスにおいて認証をかけたいので、以下のような設定になった。
server {
listen 80;
server_name example.com;
location / {
proxy_pass_header Server;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app:3000;
}
location /admin {
auth_basic "Administrator’s Area";
auth_basic_user_file /etc/apache2/.htpasswd;
proxy_pass_header Server;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app:3000;
}
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
EC2 の上段には Cloudfront などの CDN を置き、そこまでを HTTPS としていたので NGINX へのリクエストは HTTP で到達する。リクエストは 80 ポートで受け付け、"/admin"のパスの時だけ auth_basic ディレクティブで Basic 認証を有効にしている。
auth_basic_user_file が Basic 認証時の認証情報を持つファイルを指定するディレクティブで、ファイルの中身は以下のようなユーザー名・パスワードを記載したものになっている(以下はテスト用に作成した admin@admin のデータ)。
$ cat config/nginx/.htpasswd
admin:$apr1$L6c5yCmZ$gVMgoZUwFGFqzmUefsagD.
このユーザー名・パスワードの設定ファイルは、以下のように htpasswd コマンドで作成できる。
$ sudo htpasswd -c ./config/nginx/.htpasswd admin
New password:
Re-type new password:
Adding password for user admin
あとは、NGINX をアプリと同時に起動できるように docker-compose.yaml の設定をするだけ。最終的な docker-compose.yaml のイメージとしては以下のようになる。
version: '3.9'
services:
app:
container_name: app
image: app:latest
build:
context: .
dockerfile: Dockerfile
healthcheck:
test: ['CMD', 'node', './healthcheck.js']
interval: 10s
timeout: 5s
retries: 3
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
ports:
- 3000:3000
redis:
image: redis:7.2.1-alpine3.18
container_name: redis
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 3
environment:
TZ: 'Asia/Tokyo'
volumes:
- ./data/redis:/data
ports:
- 6379:6379
mysql:
image: mysql:8.0.32
container_name: mysql
healthcheck:
test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
interval: 10s
timeout: 5s
retries: 3
environment:
MYSQL_ROOT_PASSWORD: ''
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
TZ: 'Asia/Tokyo'
ports:
- 3306:3306
volumes:
- ./data/mysql:/var/lib/mysql
- ./mysql/sql:/docker-entrypoint-initdb.d
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
nginx:
image: nginx:latest
container_name: nginx
depends_on:
app:
condition: service_healthy
ports:
- 8080:80
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/conf.d/default.conf
- ./config/nginx/.htpasswd:/etc/apache2/.htpasswd
- ./data/nginx:/var/log/nginx
docker-compose.yaml の depends_on.○○○.condition の設定は、コンテナで起動するサービスの起動順序を制御するための設定で、詳細はdocker compose で依存するサービスが起動するまで待機するなどを参照ください。今回、同一ネットワークに SvelteKit のアプリも属させることで簡単にアプリが利用できるようにしたかったので、EC2 上で初回の"docker compose up"をした際に SvelteKit のビルドが走るような設定になっていた関係で、NGINX の起動順序などを制御する必要があった。
最終的に Basic 認証を取り入れた後の簡易的な構成図は以下のようになる。
実際にブラウザ上で開いて確認してみると、Basic 認証が行われることが確認できる。
仮にキャンセルすると、401 になることも確認できた。
※nginx.conf の他の設定内容については、「おまけ」の「nginx.conf の他の設定について」を参照ください。
まとめとして
今回はアプリケーション側の改修なく、手軽に Basic 認証を取り入れる方法についてみてきた。アプリで実装するより、上段のリバースプロキシ等で制御をするというアプリケーションエンジニアの領域の外?に踏み入れることで、楽ができることもあるなと感じたので、今後もそういう機能を利用して開発を楽に早くしていくという事をやっていければいいなと思った。
おまけ
nginx.conf の他の設定について
proxy_set_header
これは上段の CDN 等へのリクエスト内容を、プロキシ配下にいるアプリケーションサーバーに伝えるための設定。よくあるのは secure cookie を使うために、X-Forwarded-Proto でプロトコル情報を渡すことだろう。proxy_pass プロキシする先の URL を指定し、NGINX での処理が成功したらリクエストが到達する。
Basic 認証のキャッシュを削除する方法
基本的に、Basic 認証を 1 度行うとブラウザ上でキャッシュされるので、Basic 認証で保護されたパスにアクセスするたびに認証する必要はない。ただ、開発をする際にはあえてログアウト(キャッシュを削除)して再度認証を行いたい場面もあると思う。
その場合の方法については、以下の記事が参考になると思う。
■この公式ブロガーの記事一覧
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
PHOTO:UnsplashのChristopher Gower