LESSON 16 · 約30分
認証・権限・秘密の扱い
ログインの有無だけでは守れないものを、正しい場所で確認します。
コードを試す前に
基本演習はApp Router・JavaScript/JSX・Cache Components無効で進めます。11の有効化例は独立した発展実験です。01〜04は先に読んで、コードの実行は05の環境構築後に戻って試せます。
短い学習用の例です。各例は段階ごとの独立した例であり、全コードを一つのプロジェクトへ順に上書きして完成する形式ではありません。必要なファイル・依存関係・前提条件は注記に記載しています。
開発環境の準備を確認する →このレッスンでわかること
- 認証と認可を区別できる
- 秘密をブラウザーへ渡さない判断ができる
本人か、操作してよい人か
認証は「誰か」の確認、認可は「その人が何をしてよいか」の確認です。ログイン済みでも他人のノートを編集できてはいけません。UIのボタンを隠すだけでなく、データに近いサーバー側でも権限を検証します。
環境変数の名前で公開範囲が変わる
NEXT_PUBLIC_で始まる値はブラウザー用JavaScriptへ含まれ得ます。秘密のトークンにこの接頭辞を付けません。接頭辞のない値でも、レスポンスやpropsへ入れて送れば漏れます。.envファイルは通常Gitへコミットしないようにします。
セッション管理を自己流で急がない
認証ライブラリの利用を検討し、セッション、Cookie、期限、ログアウトを一貫して扱います。Route HandlerやServer Actionなど入口ごとに、必要なアクセス制御を設けます。Proxyでの事前確認だけを、最終的な認可の代わりにしないでください。
権限をデータ側でも確認する設計例
import 'server-only'
// 設計例:これらの関数は利用する認証・DB製品で実装します。
import { getSession } from './auth'
import { findNote, saveNote } from './database'
export async function updateNote(id, title) {
const session = await getSession()
if (!session) throw new Error('ログインが必要です')
if (typeof title !== 'string' || title.trim().length < 2) {
throw new Error('タイトルの形式が正しくありません')
}
const note = await findNote(id)
if (!note || note.ownerId !== session.userId) {
throw new Error('このノートは編集できません')
}
await saveNote(id, { title: title.trim() })
return { ok: true }
}そのまま実行する完成コードではありません。認証・DB・ID検証・競合対策などを含む実装が別途必要です。入口側では想定内のエラーを安全な戻り値へ変換します。 server-onlyパッケージの導入済みを前提とし、Client側からの誤importを検出します。 DBの内部レコード全体を返さず、呼び出し側が必要な成功情報だけを返します。
TRY IT YOURSELF
二人の利用者で考える
- 利用者Aが作ったノートを用意した想定にする
- 利用者BがIDを書き換えた場合を考える
- サーバー側のどこで拒否するか説明する
できたらOK:「ログインしている」だけでは編集を許可しない理由が分かる
QUICK CHECK
理解を確かめよう
公式資料でもう少し詳しく
ここまで読めたら、ひとつ前進。