C# Tips | セキュリティ:JWTの署名を検証する

C# C#
スポンサーリンク
スポンサーリンク

JWT とリフレッシュトークンの完全設計テンプレート

安全な認証基盤を構築するための「実務レベルの完全テンプレート」をまとめました。 初心者でも理解できるように、ステップバイステップで構造化しながら、 そのまま業務システムに組み込める形で解説します。

全体像:JWT とリフレッシュトークンの役割

JWT(アクセストークン)

  • 有効期限が短い(5〜30分)
  • API にアクセスするためのトークン
  • 改ざん防止の署名付き
  • サーバー側で状態を持たない(ステートレス)

リフレッシュトークン

  • 有効期限が長い(数時間〜数日)
  • 新しい JWT を発行するためのトークン
  • DB に保存して管理する(ステートフル)
  • 盗まれた場合のリスクが高いため厳重に扱う

設計ステップ

JWT とリフレッシュトークンの設計は次の 5 ステップで構築します。

  1. JWT を発行する
  2. リフレッシュトークンを発行する
  3. リフレッシュトークンを DB に保存する
  4. JWT が期限切れのときにリフレッシュトークンで再発行する
  5. ログアウト時にリフレッシュトークンを無効化する

JWT とリフレッシュトークンのデータモデル

RefreshToken テーブル(例)

項目説明
UserIdユーザー識別子
TokenHashリフレッシュトークンのハッシュ値
ExpiresAt有効期限
CreatedAt発行日時
Revoked無効化フラグ

※ リフレッシュトークンは 平文で保存してはいけません。 必ずハッシュ化して保存します。

JWT 発行ユーティリティ(テンプレート)

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public static class JwtService
{
    public static string GenerateAccessToken(string userId, string secretKey, int expireMinutes = 15)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim("userId", userId),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        var token = new JwtSecurityToken(
            issuer: "your-app",
            audience: "your-app",
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(expireMinutes),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
C#

リフレッシュトークン生成ユーティリティ(テンプレート)

using System;
using System.Security.Cryptography;

public static class RefreshTokenService
{
    public static string GenerateRefreshToken(int byteLength = 64)
    {
        byte[] bytes = RandomNumberGenerator.GetBytes(byteLength);
        return Convert.ToBase64String(bytes);
    }

    public static string HashToken(string token)
    {
        using var sha256 = SHA256.Create();
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(token));
        return Convert.ToBase64String(hash);
    }
}
C#

リフレッシュトークン発行フロー(実務コード例)

string accessToken = JwtService.GenerateAccessToken(user.Id, secretKey);
string refreshToken = RefreshTokenService.GenerateRefreshToken();
string refreshTokenHash = RefreshTokenService.HashToken(refreshToken);

// DB に保存
SaveRefreshToken(user.Id, refreshTokenHash, DateTime.UtcNow.AddDays(7));
C#

JWT 検証ユーティリティ(署名検証)

public static ClaimsPrincipal ValidateAccessToken(string token, string secretKey)
{
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

    var parameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "your-app",
        ValidAudience = "your-app",
        IssuerSigningKey = key
    };

    return new JwtSecurityTokenHandler().ValidateToken(token, parameters, out _);
}
C#

リフレッシュトークンによる再発行フロー(重要)

1. クライアントが「JWT が期限切れ」と判断

2. リフレッシュトークンを送信

3. サーバー側で次を確認

  • DB に保存されているハッシュと一致するか
  • 有効期限が切れていないか
  • 無効化されていないか

4. 新しい JWT とリフレッシュトークンを発行

5. 古いリフレッシュトークンを無効化

再発行処理テンプレート(実務コード)

public TokenResponse Refresh(string refreshToken, string secretKey)
{
    string hash = RefreshTokenService.HashToken(refreshToken);

    var stored = GetRefreshTokenFromDb(hash);

    if (stored == null || stored.Revoked || stored.ExpiresAt < DateTime.UtcNow)
        throw new SecurityException("Invalid refresh token");

    // 新しいトークン発行
    string newAccessToken = JwtService.GenerateAccessToken(stored.UserId, secretKey);
    string newRefreshToken = RefreshTokenService.GenerateRefreshToken();
    string newRefreshTokenHash = RefreshTokenService.HashToken(newRefreshToken);

    // 古いトークンを無効化
    RevokeRefreshToken(stored.Id);

    // 新しいトークンを保存
    SaveRefreshToken(stored.UserId, newRefreshTokenHash, DateTime.UtcNow.AddDays(7));

    return new TokenResponse(newAccessToken, newRefreshToken);
}
C#

ログアウト処理(リフレッシュトークン無効化)

public void Logout(string refreshToken)
{
    string hash = RefreshTokenService.HashToken(refreshToken);
    RevokeRefreshTokenByHash(hash);
}
C#

セキュリティの深掘りポイント

リフレッシュトークンは必ずハッシュ化して保存する

盗まれた場合のリスクが高いため、 パスワードと同じ扱いをします。

JWT は短命にする

推奨:5〜30分 理由:盗まれても被害を最小化できるため。

リフレッシュトークンは長命にする

推奨:数時間〜数日 理由:ユーザーが頻繁にログインしなくて済むため。

HTTPS は必須

JWT とリフレッシュトークンは平文で送られるため、 HTTPS がないと盗聴されます。

完全設計図まとめ

このテンプレートを使うことで、次の機能が揃います。

  • JWT の安全な発行
  • JWT の署名検証
  • リフレッシュトークンの安全な生成
  • リフレッシュトークンのハッシュ化保存
  • JWT の再発行
  • リフレッシュトークンの無効化
  • ログアウト処理

業務システムにそのまま組み込めるレベルの 「完全な認証基盤」が構築できます。

タイトルとURLをコピーしました