第127章:セキュリティ:直接叩かれる前提で守る🛡️💥
Webアプリってね、画面(UI)からだけ使われる…と思いがちだけど、実は違うんだよ〜😳 APIやページは「直接アクセスされる前提」で作るのが超大事!🔐✨
1) 「直接叩かれる」ってどういうこと?🕵️♀️💻
たとえば /api/todos があるとするよね?
ユーザーは画面のボタンから使うかもだけど…実際は URLを知ってれば誰でもアクセスできる(可能性がある)んだよ〜😱
- ブラウザのアドレスバーから直にアクセス🧭
- 別のアプリやツールからアクセス📡(※ここではやらないよ!)
- 画面で隠しててもURLは推測されることがある🔎
2) イメージ図:UIの外からも来る🚪⚠️(図解)
flowchart LR
U[普通のユーザー😊] --> UI[画面/UI]
UI --> S[Next.jsサーバー🧠]
A[悪意のあるアクセス😈] -->|UIを通らず| S
S --> API[Route Handler / Server Action]
API --> DB[(DB🗃️)]
ポイントはこれ👇 UIを通らなくても、サーバー(API/DB)に来るルートがあるってこと!🚨
3) 守り方の基本:サーバー側で「必ず」チェック✅🔒
✅ 絶対やること 3点セット
- 認証(Authentication):ログインしてる?🪪
- 認可(Authorization):その人がそれを操作していい?👑
- 入力検証(Validation):変な値じゃない?🧼
UIでボタンを消すのは「親切」だけど、セキュリティじゃないよ🙅♀️💦 本体はサーバーで守るのが正解!🛡️
4) Next.jsで「どこを守る?」マップ🗺️✨
- Middleware:入口の門番🧤(ざっくり止めるのに強い)
- Server Component / page.tsx:ページ表示の時にガードできる🏠
- Route Handler(app/api/...):APIはここが最重要🔥
- Server Actions:フォーム送信も、ここで必ずチェック🔥
- DBアクセス:最終防衛線(「そのデータはそのユーザーのもの?」)🧱
5) 実践①:APIを「ログイン必須」にする🔐🍪(Route Handler)
ここでは学習用に「cookieにログインっぽい印」を入れるだけの例だよ🍪 本番は Auth.js などの仕組みを使うのがおすすめ🙏✨
app/api/secret/route.ts(ログインしてないと401)
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function GET() {
// 例:cookieに session=1 があったらログイン扱い(学習用)
const session = cookies().get("session")?.value;
if (!session) {
return NextResponse.json(
{ error: "ログインが必要だよ🍪🔐" },
{ status: 401 }
);
}
return NextResponse.json({ message: "ひみつデータだよ🤫✨" });
}
ためしてみよう🧪
http://localhost:3000/api/secretにアクセス- cookieが無いなら 401 が返るはず!🚫
6) 実践②:Middlewareで「入口チェック」もする🧤🚦
Middlewareは「最初に止める」係! でも注意:Middlewareだけで安心しないでね(API側も必須)💡
middleware.ts(/dashboard をログイン必須にする例)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const session = req.cookies.get("session")?.value;
const isDashboard = req.nextUrl.pathname.startsWith("/dashboard");
if (isDashboard && !session) {
const url = req.nextUrl.clone();
url.pathname = "/login";
return NextResponse.redirect(url);
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
7) 「認可」こそ命:他人のデータを触れないようにする🧱🗃️
ここ、めちゃ大事!!💥
たとえば /api/todos/123 があって、ログインしてても…
- ✅ 自分のTodoならOK
- ❌ 他人のTodoなら 絶対にNG 🙅♀️
安全な考え方(DBで「自分のもの」だけ取る)
(Prismaっぽい雰囲気の例だよ🧁)
// "id が一致"だけで取るのは危険になりがち
// "id と userId の両方一致"で取るのが安全✨
const todo = await prisma.todo.findFirst({
where: {
id: todoId,
userId: sessionUserId,
},
});
if (!todo) {
return NextResponse.json({ error: "見つからないよ😢" }, { status: 404 });
}
この発想で「他人のデータに触れない」を作れるよ🔐✨
8) セキュリティ思考の合言葉📣💖
✅ 「信用しない」チェックリスト
- ユーザー入力は信用しない🧨
- cookie / header / hidden input も信用しない🍪🙅♀️
- フロント(UI)は守りじゃない🖥️💭
- サーバーが最終判断🧠⚖️
- DB取得は「本人のデータだけ」に絞る🗃️🔒
9) ミニ課題🎒✨(手を動かすやつ!)
課題A:/dashboard を守ろう🧤🏠
middleware.tsを入れて/dashboardを/loginに飛ばす/dashboard/page.tsxも「保険」でガード(ログインなければredirect("/login"))してみよう🚦
課題B:/api/todos にも「ログイン必須」を付けよう📋🔐
GET /api/todosでsessionが無かったら 401 にする- UIを作ってなくてもOK!まずはAPIを固めよう💪✨
まとめ🎀🧠
- URLやAPIは直接アクセスされる前提で作るよ🚪💥
- 守るのは UIじゃなくてサーバー🛡️
- 必須は 認証・認可・入力検証の3点セット✅✅✅
- Middlewareは便利だけど、API側のチェックも必ず🔥
- DBは「本人のデータだけ取る」で事故を防ぐ🗃️🔒
次の章(第128章)で、「アクセス制御はUIじゃなくサーバー🙅」をもっとハッキリ固めていこうね〜!💖🚀