【第27回】ログインを自作してはいけない理由を知る|認証の入口(嫁子が教えるTS講座)

今回のゴール

前回(Prisma)でデータを保存できるようになりました。今回は「誰のデータか」を扱います。全体の流れは講座の目次へ。


嫁子: 今日はほとんどコードが出てこないよ。この回でいちばん価値があるのは「作らない」という判断だから。

AIに「ログイン機能を作って」と頼むと、たぶん動くものが返ってくる。動くけど危ない、がいちばん怖いパターンなの。第1回で言った話の、最悪の形だね。


1. ログインは、見た目より中身が多い

ログイン画面は、メールアドレスとパスワードの入力欄とボタンだけです。だから簡単そうに見えます。

実際に必要なものを並べると、こうなります。

必要なこと 手を抜くとどうなるか
パスワードを元に戻せない形で保存する 漏れたとき、全員分のパスワードがそのまま流出する
総当たり攻撃を防ぐ 何万回でも試せるので、いずれ突破される
ログイン状態を安全に保持する 他人になりすませる
パスワード再設定の導線 「忘れた」で詰む。雑に作ると乗っ取り経路になる
メールアドレスの確認 他人のアドレスで登録できてしまう
ログアウトと期限切れ 共用端末でログインが残る

このうち1つでも抜けると、他が完璧でも意味がありません。攻撃する側は、いちばん弱いところだけを探すからです。


2. パスワードの保存だけでも、かなり難しい

いちばん分かりやすい例です。パスワードはそのまま保存してはいけません。

✕ パスワードをそのまま列に入れる
→ データベースが漏れた瞬間、全員のパスワードが読める

正しくは、元に戻せない形に変換してから保存します。この変換をハッシュ化と呼びます。

ただし「ハッシュ化すれば安全」でもありません。よく使われるパスワードは変換結果も同じになるので、変換後の値から元を当てる一覧表が出回っています。そこで1件ごとに違う値を混ぜたり、わざと計算に時間のかかる方式を選んだりします。

ここまでやって、やっと最低限です。しかも安全とされる方式は時間とともに変わるので、一度作って終わりにもできません。

嫁子: 「ハッシュ化してるから大丈夫ですよね?」で止まるのが危ないの。

何を使ってるか、設定値は適切か、いつ見直すか。そこまで面倒を見られないなら、自分で持たないほうがいいよ。

しかも、漏らしたときの重さが違う

パスワードが漏れると、そのサイトだけの問題では終わりません。同じパスワードを他のサービスでも使っている人が多いからです。

第23回で「ブラウザに置いたものは見られる」と書きましたが、こちらは影響がもっと外に広がります。個人が趣味で作ったツールでも、預かった以上は同じ責任が発生します。


3. だから、既存の仕組みに任せる

結論はこれです。認証は、専用の仕組みを使ってください。

2026年9月時点で、Next.jsと組み合わせてよく使われるものを挙げておきます。どれを選ぶかより、自作しないことのほうが大事です。

種類 例 特徴
ライブラリとして組み込む Auth.js(旧 NextAuth.js) 自分のデータベースに保存できる。無料
サービスとして借りる Clerk、Supabase Auth など 画面も含めて用意されている。規模により有料

いちばん楽なのは、パスワードを自分で持たない方式です。GoogleやGitHubのアカウントでログインしてもらうか、メールにリンクを送って確認する形にすれば、そもそもパスワードを預からずに済みます。

預からないものは、漏らしようがありません。

嫁子: 扱わなくていい情報は、最初から持たない。これが個人開発でいちばん効く防御だよ。

住所も電話番号も、要らないなら聞かない。聞いた瞬間から、守る責任が発生するからね。


4. ライブラリに任せられない部分がある

ここが今日いちばん重要なところです。認証の仕組みを入れても、それだけでは守れません。

2つは別の話です。

何を判定するか 誰がやるか
認証 あなたは誰か ライブラリ・サービス
認可 その人がそれを見ていいか 自分で書く

ログインの仕組みを入れると「誰か」は分かります。でも「そのデータを見ていいか」は、アプリごとの事情なので自分で判定するしかありません。

たとえば第24回で作った窓口に、削除の処理を足すとします。

// ✕ ログインしていれば誰のデータでも消せてしまう
export async function DELETE(request: Request) {
  const { id } = await request.json();
  await prisma.item.delete({ where: { id } });
  return Response.json({ ok: true });
}

ログインは必要でも、そのidが自分のものかを確認していません。他人のidを送れば、他人のデータが消えます。

// ○ 自分のデータかどうかまで確認する
const item = await prisma.item.findUnique({ where: { id } });

if (item === null || item.userId !== session.user.id) {
  return Response.json({ error: "権限がありません" }, { status: 403 });
}

画面に削除ボタンを出さないだけでは防御になりません。第23回で書いた通り、ブラウザを経由せずに直接送れるからです。

嫁子: ここ、AIがいちばん抜かすところだと思う。

「ログイン機能をつけて」と頼むと認証は入る。でも一件ずつの持ち主チェックまでは、言わないと入らないことがあるの。

だから頼むときは「ログイン必須にして、かつ自分のデータ以外は操作できないようにして」まで言ってね。2つ目が本体だから。


5. 個人で作るなら、ここまでで足りる

最後に、現実的な線を引いておきます。

自分と家族だけで使うツールなら、ログインすら要らないこともあります。URLを知っている人しか開けない場所に置く、という運用で足りる場面は多いです。

使う範囲 現実的な選択
自分だけ 手元で動かす。公開しない
家族だけ 公開はするが、認証は簡易なものでも可
他人に使ってもらう 専用の仕組みを入れる。認可も自分で書く

いちばん避けたいのは、他人に使ってもらうのに自作の認証で済ませることです。

嫁子: 段階を上げるのは後からでもできるからね。

最初から全部そろえようとして完成しないのがいちばんもったいないの。自分用なら認証なしで作りきって、人に使ってもらう段になってから入れる。この順番でいいと思うよ。


つまずきポイント

認証を入れると、開発中も毎回ログインが必要になります。手元の環境だけ簡単に入れる仕組みを用意しておくと、作業が止まりません。

「動いた」と「安全」は別です。ログインできて画面が出れば動いたように見えますが、他人のデータが見えないかどうかは別に確認する必要があります。自分の分と、もう1つ別のアカウントを作って試してください。


今回のまとめ


次にやること

次回はVercel へデプロイです。第5部の最後で、手元で動いているものを自分のURLで公開します。

今回の宿題:AIに「Next.jsでログイン機能を作って」と頼んで、返ってきたコードを読んでください。パスワードをどう保存しているかと、他人のデータを操作できない作りになっているかの2点だけ確認します。抜けていたら、それを指摘できたことが今日の成果です。

嫁子: 今日は作らない判断を覚える回だったね。

何でも自分で作れるようになるのが上達じゃないの。どこを他人に任せるかを決められるのが上達だと思うよ。