JWT ログイン機能実装の流れ
JWTの構造
JWT(JSON Web Token)の構造は、ヘッダー(Header)、ペイロード(Payload)、シグネチャ(Signature) の3つの部分で構成されます。
🔹 JWTの構造
JWTは ドット(.
)区切りの3つの部分 で構成されています。
<ヘッダー>.<ペイロード>.<シグネチャ>
1️⃣ ヘッダー(Header)
JWTの種類と署名アルゴリズムを指定します。
例(Base64エンコード前)
{
"alg": "HS256",
"typ": "JWT"
}
✅ alg
: 署名に使用するアルゴリズム(デフォルトは HS256
(HMAC SHA-256):
シークレットキーを使用して署名を作成する方式)
✅ typ
: トークンの種類(デフォルト "JWT"
)
。
ヘッダーはBase64URLエンコードされ、JWTの最初の部分になります。
2️⃣ ペイロード(Payload)
トークンに含める**ユーザー情報(クレーム)**が入っています。
例(Base64エンコード前)
{
"userId": "12345",
"role": "admin",
"exp": 1710000000
}
✅ userId
: ユーザー識別子
✅ role
: ユーザーの権限
✅ exp
: トークンの有効期限(Unixタイムスタンプ)
ペイロードもBase64URLエンコードされ、JWTの2番目の部分になります。
3️⃣ シグネチャ(Signature)
トークンの改ざんを防ぐための署名です。
以下のようにして生成します。
HMACSHA256(
Base64URLEncode(ヘッダー) + "." + Base64URLEncode(ペイロード),
シークレットキー
)
シグネチャはJWTの3番目の部分になります。
これにより、正しいシークレットキーを持っているサーバーだけがJWTを検証できます。
🔹 JWTの完成形
JWTは3つの部分を .
で結合した文字列になります。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
このような文字列がJWTトークンとして使われます。
作成方法
1. ログインの処理:
-
IDとパスワードの検証: ユーザーがログインしようとするIDとパスワードをデータベースと照合し、一致するかを確認します。
- 一致すれば、ユーザー認証が成功し、次のステップに進みます。
- 一致しなければ、認証エラーを返します。
2. JWT(JSON Web Token)の発行:
-
アクセストークン(Access Token): ユーザー認証後に発行されるトークンです。通常、アクセストークンは短時間(例えば、15分~1時間程度)で期限切れになります。これはセキュリティ上、長時間使用されないことを防ぐためです。
- アクセストークンは、APIリクエストに対してユーザー認証を行うために使われます。
- 例:
Authorization: Bearer <アクセストークン>
-
リフレッシュトークン(Refresh Token)任意: アクセストークンの期限が切れたときに、新しいアクセストークンを発行するために使います。リフレッシュトークンは、長時間(例えば、数日~数週間)有効です。
- リフレッシュトークンは通常、サーバー側で管理され、データベースに保存されます。
3. アクセストークンの期限切れ後の流れ:
-
アクセストークンの期限切れ: アクセストークンが期限切れに場合、リフレッシュトークンを使用して新しいアクセストークンを発行します。
- ユーザーがリフレッシュトークンを使って新しいアクセストークンを取得するため、サーバー側でリフレッシュトークンの検証を行い。
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
export async function POST(request:Request) {
try {
const { id , password} = await request.json();
//id有無
const user = await prisma.user.findUnique({ where : {id} });
if(!user){
return NextResponse.json({message : "User Not Found"} , {status : 404});
}
//PW検証 (bcryptを使って比較)
const isValidPw = await bcrypt.compare( password , user.password );
if(!isValidPw){
return NextResponse.json( {message : "Invalid credentials"} , { status : 401 } );
}
//JWT生成
const token = jwt.sign(
{ userId: user.id }, // 페이로드
process.env.JWT_SECRET as string, // secret key
{ expiresIn: parseInt(process.env.JWT_EXPIRES_IN as string ,10) } // 満了時間 (string)
);
// トークンをクライアントへリターン
return NextResponse.json({ message: "Login successful", token }, { status: 200 });
} catch (error) {
return NextResponse.json({message : "Error" , error} , {status : 500})
}
}
middlewear
JWT検証ミドルウェアの流れ
-
リクエストを受け取る:
- ユーザーがAPIにリクエストを送信する際、そのリクエストには通常
Authorization
ヘッダーにJWTが含まれています。
- ユーザーがAPIにリクエストを送信する際、そのリクエストには通常
-
JWTを検証する:
- ミドルウェアがリクエストを受け取り、
Authorization
ヘッダーにJWTが含まれているかをチェックします。 - JWTが存在すれば、そのトークンを検証して、トークンが改ざんされていないか、有効期限が切れていないかを確認します。
- ミドルウェアがリクエストを受け取り、
-
JWTが有効であれば、ペイロードからユーザー情報を取得:
- JWTが正しく検証され、トークンが有効であれば、トークン内のペイロード(通常は
userId
やユーザー情報)を取り出します。 - これを使って、データベースなどから該当するユーザー情報を取得することができます。
- JWTが正しく検証され、トークンが有効であれば、トークン内のペイロード(通常は
-
ユーザー情報をリクエストに追加:
- 取得したユーザー情報をリクエストのオブジェクトに追加し、次の処理に渡します。これにより、後続の処理でユーザー情報を利用できるようになります。
-
レスポンスの処理:
- ミドルウェアが完了した後、レスポンスがクライアントに返されます。例えば、ユーザーがアクセスできるページやリソースが提供されます。
実際の流れ:
- ログイン → ユーザー情報とパスワードをチェック → JWT発行 → クライアントに送信
- クライアントからのリクエスト → JWTを含めたリクエスト送信
- ミドルウェア → JWT検証 → ペイロードからユーザー情報を取得
- 後続の処理 → 必要な情報を使ってレスポンスを返す
//JWTが有効なのか検証
import { NextRequest } from "next/server";
import jwt, { JwtPayload } from "jsonwebtoken";
export function verifyToken(request:NextRequest):JwtPayload | null{
//トークンがあるかどうか
const authHeader = request.headers.get("Authorization");
if(!authHeader){
return null;
}
//トークン持ってくる
const token = authHeader.split(" ")[1]; //"Bearer <TOKEN>"
if (!token) {
console.log("Token is missing");
return null; // なかったらnull
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload ; //検証後、ユーザー情報変換
return decoded; // ペイロード (userId 含む)
} catch (error) {
console.error("JWT verification failed:", error);
return null
}
}
Middlewareを使ってUser Profile取得
ミドルウェアを使ってユーザープロファイルを取得する流れ
-
ログイン時の処理:
- ユーザーがログインすると、サーバーはIDとパスワードを検証します。
- 検証が成功した場合、サーバーはアクセストークン(JWT)を発行し、クライアントに返します。
- クライアント(ブラウザ)はこのトークンを ローカルストレージ に保存します。トークンは後のリクエストでAuthorizationヘッダーに含まれます。
-
クライアントからリクエストが来る:
- クライアントが認証が必要なAPIにアクセスするとき、リクエストヘッダーの
Authorization
にJWTトークンを含めます。
- クライアントが認証が必要なAPIにアクセスするとき、リクエストヘッダーの
-
ミドルウェアでトークンを検証:
- サーバー側で、リクエストを受け取った時にミドルウェアが呼び出され、JWTトークンがリクエストヘッダーに含まれているかチェックします。
- トークンが存在し、有効であれば、そのペイロード(例えば、
userId
)をデコードして、リクエストにユーザー情報を追加します。
-
データベースからユーザー情報を取得:
- ミドルウェアで取得した
userId
を使って、データベース(例えばPrismaやMongoDB)からユーザーのIDやEmailなどの情報を取得します。 - 例えば、
prisma.user.findUnique
を使って、IDに対応するユーザー情報を取得します。
import prisma from "@/lib/prisma"; // Prismaのインスタンス import jwt from "jsonwebtoken"; import { NextRequest, NextResponse } from "next/server"; export function verifyToken(request: NextRequest): JwtPayload | null { const authHeader = request.headers.get("Authorization"); if (!authHeader) { return null; } const token = authHeader.split(" ")[1]; // "Bearer <TOKEN>" if (!token) { console.log("Token is missing"); return null; // トークンがない場合 } try { const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload; // トークン検証 return decoded; // ペイロードを返す(例: userId) } catch (error) { console.error("JWT verification failed:", error); return null; } } export async function getUserProfile(request: NextRequest) { const user = verifyToken(request); // JWTからユーザー情報を取得 if (!user) { return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); } // ユーザーIDを使ってデータベースからプロファイル情報を取得 const userProfile = await prisma.user.findUnique({ where: { id: user.userId }, select: { id: true, email: true, name: true }, // 必要な情報を選択 }); if (!userProfile) { return NextResponse.json({ message: "User not found" }, { status: 404 }); } return NextResponse.json(userProfile, { status: 200 }); }
- ミドルウェアで取得した
-
レスポンス:
- ミドルウェアで取得したユーザー情報(IDやEmailなど)を、後続の処理に渡します。
- 最終的に、ユーザーのプロファイル情報をクライアントに返します。
ミドルウェアのポイント
- トークン検証: JWTトークンが有効かどうかを確認し、ユーザーIDなどの情報を取り出す。
- データベースアクセス: トークンから得た情報を基に、データベースからユーザーの詳細な情報を取得する。
- セキュリティ: トークンがない場合や無効な場合は、401エラー(Unauthorized)を返す。
この流れで、ログイン後にJWTトークンを使って、後続のリクエストでもユーザー情報を取得できるようになります。
Discussion