第199章:セキュリティの基本
ここまで作ってきたアプリ、**「動く」だけじゃなくて「守れる」**ようにしよ〜!😊 今日はとくに大事な XSS(クロスサイトスクリプティング) を中心に、フロントでできる基本対策をギュッとやるよ💪🍀
この章でできるようになること🎯
- XSSって何がヤバいのか、イメージできる👀💥
- Reactで「安全になりやすい所」と「事故る所」が分かる⚠️
- うっかりXSSを呼び込まない書き方ができる✅
- どうしてもHTMLを表示したい時の安全なやり方が分かる🧼✨
- ついでに最低限のセキュリティ習慣が身につく🧠🔒
まず結論:入力は信じない(これ最強)🧠🔒
ユーザー入力、URLのパラメータ、APIから返ってきた文字列… 「外から来た文字」は全部あやしいと思ってOK😇
XSSってなに?😱
ざっくり言うと、悪意あるスクリプト(JavaScript)を、あなたのサイト上で動かされちゃう攻撃のこと💣
たとえば、もしXSSが成立すると…👇
- ログイン中のユーザーになりすまして操作される😵💫
- 画面に偽フォームを出してパスワードを入力させられる🎭
- トークン等が盗まれる可能性が上がる(特に localStorage 置きは危険度UP)🧨
XSSの流れ(図でイメージ)🖼️
(“ユーザーが入れた文章”を、うっかりHTMLとして表示しちゃうと…の図)
ReactはXSSに強い?✅ でも「例外」で死ぬ⚠️
Reactは基本的にえらくて、JSXに文字列を入れると 自動でエスケープしてくれるよ😊 だから、普通にこう表示する限りはかなり安全👇
export function Profile({ bio }: { bio: string }) {
return <p>{bio}</p>; // ✅ Reactが基本エスケープしてくれる
}
でも!事故ポイントがあるよ⚠️ とくにこの3つは超要注意😵💫
dangerouslySetInnerHTML(危険って名前に書いてあるやつ)💀- URL系(
href,srcなど)に「怪しい文字列」を入れる🔗 - 外部ライブラリ/Markdown/HTMLレンダリング系を雑に使う📦
事故ポイント1:dangerouslySetInnerHTML 💀
これを使うと、Reactの自動エスケープを自分で無効化する感じになるよ⚠️ 「HTML文字列をそのまま表示したい」って時に使えるけど、丸腰だと危険!
❌ ダメ例(そのまま突っ込む)
export function UnsafeHtml({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />; // ❌ 危険
}
✅ 安全にやる:サニタイズ(無害化)してから入れる🧼✨
「どうしてもHTML表示したい!」って時は、サニタイズしよう😊 定番は DOMPurify だよ(フロント側の“掃除機”みたいなやつ🧹)
1) インストール(Windowsでも同じ)
npm i dompurify
(もし型で困ったら)
npm i -D @types/dompurify
2) サニタイズしてから表示 ✅
import DOMPurify from "dompurify";
type Props = { html: string };
export function SafeHtml({ html }: Props) {
const clean = DOMPurify.sanitize(html, {
USE_PROFILES: { html: true }, // まずは無難に
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
💡ポイント
- 「表示したいHTML」=「安全」じゃないから、必ずサニタイズ🧼
- できれば「許可するタグを絞る(allowlist)」のがさらに強い🛡️
事故ポイント2:リンク(href)に要注意🔗⚠️
ユーザー入力をリンクにするとき、javascript: みたいなスキームが混ざると危ないことがあるよ😱
(ブラウザや状況によって挙動は違うけど、基本は弾くのが正解✅)
✅ 安全寄り:URLをチェックしてから使う
function safeHttpUrl(input: string): string | null {
try {
const url = new URL(input, window.location.origin);
if (url.protocol === "http:" || url.protocol === "https:") return url.toString();
return null;
} catch {
return null;
}
}
export function SafeLink({ urlText }: { urlText: string }) {
const safe = safeHttpUrl(urlText);
if (!safe) return <span>リンクが無効だよ🥺</span>;
return (
<a href={safe} target="_blank" rel="noopener noreferrer">
開くよ🔗✨
</a>
);
}
💡target="_blank" を使うときは rel="noopener noreferrer" をセットが基本だよ(別タブ悪用の対策)🧷
事故ポイント3:MarkdownやHTMLを“そのまま表示”系📦⚠️
「ユーザーの文章をMarkdownで表示したい!」って、よくあるよね😊 でも、Markdownレンダラーの設定次第で HTMLが通っちゃうことがある…😇 ✅ 対策はこれ👇
- 「ユーザー入力→HTML化」するなら、必ずサニタイズ🧼
- レンダラー側でも HTMLを無効にできるなら無効にする🚫
さらに一段強くする:CSP(コンテンツセキュリティポリシー)🧱✨
フロントだけで100%は無理でも、CSPを入れると“爆発を小さく”できるよ💥➡️🧯 ざっくり言うと「このサイトでは、ここ以外のスクリプト動かないでね!」ってブラウザに命令するやつ🧠
※CSPはホスティング(Vercel/Cloudflare等)側の設定が絡むので、ここでは「そういう盾がある」って覚えておけばOKだよ😊🛡️
ありがちな勘違い3つ(ここ落とし穴)🕳️😵💫
- 「TypeScriptだから安全でしょ?」 → 型とXSSは別問題🥺
- 「Reactだから絶対安全でしょ?」 →
dangerouslySetInnerHTMLなど例外で死ぬ😇 - 「サニタイズしたから完全勝利!」 → できればサーバー側でも対策&CSPも合わせ技が強い💪
実戦ミニチェックリスト✅✨(この章のゴール)
- ユーザー入力は 基本そのままJSX表示にする(HTMLとして解釈させない)
-
dangerouslySetInnerHTMLは 原則禁止、使うなら サニタイズ必須 -
href/srcなどURLは http/https だけ許可(最低限) -
target="_blank"にはrel="noopener noreferrer"を付ける - “HTML化するライブラリ”は設定をちゃんと確認する
練習問題(5〜15分)✍️💡
練習1:自己紹介の「安全表示」✅
- 入力欄(自己紹介)を
useStateで持つ - そのまま
<p>{bio}</p>で表示する(まずはこれが安全ルート😊)
練習2:自己紹介の「HTML表示(上級)」🧼
- どうしても
<b>とか使いたい想定で、DOMPurify を入れて SafeHtmlコンポーネントで表示してみる- 「サニタイズなし」と「あり」の違いを体感してみよ👀✨ (※危ない文字列はテスト用でも貼りすぎ注意ね🙏)
まとめ🎀
Reactは基本エスケープしてくれるから頼もしい😊✨
でも dangerouslySetInnerHTML と URL と “HTML化系” は、こちらがちゃんと守らないと危ない⚠️
この章の合言葉はこれ👇
「外から来た文字は信じない」🧠🔒
次(第200章)は、認証つきのメモアプリを完成させるよ〜!📓✨