WebSurfer's Home

Filter by APML

Minimal API で JWT からクレーム情報を取得

by WebSurfer 12. July 2025 13:07

ASP.NET Core Minimal API アプリで、クライアントから送信されてきた JWT からユーザー名やトークンの有効期限などの Payload 情報(ASP.NET ではそれらがクレーム情報になる)を取得する方法を書きます。

JWT (デコード結果)

先の記事「Minimal API で JWT を使った認証」に書いた JWT による認証を実装した ASP.NET Core Minimal API アプリでは、普通に Controller を使用した Web API と同様に、上の画像の JWT の Payload 部分は ASP.NET Core で言うクレームのコレクションとして取り扱われます。

先の記事に従って JWT の発行機能を実装すれば、上の画像通り JWT の Payload に exp (Expiration Time), iss (Issuer), aud (Audience) はデフォルトで含まれます。

任意のクレーム情報を追加することも可能です。例えば Admin ロールとユーザーの id を追加する場合は、先の記事に書いた JWT を生成するヘルパメソッド BuildToken で JwtSecurityToken コンストラクタのパラメータ claims に追加する情報を設定します。具体例は以下の通りです。

// JWT を生成するヘルパメソッド
private static string BuildToken(IConfiguration config, string id)
{
    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(config["Jwt:Key"]!));

    var creds = new SigningCredentials(
        key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: config["Jwt:Issuer"],
        audience: config["Jwt:Issuer"],
        
        // Admin ロールと id を Claims に追加
        claims: [
            new Claim(ClaimTypes.Role, "Admin"),
            new Claim("UserId", id)
        ],
        
        notBefore: null,
        expires: DateTime.Now.AddMinutes(30),
        signingCredentials: creds);

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

上の BuildToken メソッドで生成した JWT をデコードしたものがこの記事の一番上の画像で、Payload に Admin ロールと UserId が追加されています。

エンドポイントにおいて JWT からクレーム情報を取得するのは先の記事「JWT からクレーム情報を取得」に書いた方法と同様にして可能です。

ただし、先の記事のアプリでは ControllerBase.User プロパティから ClaimsPrincipal オブジェクトを取得していましたが、Minimal API は Controller を使わないので、そこのところのみ異なります。

Minimal API では、Microsoft のドキュメント「Minimal API クイック リファレンス」の「特殊な型」のセクションに書いてありますように、エンドポイントに設定したデリゲートの引数に ClaimsPrincipal を含めることで取得します。具体例は以下の通りです。

// 認証が必要なエンドポイント
app.MapGet("/todoitems/auth", [Authorize(Roles = "Admin")] 
                    async (TodoDb db, ClaimsPrincipal user) =>
{
    // JWT の Payload の情報は以下のようにして取得できる
    string? role = user.FindFirst(ClaimTypes.Role)?.Value;
    string? userId = user.FindFirst("UserId")?.Value;
    string? exp = user.FindFirst("exp")?.Value;  // UNIX 時間
    if (exp != null)
    {
        var ticks = long.Parse(exp) * 1000L * 1000L * 10L +
                    DateTime.UnixEpoch.Ticks;
        var expDateTime = new DateTime(ticks, DateTimeKind.Utc);
        var dateTimeUtcNow = DateTime.UtcNow;
        bool isBeforeExp = expDateTime > dateTimeUtcNow;
    }

    return Results.Ok(await db.Todos.ToListAsync());
});

クライアントから JWT が送信されてくると、その Payload の情報から ClaimsPrincipal オブジェクトが生成され、上のコードのデリゲートの引数 ClaimsPrincipal user に渡されます。それから FindFirst(String) メソッドを使ってJWT の Payload の情報を取得できます。

下の画像は Visual Studio 2022 のデバッガを使って上のコードのローカル変数の値を表示したものです。JWT はこの記事の一番上の画像のものです。JWT の Payload の role, userId, exp の情報が正しく取得できていることが分かりますでしょうか?

クレーム情報の取得

Tags: , , , , ,

Web API

JWT からクレーム情報を取得

by WebSurfer 20. August 2024 16:47

トークンベース (JWT) の認証を実装した ASP.NET Core Web API アプリで、クライアントから送信されてきた JWT から、ユーザー名やトークンの有効期限などの情報を取得する方法を書きます。

デコードされた JWT

実は最近知ったのですが、先の記事「ASP.NET Core Web API に Role ベースの承認を追加」に書いた JWT による認証を実装した ASP.NET Core Web API アプリでは、上の画像の JWT の Payload 部分は ASP.NET Core で言うクレームのコレクションとして取り扱われるようです。

具体的に言うと、JWT の Payload の項目から ClaimsIdentity オブジェクトが作られ、コントローラーに使われる ControllerBase クラスの User プロパティで取得できる ClaimsPrincipal オブジェクトに ClaimsIdentity が含まれるようになります。

なので、上の画像のように Payload に Role を追加すれば、先の記事に書いたように [Authorize(Roles = "Admin")] 属性によって Web API 側でアクセス制限ができるようになります。

