はい。この条件なら、**「認証は nginx + 外部認証サービスに完全に寄せ、アプリは認証済みユーザー名をヘッダで受け取るだけ」**にするのが適切です。
推奨構成
第一候補は nginx + Authelia です。
Internet
│
HTTPS :443
│
┌─────▼─────┐
│ nginx │
│ TLS終端 │
│ auth_request
└─────┬─────┘
│
認証確認 │ 未認証
┌─────────────┘ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Authelia │◀─────────▶│ auth.example │
│ password/MFA│ │ ログイン画面 │
└─────────────┘ └──────────────┘
│
認証OK
│
▼
┌───────────────────┐
│ nginx │
│ Remote-User: foo │
│ を強制的に付与 │
└─────────┬─────────┘
│ private network only
▼
┌───────────────────┐
│ Existing App │
│ user = Remote-User│
└───────────────────┘
Authelia は nginx の auth_request を正式にサポートしており、認証成功後に Remote-User、Remote-Groups、Remote-Email 等を nginx に返せます。nginx は auth_request_set で値を受け、バックエンドへヘッダとして渡せます。(Authelia)
なぜ Authelia か
今回の要件との対応がかなり素直です。
| 要件 | nginx + Authelia |
|---|---|
| 既存アプリ変更を最小化 | ◎ |
| ユーザー名+パスワード | ◎ |
Remote-User | ◎ ネイティブ |
| nginx のまま使う | ◎ |
| ローカルユーザーDB | ◎ |
| LDAP連携 | ◎ |
| MFAを後から追加 | ◎ |
| グループによるアクセス制御 | ◎ |
| brute-force対策 | ◎ |
| SSO化 | ◎ |
| 小規模構成 | ◎ |
| 複数アプリへ拡張 | ◎ |
Authelia の file backend は Argon2id を利用でき、認証失敗回数に応じたユーザー/IP単位の一時BANもあります。(Authelia)
アプリ側の変更
アプリ側は極端に言えばこれだけです。
Remote-User: alice
を、
現在ログインしているユーザー = alice
として扱います。
例えば、
username = request.headers["Remote-User"]
if username is empty:
return 401
user = find_or_create_user(username)
程度です。
ただし、ここに1つ非常に重要なセキュリティ境界があります。
アプリ自身は、
Remote-Userが付いている → 認証済み
と無条件に信用することになるので、アプリへ nginx を迂回してアクセスできてはいけません。
最重要ポイント:アプリを直接公開しない
例えば Docker なら、
app:
expose:
- "8080"
にはするが、
ports:
- "8080:8080"
にはしません。
構成として、
Internet ──► nginx ──► app
│
└──► Authelia
だけを許可します。
以下はNGです。
Internet ──► nginx ──► app
│
└────────────────► app:8080 ← NG
後者だと攻撃者が直接、
GET /
Remote-User: admin
を送れば認証を迂回できるからです。
nginx側も必ずヘッダを上書きする
ブラウザから送られてきた Remote-User をそのまま転送してはいけません。
概念的にはこうします。
location / {
auth_request /internal/authelia/authz;
auth_request_set $authenticated_user $upstream_http_remote_user;
proxy_set_header Remote-User $authenticated_user;
proxy_set_header X-Forwarded-User $authenticated_user;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_pass http://app:8080;
}
ポイントは、
proxy_set_header Remote-User $authenticated_user;
です。
クライアントが、
Remote-User: admin
を送り込んでも、nginx が認証サービスから取得した値で置換します。
Authelia の公式 nginx 構成も、
auth_request_set $user $upstream_http_remote_user;
proxy_set_header Remote-User $user;
という構成になっています。(Authelia)
Remote-User と X-Forwarded-User のどちらを使うか
今回なら Remote-User を推奨します。
Authelia が直接、
Remote-User
Remote-Groups
Remote-Name
Remote-Email
を返すためです。(Authelia)
例えば、
Remote-User: tanaka
Remote-Email: tanaka@example.com
Remote-Groups: users,developers
のようにできます。
アプリ側では基本的に、
Remote-User
だけ見れば十分です。
将来、
admin
editor
viewer
などを追加したければ Remote-Groups も使えます。
認証フロー
ユーザーが
https://app.example.com/foo
へアクセスすると、
1. Browser → nginx
2. nginx
└─ auth_request → Authelia
3. Authelia
├─ sessionあり → OK
└─ sessionなし → loginへ
4. ユーザー
username/password入力
5. Authelia
└─ session cookie発行
6. 再度 app.example.com/foo
7. nginx → Authelia
↓
alice
8. nginx → App
Remote-User: alice
という動作になります。
nginx の auth_request は、認証サブリクエストが 2xx なら許可、401/403 なら拒否する仕組みです。(Nginx)
Authelia側
少人数ならユーザー情報をファイルで持てます。
概念として、
authentication_backend:
file:
path: /config/users.yml
password:
algorithm: argon2
argon2:
variant: argon2id
です。
現在の Authelia の file backend は Argon2id を推奨設定として持っています。(Authelia)
例えばユーザーを、
alice
bob
charlie
と登録。
パスワードそのものは保存せず、
$argon2id$...
というハッシュを保存します。
インターネット公開なら default deny
Authelia側は、
access_control:
default_policy: deny
rules:
- domain: app.example.com
policy: one_factor
という思想がよいです。
Authelia自身も default_policy: deny を推奨しています。(Authelia)
つまり設定を間違えても、
設定なし → 公開
ではなく、
設定なし → 拒否
になります。
パスワードだけで始めてもMFAへ移行可能
最初は、
policy: one_factor
として、
username
password
のみ。
あとから、
policy: two_factor
へ変更すれば、
password
+
TOTP / WebAuthn等
という構成へ持っていけます。
Authelia は one_factor / two_factor をアクセス制御ポリシーとして持っています。(Authelia)
公開インターネットに置くサービスなら、特に管理者だけでも最終的には2FAを使える構成にしておく価値があります。
brute-force対策
単純な nginx Basic Auth と比較して大きい部分です。
Authelia には例えば、
regulation:
modes:
- user
- ip
max_retries: 5
find_time: 2m
ban_time: 15m
のような認証試行制限があります。
現行ドキュメントでも、username/password endpoint への試行回数に応じてユーザー/IPを一時BANできる仕組みが用意されています。(Authelia)
ストレージ構成
小規模・単一サーバー
これで十分です。
nginx
Authelia
Existing App
SQLite
users.yml
Authelia はローカルSQLiteストレージを利用できます。公式例でもこの構成があります。(Authelia)
Docker Composeならかなり簡単になります。
docker compose
├── nginx
├── authelia
└── app
- volume:
authelia/config
authelia/db.sqlite3
HAが必要になったら
その時点で、
nginx
│
┌──────┴──────┐
Authelia1 Authelia2
│ │
└──────┬───────┘
│
PostgreSQL
+
Redis
へ移行できます。
Authelia のセッションストレージは単一インスタンス向けの memory と、HA向けの Redis / Redis Sentinel をサポートしており、HA用途では stateless な Redis 系が推奨されています。(Authelia)
最初からここまでやる必要はないと思います。
TLS
これは必須と考えた方がよいです。
https://app.example.com
https://auth.example.com
にします。
例えば、
Let's Encrypt
↓
nginx TLS termination
↓
internal docker network
です。
Authelia自身も、認証ポータルおよび forward authentication で保護するアプリについて HTTPS/WSS を要求しています。(Authelia)
nginx Basic Auth というもっと簡単な案
実は「ほぼ一切追加したくない」なら、
nginx
+
.htpasswd
だけでもできます。
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/users.htpasswd;
proxy_set_header Remote-User $remote_user;
proxy_pass http://app:8080;
}
nginx の $remote_user には Basic Authentication のユーザー名が入ります。(Nginx)
したがって、
Browser
↓
Basic Auth
↓
nginx
↓
Remote-User: alice
↓
App
という、極めて小さい構成も成立します。
ただし
インターネット向け一般ユーザーサービスなら、私はこれを暫定構成として扱います。
理由は、
- ログイン画面を作れない
- ブラウザのBasic Auth UXに依存
- 明示的なログアウトが扱いづらい
- パスワード変更UIがない
- アカウント管理が
htpasswd - MFAがない
- ロックアウト/認証監査が弱い
- 将来的なSSOが難しい
からです。
「管理者3人だけが使うツール」程度なら合理的ですが、一般ユーザーが存在するなら Authelia の方が扱いやすいです。
authentikという選択肢
もう一つ有力なのは、
nginx + authentik
です。
authentik にも nginx 用の Forward Auth があり、既存アプリ自身を authentik 経由でプロキシする必要はなく、
nginx → authentikへ認証問い合わせ
nginx → existing app
という構成ができます。(authentik)
認証済みユーザーについて、
X-authentik-username
X-authentik-groups
X-authentik-email
X-authentik-name
X-authentik-uid
などを出せます。(authentik)
さらに custom header で、
Remote-User
へ変換することも可能です。(authentik)
ただし今回の用途だけなら、
authentik
は Authelia よりやや大掛かりです。
3案を整理すると
| 構成 | 手軽さ | 公開向け | ユーザー管理 | MFA | 向くケース |
|---|---|---|---|---|---|
| nginx Basic Auth | ◎ | △ | △ | × | 数人用 |
| nginx + Authelia | ○ | ◎ | ○ | ◎ | 今回 |
| nginx + authentik | △ | ◎ | ◎ | ◎ | 本格IAM/SSO |
今回の条件なら、
nginx + Authelia + Remote-User
が最もバランスのよい設計です。
実際に組むならこの構成
Internet
│
TCP 80/443
│
┌───────▼───────┐
│ nginx │
│ │
│ TLS │
│ auth_request │
│ rate limit │
│ access log │
└───┬───────┬───┘
│ │
/auth │ │ /app
▼ │
┌─────────┐ │
│Authelia │ │
└────┬────┘ │
│ │
┌────▼────┐ │
│ users │ │
│ SQLite │ │
└─────────┘ │
│
Remote-User: alice
│
▼
┌──────────┐
│ App │
│ :8080 │
└──────────┘
nginx : public
Authelia : internal + nginx経由のみ
App : internal only
SQLite/users.yml : filesystem only
アプリ側で最低限実装するべきこと
認証そのものを実装する必要はありませんが、以下だけは入れた方がよいです。
Remote-Userが無ければアクセス拒否Remote-UserをユーザーIDとして使用- nginx以外からの接続を受けない
Remote-Userでユーザーを切り替え、全てのデータアクセスをそのユーザーに紐付ける- username変更を許すなら、ユーザー名ではなく不変IDを使う設計も検討
特に 4 が重要です。Authelia/nginx が解決するのは「この人が alice である」という認証までです。
alice は alice のデータだけ読める
bob は bob のデータだけ読める
admin は管理機能を使える
という認可は、マルチユーザーアプリ自身が担保する必要があります。
結論
公開を前提にした最小構成としては、
Let's Encrypt
↓
nginx
↓ auth_request
Authelia
↓
Remote-User
↓
既存アプリ
を推します。
アプリの変更点は「信頼された Remote-User を現在ユーザーとして扱う」のみにでき、パスワード保存・ログイン画面・セッション・MFA・brute-force対策をアプリから完全に分離できます。
必要なら次に、**Docker Compose前提で nginx + Authelia + 既存app のほぼそのまま起動できる構成例(compose.yml、nginx.conf、Authelia configuration.yml、users.ymlまで)**に落とせます。