土台(PostgreSQL・SQLite)の上に、最初に立てるのは 門番 ── 認証だ。どのアプリも、入口で「誰か」を確かめる。その確認を アプリごとに作るのは、車輪の再発明の筆頭であり、しかも一番間違えやすい(セキュリティの中心だ)。
だから認証も、書かない。立てる。汎用中の汎用は、すでに OSS にある。
なぜ認証を最初に立てるのか
土台の次が門番である理由は単純だ。この後に立てる全アプリが、同じ身元 (誰か)を共有する。文書も、講座の予約も、基幹システムも ── 利用者は一度ログインすればいい。
ただし、ここで思想を一つ決めておく。門番が持つのは、最小限の共通=身元(認証)だけだ。「何ができるか」=アクセス制御は、各アプリ・各サーバーが、自分で持つ。「門番を通った者は、中ではどこでも通す」── この一枚の壁(境界防御)に頼る思想は採らない。一箇所破られれば全部が抜けるからだ。 中央は最小限、守りは各サーバーで多層に。これが、分散して強い形だ。
自前のログイン実装は、ハッシュ化・セッション・トークン失効・二要素・パスワード再設定・ソーシャルログイン ── どれ一つ手を抜けない。身元の確認だけを一度立てて共有し、権限の判断は各アプリに残すほうが、速く、安全で、強い。
PocketBase を立てる
門番は PocketBase。Go 製の 単一バイナリに、認証・管理画面・REST /
リアルタイム API・ファイル保管まで入っている。2-02で触れた SQLite の上で動くので、別のサーバも要らない。compose.yaml 一枚で立つ。
# compose.yaml ── 単一バイナリの門番を置くだけ
services:
auth:
image: ghcr.io/muchobien/pocketbase:latest
ports: ["8090:8090"]
volumes: ["./pb_data:/pb_data"]
restart: always
docker compose up -d
# 管理者を一人作る
docker compose exec auth pocketbase superuser create admin@example.com 'change-me'
http://localhost:8090/_/ が管理画面だ。ユーザー、ログイン方法、発行するトークンの寿命 ── すべてここから設定する。コードは、まだ一行も書いていない。
メール・OAuth・ワンタイムを開く
PocketBase の users コレクションに、要る入口を有効にするだけでよい。
- メール + パスワード ── 既定。確認メールとパスワード再設定も内蔵
- OAuth2 ── Google・Microsoft・GitHub を「ログイン」ボタンに足す
- ワンタイム(OTP) ── メールに届く番号だけで入る、パスワードレス
- MFA ── 二要素を全体、または管理者だけに強制
Entra ID(旧 Azure AD)からの移行中も、OAuth2 に Microsoft を残せば、利用者は今までの Microsoft アカウントのまま入れる。門番だけ自分の側へ移し、利用者の体験は変えない。
身元は門番、業務データは倉庫
ここが設計の要だ。身元(誰か)は門番が持ち、業務データ(何をしたか)は PostgreSQL に置く(2-02)。門番と倉庫を分ける。
アプリは、門番が発行した トークンの署名を手元で確かめて user.id を取り出す(門番に毎回問い合わせない ── 門番が一瞬落ちても動く)。そのうえで、
この利用者がこの操作をしてよいかは、アプリ自身が判断する。
# 業務アプリ側 ── 署名を手元で確かめ、権限も自分で判断する
user_id = verify_token(request.headers["Authorization"]) # 門番の公開鍵で署名検証(毎回問い合わせない)
require(can_read_orders(user_id)) # 「何ができるか」は、このアプリが決める
orders = pg.execute("SELECT * FROM orders WHERE user_id = %s", [user_id])
身元の確認は門番に集約し、権限の判断は各アプリに残す。身元を共有しつつ、守りは各サーバーに分散する ── これが多層防御だ。
身元は、各アプリが各々作るものではない ── 一度立てて皆で共有する。 だが「何ができるか」は、各サーバーが自分で守る。
入口の前に立てる ── リバースプロキシ
自作アプリは PocketBase の SDK でそのまま守れる。一方、文書(OnlyOffice)やコード(Forgejo)のような 既製の OSS は、各々が自前のログインを持つ。これらは入口の前に リバースプロキシ(Caddy) を一枚置き、社外には門番を通った者だけを通す ── ただしこれは最初の一枚であって、各アプリも自分で身元と権限を確かめる(境界の中も信用しない=多層防御)。
# Caddyfile ── 門番を通した先に各アプリを並べる
auth.example.com { reverse_proxy auth:8090 }
docs.example.com { reverse_proxy onlyoffice:80 }
git.example.com { reverse_proxy forgejo:3000 }
将来、これらを 一度のログインで束ねる(SSO) なら、OIDC を話す層を前に足す ── だが多くの社内利用は、門番と各アプリのログインで足りる。 まず身元を一つに共有することが先で、完全な統合は後でよい。
Entra ID から降りる
Microsoft Entra ID は、利用者数で月額が積み上がる。門番を自分の側に立てれば、その課金から降りる。手順は段階的でよい。
- PocketBase を立て、OAuth2 に Microsoft を残す(既存アカウントで入れる)
- 新しいアプリは PocketBase のトークンで作る
- 利用者を順に PocketBase の
usersへ移す(CSV で一括登録できる) - 全員が移ったら、OAuth2 の Microsoft を外す ── Entra への依存が切れる
止めるのは一度ではない。並行で動かし、移り終えてから古い門を閉じる (2-09)。
出るのも、一点だ
入る手続きは述べた。退職は、もっと簡単だ ── 門番でアカウントを止める。それだけで、全部から出る。文書も、メールも、会議も、基幹も、すべてが同じ門番のトークンで開くから、一点を閉じれば同時に閉じる。
旧世界で退職処理が大仕事だったのは、鍵が SaaS ごとに散らばっていたからだ ── 棚卸しをして、一つずつ止めて回る。2-01 で見た「閉じた束は全層から同時に締め出される」という怖さは、鍵が自分の手元にある世界では、そのまま 退職処理の確実さ に裏返る。
まとめ
土台の上に、最初の門番を。
- PocketBase ── 単一バイナリに認証・管理画面・API・ファイル保管(SQLite の上)
- メール / OAuth2 / OTP / MFA ── 要る入口だけ管理画面から開く
- 門番と倉庫を分ける ── 身元は PocketBase、業務データは PostgreSQL
- 中央は最小限、守りは各サーバーで ── 門番は身元だけ、アクセス制御は各アプリ(多層防御)
- リバースプロキシ ── 既製 OSS は門を通した先に並べる(最初の一枚であって、唯一の壁ではない)
- Entra ID から降りる ── OAuth2 で橋を架け、移り終えてから断つ
書いたコードは、署名を確かめ、権限を判断する数行だけ。身元は共有し、守りは各サーバーで。次章では、その門の内側に 文書(OnlyOffice) を据え、Word・Excel・ PowerPoint を自分の側に置く。