📚

JWT ログイン機能実装の流れ

2025/02/18に公開

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検証ミドルウェアの流れ

  1. リクエストを受け取る:

    • ユーザーがAPIにリクエストを送信する際、そのリクエストには通常AuthorizationヘッダーにJWTが含まれています。
  2. JWTを検証する:

    • ミドルウェアがリクエストを受け取り、AuthorizationヘッダーにJWTが含まれているかをチェックします。
    • JWTが存在すれば、そのトークンを検証して、トークンが改ざんされていないか、有効期限が切れていないかを確認します。
  3. JWTが有効であれば、ペイロードからユーザー情報を取得:

    • JWTが正しく検証され、トークンが有効であれば、トークン内のペイロード(通常はuserIdやユーザー情報)を取り出します。
    • これを使って、データベースなどから該当するユーザー情報を取得することができます。
  4. ユーザー情報をリクエストに追加:

    • 取得したユーザー情報をリクエストのオブジェクトに追加し、次の処理に渡します。これにより、後続の処理でユーザー情報を利用できるようになります。
  5. レスポンスの処理:

    • ミドルウェアが完了した後、レスポンスがクライアントに返されます。例えば、ユーザーがアクセスできるページやリソースが提供されます。

実際の流れ:

  • ログイン → ユーザー情報とパスワードをチェック → 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取得

ミドルウェアを使ってユーザープロファイルを取得する流れ

  1. ログイン時の処理:

    • ユーザーがログインすると、サーバーはIDとパスワードを検証します。
    • 検証が成功した場合、サーバーはアクセストークン(JWT)を発行し、クライアントに返します。
    • クライアント(ブラウザ)はこのトークンを ローカルストレージ に保存します。トークンは後のリクエストでAuthorizationヘッダーに含まれます。
  2. クライアントからリクエストが来る:

    • クライアントが認証が必要なAPIにアクセスするとき、リクエストヘッダーのAuthorizationにJWTトークンを含めます。
  3. ミドルウェアでトークンを検証:

    • サーバー側で、リクエストを受け取った時にミドルウェアが呼び出され、JWTトークンがリクエストヘッダーに含まれているかチェックします。
    • トークンが存在し、有効であれば、そのペイロード(例えば、userId)をデコードして、リクエストにユーザー情報を追加します。
  4. データベースからユーザー情報を取得:

    • ミドルウェアで取得した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 });
    }
    
  5. レスポンス:

    • ミドルウェアで取得したユーザー情報(IDやEmailなど)を、後続の処理に渡します。
    • 最終的に、ユーザーのプロファイル情報をクライアントに返します。

ミドルウェアのポイント

  • トークン検証: JWTトークンが有効かどうかを確認し、ユーザーIDなどの情報を取り出す。
  • データベースアクセス: トークンから得た情報を基に、データベースからユーザーの詳細な情報を取得する。
  • セキュリティ: トークンがない場合や無効な場合は、401エラー(Unauthorized)を返す。

この流れで、ログイン後にJWTトークンを使って、後続のリクエストでもユーザー情報を取得できるようになります。

Discussion