1.JWT(JSON Web Tokens)とは
JWT認証は、Webアプリケーションでユーザーの認証状態を管理する仕組みの一つです。
従来のセッションベース認証とは異なり、
サーバー側で状態を保持せずに認証を行うことができるため、
現代のWebアプリケーションで広く使われています。
2.JWTの基本構造
JWT は「ドット(.)」で区切られた 3つの部分 から成り立っています。
ヘッダー.ペイロード.署名
■Header(ヘッダー)
トークンのタイプ(JWT)と使用する暗号化アルゴリズムを指定します。
Base64でエンコードされています。
{
"alg": "HS256",
"typ": "JWT"
}
■Payload(ペイロード)
実際のデータ(クレーム)が含まれる部分です。
ユーザーID、権限、有効期限などの情報が格納されます。
こちらもBase64でエンコードされています。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
■Signature(署名)
Header、Payload、および秘密鍵を使って生成される署名です。
トークンが改ざんされていないことを検証するために使用されます。
署名部分は.envなどで外部に漏れないように管理することが多いです。
JWT_SECRET=your-super-secret-key-here-should-be-very-long-and-random
■Base64エンコード後のJWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3.JWT認証の流れ
ステップ1: ログイン
ユーザーがユーザー名とパスワードでログインします。
ステップ2: トークン生成
サーバーが認証情報を確認し、有効であればJWTトークンを生成して返します。
ステップ3: トークン保存
クライアントは受け取ったJWTトークンを保存します(通常はlocalStorageやcookieに)。
ステップ4: API呼び出し
以降のAPI呼び出しでは、HTTPヘッダーにJWTトークンを含めて送信します。
ステップ5: トークン検証
サーバーはトークンの有効性を検証し、有効であればリクエストを処理します。
4.実装例
バックエンド(Node.js+Express)
// server.js
const express = require("express");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const cors = require("cors");
const app = express();
app.use(express.json());
app.use(cors()); // Next.jsとの通信を許可
// JWT用の秘密鍵(本番では環境変数で管理!)
const JWT_SECRET = process.env.JWT_SECRET || (() => {
throw new Error('JWT_SECRET環境変数が設定されていません');
})();
// ダミーユーザー(本来はDBから取得)
const user = {
id: 1,
username: "testuser",
// "password123" をハッシュ化したもの
password: bcrypt.hashSync("password123", 10),
};
// ✅ ログインAPI(JWT発行)
app.post("/login", (req, res) => {
const { username, password } = req.body;
if (username !== user.username) {
return res.status(401).json({ message: "ユーザーが存在しません" });
}
// パスワード検証
const isPasswordValid = bcrypt.compareSync(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ message: "パスワードが間違っています" });
}
// JWT作成(有効期限1時間)
const token = jwt.sign({ id: user.id }, JWT_SECRET, {
expiresIn: "1h",
});
res.json({ token });
});
// ✅ 認証ミドルウェア
function authenticateToken(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (!token) return res.sendStatus(401);
// ログイン時に取得したJWTトークンで認証を行う
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) return res.sendStatus(403);
req.user = decoded; // デコードされたユーザー情報を保存
next();
});
}
// ✅ 保護されたルート
app.get("/profile", authenticateToken, (req, res) => {
res.json({
message: "認証OK!プロフィール情報を返します。",
user: req.user,
});
});
app.listen(4000, () => {
console.log("Server running on http://localhost:4000");
});
フロントエンド(Next.js)
// app/page.tsx
"use client";
import { useState } from "react";
export default function HomePage() {
const [username, setUsername] = useState("testuser");
const [password, setPassword] = useState("password123");
const [token, setToken] = useState("");
const [profile, setProfile] = useState<any>(null);
// 🔑 ログインしてJWTトークン取得
const handleLogin = async () => {
const res = await fetch("http://localhost:4000/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
if (res.ok) {
const data = await res.json(); // サーバーから返されたJWTトークンを設定
setToken(data.token);
localStorage.setItem("jwt", data.token); // 簡易サンプルなのでlocalStorage保存
alert("ログイン成功!トークンを保存しました。");
} else {
alert("ログイン失敗!");
}
};
// 🔒 プロフィール取得(ログイン時に取得したJWTトークンが必要)
const handleGetProfile = async () => {
const savedToken = localStorage.getItem("jwt");
// サーバー接続時、HeaderにBearer認証を設定する際、JWTトークンを設定して送信する
const res = await fetch("http://localhost:4000/profile", {
method: "GET",
headers: {
Authorization: `Bearer ${savedToken}`,
},
});
if (res.ok) {
const data = await res.json();
setProfile(data);
} else {
alert("認証エラー!");
}
};
return (
<div className="p-6 space-y-4">
<h1 className="text-2xl font-bold">JWT 認証サンプル</h1>
{/* ログインフォーム */}
<div className="space-y-2">
<input
className="border p-2"
placeholder="ユーザー名"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
className="border p-2"
placeholder="パスワード"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button className="bg-blue-500 text-white px-4 py-2 rounded" onClick={handleLogin}>
ログイン
</button>
</div>
{/* トークン表示 */}
{token && (
<div className="bg-gray-100 p-2 rounded">
<p><strong>保存されたトークン:</strong></p>
<p className="break-all text-sm">{token}</p>
</div>
)}
{/* 認証付きリクエスト */}
<div>
<button className="bg-green-500 text-white px-4 py-2 rounded" onClick={handleGetProfile}>
プロフィール取得
</button>
</div>
{/* プロフィール表示 */}
{profile && (
<div className="bg-gray-100 p-4 rounded mt-4">
<h2 className="text-xl font-semibold">プロフィール情報</h2>
<pre className="text-sm">{JSON.stringify(profile, null, 2)}</pre>
</div>
)}
</div>
);
}
5.まとめ
JWTとは
- トークンのフォーマット(形式)のひとつ
- Header.Payload.Signature という3部構成で、署名付きで改ざん防止可能
- 誰がログインしているか、いつまで有効か、などの情報を自己完結的に所持できる
Bearer認証との関係
- Bearer認証:HTTPヘッダーにトークンを入れて送る認証方式のひとつ
Authorization: Bearer <アクセストークン> - JWT認証:Bearer認証の仕組みを使って、トークンにJWTを利用
Authorization: Bearer <JWT>
ということを理解できました。
認証も色々あって、頭がパンパンです。