1JWTの3部構造
JWT が header.payload.signature の3つをドットでつないだ文字列であることを図で示す例です。payload に本人情報、signature に署名が入り、ドット区切りという見た目だけ先に掴みます。
// JWT = header . payload . signature をドットで連結した1つの文字列
//
// eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxIn0 . dBjftJeZ4CV...
// └─ header ─────────┘ └─ payload ───┘ └─ signature ┘
//
// header … 署名アルゴリズム 例 { "alg": "HS256" }
// payload … 本人情報やロール 例 { "sub": "1", "role": "admin" }
// signature … 秘密鍵で作った署名(改ざん検知用)
//
// header と payload は base64url エンコードされているだけ(暗号化ではない)
2payloadは暗号化されていない
JWT の payload が「署名されているだけで誰でも読める」ことをプレーン TypeScript で再現した例です。中身を覗けてしまう以上、パスワードなどの秘密情報を payload に入れてはいけない理由が体感できます。
TypeScript
3有効期限(exp)の検証
JWT の verify() が署名検証に加えて必ず行う「有効期限のチェック」をプレーン TypeScript で再現した例です。exp を過ぎたトークンは弾くため、漏れたトークンの悪用可能時間を短く抑えられます。
TypeScript
4JwtModule の登録と発行
@nestjs/jwt の JwtModule を秘密鍵と有効期限つきで登録し、sign() でトークンを発行する例です。秘密鍵は環境変数から読み、exp を必ず付けるのが実務の基本設定です。
import { Module, Injectable } from "@nestjs/common";
import { JwtModule, JwtService } from "@nestjs/jwt";
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET, // 鍵は必ず環境変数から
signOptions: { expiresIn: "15m" }, // 有効期限を必ず設定
}),
],
})
export class AuthModule {}
@Injectable()
export class TokenService {
constructor(private readonly jwt: JwtService) {}
issue(user: { id: number; role: string }): string {
// payload に秘密情報は入れない(誰でも読めるため)
return this.jwt.sign({ sub: user.id, role: user.role });
}
}
5リフレッシュトークンの使い分け
短命なアクセストークンと長命なリフレッシュトークンを組み合わせる実務パターンの例です。アクセストークンは短くして漏えい被害を抑え、切れたらリフレッシュトークンで再発行する、という二段構えにします。
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
@Injectable()
export class TokenService {
constructor(private readonly jwt: JwtService) {}
// ログイン時: 2種類のトークンを返す
issueTokens(userId: number) {
return {
// アクセス: 短命。漏れても被害が及ぶ時間を短くする
accessToken: this.jwt.sign({ sub: userId }, { expiresIn: "15m" }),
// リフレッシュ: 長命。再発行専用で、別の鍵・別の保存先にする
refreshToken: this.jwt.sign(
{ sub: userId, type: "refresh" },
{ expiresIn: "7d" },
),
};
}
// /auth/refresh: リフレッシュを検証して新しいアクセスを発行
refresh(refreshToken: string) {
const payload = this.jwt.verify(refreshToken); // 失敗なら例外
return { accessToken: this.jwt.sign({ sub: payload.sub }, { expiresIn: "15m" }) };
}
}