自立編 2-03 / Essay
自立編 2-03 № 03 · 2026

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

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

土台(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 コレクションに、要る入口を有効にするだけでよい。

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 は、利用者数で月額が積み上がる。門番を自分の側に立てれば、その課金から降りる。手順は段階的でよい。

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

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

出るのも、一点だ

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

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

まとめ

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

書いたコードは、署名を確かめ、権限を判断する数行だけ。身元は共有し、守りは各サーバーで。次章では、その門の内側に 文書(OnlyOffice) を据え、Word・Excel・ PowerPoint を自分の側に置く。


関連記事