さらに、ClaimsIdentity オブジェクトの各クレームの値は ClaimsPrincipal クラスの FindFirst(String) メソッドを使って取得できますので、必要があればサーバー側でユーザー名とかトークンの有効期限などを取得して、その内容に応じて何らかの処置を行うというようなことも可能になります。

以下に具体例を書いておきます。

まず、上の画像のように JWT の Payload に Role、UserId、exp を含める方法ですが、先の記事「Blazor WASM から ASP.NET Core Web API を呼び出し」で紹介したトークンを発行する API の BuildToken メソッドで、JwtSecurityToken コンストラクタの引数の claims に以下のようにロール情報と UserId を追加します。上の画像の JWT の Payload の "exp" は引数 expires に設定した UNIX 時間となります。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TokenController : ControllerBase
    {
        private readonly IConfiguration _config;
        private readonly UserManager<IdentityUser> _userManager;

        public TokenController(IConfiguration config,
                               UserManager<IdentityUser> userManager)
        {
            _config = config;
            _userManager = userManager;
        }

        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> CreateToken(LoginModel login)
        {
            string? id = login.Username;
            string? pw = login.Password;
            IActionResult response = Unauthorized();

            if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(pw))
            {
                var user = await _userManager.FindByNameAsync(id);
                if (user != null && 
                    await _userManager.CheckPasswordAsync(user, pw))
                {
                    // クライアントから送信されてきた id を UserId 
                    // として JWT の Payload に含める
                    var tokenString = BuildToken(id);

                    response = Ok(new { token = tokenString });
                }
            }

            return response;
        }

        private string BuildToken(string userId)
        {
            var key = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));

            var creds = new SigningCredentials(
                key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: _config["Jwt:Issuer"],
                audience: _config["Jwt:Issuer"],

                // Role 情報と UserID を追加
                claims: [ 
                    new Claim(ClaimTypes.Role, "Admin"),
                    new Claim("UserId", userId)
                ],

                notBefore: null,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

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

    public class LoginModel
    {
        public string? Username { get; set; }
        public string? Password { get; set; }
    }
}

Web API のコントローラーで、ControllerBase.User プロパティから取得できる ClaimsPrincipal オブジェクトの FindFirst(String) メソッドを使って、JWT の Payload の情報を取得できます。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        // ・・・中略・・・

        [Authorize(Roles = "Admin")]
        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            // 2024/8/20
            // JWT の Payload の情報は以下のようにして取得できる
            string? role = User.FindFirst(ClaimTypes.Role)?.Value;
            string? userId = User.FindFirst("UserId")?.Value;
            string? exp = User.FindFirst("exp")?.Value;  // UNIX 時間
            if (exp != null)
            {
                var ticks = long.Parse(exp) * 1000L * 1000L * 10L + 
                            DateTime.UnixEpoch.Ticks;
                var expDateTime = new DateTime(ticks, DateTimeKind.Utc);
                var dateTimeUtcNow = DateTime.UtcNow;
                bool isBeforeExp = expDateTime > dateTimeUtcNow;
            }

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

クライアントから上の画像の JWT を送信した結果は下の画像の通りとなります。Web API のコントローラーで上の画像の Payload の情報を取得できていることが分かりますでしょうか?

JWT の Payload 情報の取得結果



【オマケ】

本題とは直接関係ないことですが、Microsoft のドキュメント「ASP.NET Core でのクレーム ベースの承認」に ClaimsIdentity というのは何かを、運転免許証を例にとって説明してあって分かりやすかったので、忘れないように以下に抜粋を貼っておきます。

"運転免許証にはあなたの生年月日が記載されています。 この場合、クレーム名は DateOfBirth になり、クレームの値は、たとえば 8th June 1970 となります。そして発行者は、運転免許証機関になります。クレーム ベースの承認では、簡単に言うと、クレームの値がチェックされ、その値に基づいてリソースへのアクセスが許可されます。たとえば、ナイト クラブへの入場 (アクセス) であれば、承認プロセスは次のようになる可能性があります。"

"出入口のセキュリティ責任者が、あなたの生年月日クレームの値と、発行者 (運転免許機関) を信頼するかどうかを評価します。"

ちなみに、ClaimsIdentity クラスの解説は以下のようになっています。

"ClaimsIdentity クラスは、クレームベースの ID の具体的な実装です。つまり、クレームのコレクションによって記述される ID です。クレームは発行者によるエンティティに関する宣言で、プロパティ、権利、またはその他の品質が記述されます。このようなエンティティは、クレームの対象と言われます。クレームは Claim クラスによって表されます。ClaimsIdentity に含まれるクレームは、対応する ID が表すエンティティに記述し、承認と認証の決定を行うために使用できます。"

免許証の例を読んでからでないと分かりにくいかも。何を隠そう、自分はさっぱり分かりませんでした。(笑)

Tags: , , ,

Authentication

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  January 2026  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar