こんにちは、MLBお兄さんこと松村です。オルターブース Advent Calendar 2022 3日目のブログです。
前日はきよぴーのターンでした。もう5ヶ月が経つなんて早いですね。
先日のブログでも紹介したとおり、.NET Conf 2022 Recap Event 福岡で登壇しました。
イベントについてはこちらをご覧ください。そのうちアーカイブ動画が公開されると思います。
このイベントでお話ししたのは ASP.NET Core Minimal API ですが、資料については公開していますので、こちらをご覧ください。
さて、この登壇のなかで個人的な新しい発見として dotnet user-jwts
というコマンドラインです。
このコマンドラインは、開発環境で使用するための JWT (JSON Web Tokens) を発行・管理するための機能です。
.csproj のディレクトリで dotent user-jwts create
を実行すると JWT が発行されます。
$ dotnet user-jwts create New JWT saved with ID 'f60f7427'. Name: YutaMatsumura Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ill1dGFNYXRzdW11cmEiLCJzdWIiOiJZdXRhTWF0c3VtdXJhIiwianRpIjoiZjYwZjc0MjciLCJhdWQiOlsiaHR0cDovL2xvY2FsaG9zdDo4OTkyIiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzMzYiLCJodHRwOi8vbG9jYWxob3N0OjUwNzQiLCJodHRwczovL2xvY2FsaG9zdDo3MDM1Il0sIm5iZiI6MTY2OTczMzg3NSwiZXhwIjoxNjc3NTk2Mjc1LCJpYXQiOjE2Njk3MzM4NzYsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.MBcGYPznnZfx6ekDvbLFrocAaOqHml22hjb-dnajIyw
この JWT を https://jwt.ms/ で解析すると、このような内容になっていることがわかります。
{ "alg": "HS256", "typ": "JWT" }.{ "unique_name": "YutaMatsumura", "sub": "YutaMatsumura", "jti": "f60f7427", "aud": [ "http://localhost:8992", "https://localhost:44336", "http://localhost:5074", "https://localhost:7035" ], "nbf": 1669733875, "exp": 1677596275, "iat": 1669733876, "iss": "dotnet-user-jwts" }.[Signature]
unique_name
や sub
については 環境変数 USERNAME
が使われていることがわかります。
$ echo $USERNAME YutaMatsumura
aud
については、対象のアプリケーションの URL が使われています。
アプリケーション URL は Properties/launchSettings.json
に記載されています。
$ cat Properties/launchSettings.json { "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:8992", "sslPort": 44336 } }, "profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "http://localhost:5074", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "https://localhost:7035;http://localhost:5074", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
当然ですが、独自のクレームを JWT に含めることもできます。
$ dotnet user-jwts create --claim "team=Yankees" New JWT saved with ID '3f1abc05'. Name: YutaMatsumura Custom Claims: [team=Yankees] Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ill1dGFNYXRzdW11cmEiLCJzdWIiOiJZdXRhTWF0c3VtdXJhIiwianRpIjoiM2YxYWJjMDUiLCJ0ZWFtIjoiWWFua2VlcyIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0Ojg5OTIiLCJodHRwczovL2xvY2FsaG9zdDo0NDMzNiIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA3NCIsImh0dHBzOi8vbG9jYWxob3N0OjcwMzUiXSwibmJmIjoxNjY5NzM0OTYwLCJleHAiOjE2Nzc1OTczNjAsImlhdCI6MTY2OTczNDk2MSwiaXNzIjoiZG90bmV0LXVzZXItand0cyJ9.jeaHMf-pRK_rYCjP1jraSJNxSM7q9ia0SWWerSrBNsQ
また JWT 発行に際して、.csproj に紐づくユーザーシークレットも登録されていました。
$ dotnet user-secrets list Authentication:Schemes:Bearer:SigningKeys:0:Value = ji3voylTNLlQtkVLGOQQQbHfqVK3NOlmNEzRwoOpl2s= Authentication:Schemes:Bearer:SigningKeys:0:Length = 32 Authentication:Schemes:Bearer:SigningKeys:0:Issuer = dotnet-user-jwts Authentication:Schemes:Bearer:SigningKeys:0:Id = 44ff75ec
では、この JWT が実際に認証時のトークンとして識別してくれるのかを確認します。
ASP.NET Core Minimal API で、認証必須の API を準備します。
var builder = WebApplication.CreateBuilder(args); // Swaggerの構成 builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.Configure<SwaggerGeneratorOptions>(options => { options.InferSecuritySchemes = true; }); // 認証の構成 builder.Services.AddAuthentication().AddJwtBearer(); builder.Services.AddAuthorization(options => options.AddPolicy("AdminsOnly", b => b.RequireClaim("admin", "true"))); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthentication(); app.UseAuthorization(); // admin=true というクレームをもつ JWT 認証が必要なAPI // ※EnableOpenApiWithAuthenticationは独自に用意した拡張メソッド(本記事には関係ない) app.MapGet("/admin", () => "You are Administrator") .RequireAuthorization("AdminsOnly") .EnableOpenApiWithAuthentication();
※ EnableOpenApiWithAuthentication
について知りたい方はこちらを参照してください。
JWT を指定しない状態で API を実行すると、HTTP 401 (Unauthorized) が返ります。
次に admin=true
というクレームをもつ JWT を発行します。
dotnet user-jwts create --claim admin=true
その JWT を Authorization
ヘッダーに指定した状態で API を実行すると、HTTP 200 (OK) となり、正しく結果を取得することができました。
なお、JWT は指定するものの クレームが無い(一致しない)場合は、HTTP 403 (Forbidden) となります。
ローカル環境での開発のためだけにわざわざ IdP を準備したり、デバッグのたびに JWT を発行する必要がなくなるのは、とても開発が楽になりますね。