2-05 / Series
2-05 № 05 · 2026

身元は一つ、
守りは各サーバーで。

共有するのは身元(認証)だけ ── アクセス制御は各サーバーが持つ。中央は最小限、守りは多層で

認証は、各アプリが個別に作るものではない。一度立てて皆で共有する門番である。

2-03 で土台(SQLite・PostgreSQL)を据えた。その上に最初に立てるのが、この門番だ。門番も、2-02 で AI に渡した一台の上で動く。

どのアプリも、入口で「誰か」を確かめる。その確認をアプリごとに作るのは、車輪の再発明の筆頭であり、しかも一番間違えやすい。セキュリティの中心だからだ。だから認証も、書かない。立てる。

土台の次に門番を立てる

理由は単純だ。この後に立てる全アプリが、同じ身元(誰か)を共有する。文書も、講座の予約も、基幹システムも、利用者は一度ログインすればいい。

ただし、ここで思想を一つ決めておく。*門番が持つのは、最小限の共通=身元(認証)だけである*。「何ができるか」、つまりアクセス制御は、各アプリ・各サーバーが自分で持つ。「門番を通った者は、中ではどこでも通す」という一枚の壁(境界防御)には頼らない。一箇所破られれば全部が抜けるからだ。中央は最小限、守りは各サーバーで多層に。これが、分散して強い形だ。

自前のログイン実装は、ハッシュ化・セッション・トークン失効・二要素・パスワード再設定・ソーシャルログインのどれ一つ手を抜けない。身元の確認だけを一度立てて共有し、権限の判断は各アプリに残すほうが、速く、安全で、強い。

PocketBase を立てる

門番は PocketBase だ。Go 製の単一バイナリに、認証・管理画面・REST / リアルタイム API・ファイル保管まで入っている。SQLite を内蔵しているので、別のサーバも要らない。公式が配るのは単体のファイルで、Docker のイメージは無い(公式文書に明記)。だから 2-02 の決めどおり、ファイルを置いて systemd に登録する。

# GitHub Releases の zip を /opt/pocketbase に展開し、systemd に登録したあと
sudo -u pocketbase /opt/pocketbase/pocketbase superuser create [email protected] 'change-me'

http://localhost:8090/_/ が管理画面だ。ユーザー、ログイン方法、発行するトークンの寿命は、すべてここから設定する。コードは、まだ一行も書いていない。管理者のメールアドレスとパスワードは、人が決めて渡す値だ。

メール・OAuth・ワンタイムを開く

PocketBase の users コレクションに、要る入口を有効にするだけでよい。

Entra ID(旧 Azure AD)からの移行中も、OAuth2 に Microsoft を残せば、利用者は今までの Microsoft アカウントのまま入れる。門番だけ自分の側へ移し、利用者の体験は変えない。

身元は門番、業務データは倉庫

ここが設計の要だ。身元(誰か)は門番が持ち、業務データ(何をしたか)は PostgreSQL に置く(2-03)。門番と倉庫を分ける。

アプリは、門番が発行したトークンの署名を手元で確かめて user.id を取り出す。門番に毎回問い合わせない。だから門番が一瞬落ちても動く。PocketBase のトークンは共通鍵 (HS256)で署名されていて公開鍵は無いので、管理画面の「Auth token secret」をアプリと共有する。同じ一台の上だから、これで足りる。そのうえで、この利用者がこの操作をしてよいかは、アプリ自身が判断する。

# 業務アプリ側 ── 署名を手元で確かめ、権限も自分で判断する
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])

身元の確認は門番に集約し、権限の判断は各アプリに残す。身元を共有しつつ、守りは各サーバーに分散する。これが多層防御だ。

身元は、各アプリが各々作るものではない ── 一度立てて皆で共有する。 だが「何ができるか」は、各サーバーが自分で守る。

入口の前にリバースプロキシを置く

自作のアプリは、門番のトークンで守る。一方、コードの置き場(Forgejo、2-06)やメール (2-08)のような既製の OSS は、各々が自前のログインを持つ。PocketBase は OAuth2 の利用者であって提供者(OIDC)ではないので、既製の OSS を門番で直接は束ねられない。だから決めはこうなる。

# Caddyfile ── 名前ごとに振り分ける。証明書は Caddy が取る
auth.example.com { reverse_proxy localhost:8090 }
git.example.com  { reverse_proxy localhost:3000 }

example.com の部分は、人が決めたドメイン名に置き換える。まず身元を一つに共有することが先で、完全な統合は後でよい。多くの社内利用は、門番と各アプリのログインで足りる。

Entra ID から降りる

Microsoft Entra ID の無料版は Microsoft 365 に含まれていて、人数課金の一部だ。条件付きアクセスなどを使う P1・P2 は、さらに利用者ごとに積む(P1 が月 7 ドル、P2 が月 10 ドル。2026-10-05 時点の公開価格、年払い)。門番を自分の側に立てれば、その課金から降りる。手順は段階的でよい。

  1. PocketBase を立て、OAuth2 に Microsoft を残す(既存アカウントで入れる)
  2. 新しいアプリは PocketBase のトークンで作る
  3. 利用者を順に PocketBase の users へ移す(API で一括登録する。スクリプトは AI が書く)
  4. 全員が移ったら、OAuth2 の Microsoft を外す ── Entra への依存が切れる

止めるのは一度ではない。並行で動かし、移り終えてから古い門を閉じる(2-12)。

出るのも、一点だ

入る手続きは述べた。退職は、もっと簡単だ。門番でアカウントを止める。それだけで、全部から出る。文書も、メールも、会議も、基幹も、すべてが同じ門番のトークンで開くから、一点を閉じれば同時に閉じる。

旧世界で退職処理が大仕事だったのは、鍵が SaaS ごとに散らばっていたからだ。棚卸しをして、一つずつ止めて回る。2-01 で見た「門を閉じられれば、全層から同時に締め出される」という怖さは、鍵が自分の手元にある世界では、そのまま退職処理の確実さに裏返る。

確かめ方

この章は、次の六つができていれば済みだ。

  1. ブラウザで http://localhost:8090/_/ を開き、作った管理者でログインできる
  2. 管理画面で利用者を一人作り、その利用者でログインできる
  3. 管理画面の users で、メール + パスワード・OAuth2・OTP・MFA の入口を切り替えられる
  4. アプリが、門番に問い合わせずにトークンの署名を確かめ、user.id を取り出せる。門番を止めても、そのアプリは動き続ける
  5. auth.example.com と git.example.com をブラウザで開くと、Caddy 越しに各アプリが出る
  6. 管理画面でその利用者のアカウントを止めると、その人はどのアプリにも入れなくなる
systemctl status pocketbase                    # systemd で動いているか
curl -s http://localhost:8090/api/health       # 門番が答えるか
sudo systemctl stop pocketbase                 # 止めても業務アプリが動くか見る

人が持つ物

人が渡す値

AI が「やる前に言う」操作

確かめた版と日付

まとめ

土台の上に、最初の門番を。

書いたコードは、署名を確かめ、権限を判断する数行だけだ。身元は共有し、守りは各サーバーで。次章では、その門の内側にコードの置き場(Forgejo)を据え、リポジトリと CI を自分の側に置く。


関連記事