認証は、各アプリが個別に作るものではない。一度立てて皆で共有する門番である。
2-03 で土台(SQLite・PostgreSQL)を据えた。その上に最初に立てるのが、この門番だ。門番も、2-02 で AI に渡した一台の上で動く。
どのアプリも、入口で「誰か」を確かめる。その確認をアプリごとに作るのは、車輪の再発明の筆頭であり、しかも一番間違えやすい。セキュリティの中心だからだ。だから認証も、書かない。立てる。
土台の次に門番を立てる
理由は単純だ。この後に立てる全アプリが、同じ身元(誰か)を共有する。文書も、講座の予約も、基幹システムも、利用者は一度ログインすればいい。
ただし、ここで思想を一つ決めておく。*門番が持つのは、最小限の共通=身元(認証)だけである*。「何ができるか」、つまりアクセス制御は、各アプリ・各サーバーが自分で持つ。「門番を通った者は、中ではどこでも通す」という一枚の壁(境界防御)には頼らない。一箇所破られれば全部が抜けるからだ。中央は最小限、守りは各サーバーで多層に。これが、分散して強い形だ。
自前のログイン実装は、ハッシュ化・セッション・トークン失効・二要素・パスワード再設定・ソーシャルログインのどれ一つ手を抜けない。身元の確認だけを一度立てて共有し、権限の判断は各アプリに残すほうが、速く、安全で、強い。
PocketBase を立てる
門番は PocketBase だ。Go 製の単一バイナリに、認証・管理画面・REST / リアルタイム API・ファイル保管まで入っている。SQLite を内蔵しているので、別のサーバも要らない。公式が配るのは単体のファイルで、Docker のイメージは無い(公式文書に明記)。だから 2-02 の決めどおり、ファイルを置いて systemd に登録する。
- 置き場は
/opt/pocketbase、データは同じ所のpb_data - 待ち受けは localhost だけ(
--http=127.0.0.1:8090)。外からは Caddy が受ける(後述) - 公式文書が勧める systemd のユニットで動かす。ユニットは AI が書く
# GitHub Releases の zip を /opt/pocketbase に展開し、systemd に登録したあと
sudo -u pocketbase /opt/pocketbase/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-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 を門番で直接は束ねられない。だから決めはこうなる。
- 既製の OSS は、自分のログインを持ったまま並べる
- 入口の前にリバースプロキシ(Caddy)を一枚置く。Caddy が受け持つのは TLS と、名前ごとの振り分けだ
- 一度のログインで全部を束ねる(SSO)のは、OIDC を話す層を足すときで、後でよい
# 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 時点の公開価格、年払い)。門番を自分の側に立てれば、その課金から降りる。手順は段階的でよい。
- PocketBase を立て、OAuth2 に Microsoft を残す(既存アカウントで入れる)
- 新しいアプリは PocketBase のトークンで作る
- 利用者を順に PocketBase の
usersへ移す(API で一括登録する。スクリプトは AI が書く) - 全員が移ったら、OAuth2 の Microsoft を外す ── Entra への依存が切れる
止めるのは一度ではない。並行で動かし、移り終えてから古い門を閉じる(2-12)。
出るのも、一点だ
入る手続きは述べた。退職は、もっと簡単だ。門番でアカウントを止める。それだけで、全部から出る。文書も、メールも、会議も、基幹も、すべてが同じ門番のトークンで開くから、一点を閉じれば同時に閉じる。
旧世界で退職処理が大仕事だったのは、鍵が SaaS ごとに散らばっていたからだ。棚卸しをして、一つずつ止めて回る。2-01 で見た「門を閉じられれば、全層から同時に締め出される」という怖さは、鍵が自分の手元にある世界では、そのまま退職処理の確実さに裏返る。
確かめ方
この章は、次の六つができていれば済みだ。
- ブラウザで
http://localhost:8090/_/を開き、作った管理者でログインできる - 管理画面で利用者を一人作り、その利用者でログインできる
- 管理画面の
usersで、メール + パスワード・OAuth2・OTP・MFA の入口を切り替えられる - アプリが、門番に問い合わせずにトークンの署名を確かめ、
user.idを取り出せる。門番を止めても、そのアプリは動き続ける auth.example.comとgit.example.comをブラウザで開くと、Caddy 越しに各アプリが出る- 管理画面でその利用者のアカウントを止めると、その人はどのアプリにも入れなくなる
systemctl status pocketbase # systemd で動いているか
curl -s http://localhost:8090/api/health # 門番が答えるか
sudo systemctl stop pocketbase # 止めても業務アプリが動くか見る
人が持つ物
人が渡す値
- 門番と各アプリのドメイン名(
auth.example.com・git.example.com) - 最初の管理者のメールアドレスとパスワード
- OAuth2 で使う Microsoft・Google・GitHub のクライアント ID と秘密
- 発行するトークンの寿命と、MFA を全体に強制するか管理者だけにするか
- Entra ID から移す利用者の一覧
AI が「やる前に言う」操作
- OAuth2 から Microsoft を外す(Entra への橋を落とす)
- 利用者のアカウントを止める、または消す
- DNS のレコードを、これらのドメイン名に向ける
pb_dataを消す、または作り直す
確かめた版と日付
- PocketBase 0.40.4(2026-09-12、GitHub Releases の単体ファイル)
- Caddy、PostgreSQL(業務データ側)── 版は指定していない
- 手順を書いたのは 2026-07-02、見直したのは 2026-10-05
- 版が上がっていたら、AI に公式の手順を確かめさせてから進める
まとめ
土台の上に、最初の門番を。
- PocketBase ── 単一バイナリに認証・管理画面・API・ファイル保管(SQLite 内蔵)。ファイルを置いて systemd で動かす
- メール / OAuth2 / OTP / MFA ── 要る入口だけ管理画面から開く
- 門番と倉庫を分ける ── 身元は PocketBase、業務データは PostgreSQL
- 中央は最小限、守りは各サーバーで ── 門番は身元だけ、アクセス制御は各アプリ(多層防御)
- リバースプロキシ ── Caddy は TLS と名前の振り分け。既製 OSS は自分のログインのまま並べ、SSO は OIDC の層を足すときに
- Entra ID から降りる ── OAuth2 で橋を架け、移り終えてから断つ
書いたコードは、署名を確かめ、権限を判断する数行だけだ。身元は共有し、守りは各サーバーで。次章では、その門の内側にコードの置き場(Forgejo)を据え、リポジトリと CI を自分の側に置く。