paint-brush
ASP.NET Core プロジェクトに JWT が必要なのはなぜですか?by@igorlopushko
7,140
7,140

ASP.NET Core プロジェクトに JWT が必要なのはなぜですか?

Igor Lopushko16m2024/02/13
Read on Terminal Reader

このストーリーは、JWT を生成する Web API を作成し、それを CRUD Web API での認証に使用する方法についてです。
featured image - ASP.NET Core プロジェクトに JWT が必要なのはなぜですか?
Igor Lopushko HackerNoon profile picture
0-item

JWT はJSON Web Tokenの略で、認証ではなく承認メカニズムです。それでは、これら 2 つの違いは何かを考えてみましょう。


認証は、ユーザーが本人であることを確認できるメカニズムです。これは、ユーザーがユーザー名とパスワードを入力し、システムがそれらを検証するログイン プロセスです。したがって、認証は「ユーザーは誰なのか?」という質問に答えます。


認可は、ユーザーが特定のリソースに対してどのアクセス権を持っているかを検証できるメカニズムです。これは、ユーザーにいくつかの役割と、特定の役割が持つ一連の権限を付与するプロセスです。したがって、承認は、ユーザーがシステム内でどのような権限を持っているかという質問に答えます。

認証と認可


常に認証が最初に行われ、認可が 2 番目に行われることを理解することが重要です。つまり、本人確認をするまでは許可を得ることができないのです。しかし、最も一般的な認証方法は何でしょうか? Web アプリケーションの承認を処理するには、主に 2 つのアプローチがあります。

セッション

認証ユーザーに対する Web 上の従来のアプローチは、Cookie ベースのサーバー側セッションです。このプロセスは、ユーザーがログインし、サーバーがユーザーを認証すると開始されます。その後、サーバーはセッション ID を持つセッションを作成し、それをサーバーのメモリのどこかに保存します。サーバーはセッション ID をクライアントに送り返し、クライアントはセッション ID を Cookie に保存します。すべてのリクエストに対して、クライアントはリクエストの一部としてセッション ID を送信し、サーバーはメモリ内のセッション ID と、このセッションに関連するユーザーの権限を確認します。

セッションベースの認証

トークン

もう 1 つの一般的なアプローチは、承認にトークンを使用することです。このプロセスは、ユーザーがログインとパスワードを入力し、クライアントがサーバーにログイン要求を送信すると同様に開始されます。セッションを作成する代わりに、サーバーはシークレット トークンで署名されたトークンを生成します。次に、サーバーはトークンをクライアントに送り返し、クライアントはそれをローカル ストレージに保存する必要があります。セッションベースのアプローチと同様に、クライアントはリクエストごとにトークンをサーバーに送信する必要があります。ただし、サーバーはユーザー セッションに関する追加情報を保存しません。サーバーは、トークンが作成されて秘密鍵で署名されてから変更されていないことを検証する必要があります。

トークンベースの認可

セッションとトークン

セッションベースの認証アプローチは、クロスサイト リクエスト フォージェリ (CSRF) として知られる攻撃に対して脆弱になる可能性があります。これは、攻撃者がログインしているサイトを参照して、支払いの送信やパスワードの変更など、意図しないアクションを実行する一種の攻撃です。


もう 1 つは、セッション ベースの承認アプローチを使用すると、クライアントとサーバーの間にステートフル セッションが作成されることです。問題は、クライアントが同じアプリケーションのスコープ内の異なるサーバーにアクセスしたい場合、それらのサーバーがセッション状態を共有する必要があることです。別のケースでは、セッションが異なるため、クライアントは各サーバーで認証される必要があります。

セッションベースの認可状態共有


一方、トークンベースの認証アプローチでは、サーバー側にセッション データを保存する必要がなく、複数のサーバー間の認証が簡素化される可能性があります。


ただし、トークンは依然として攻撃者によって盗まれる可能性があり、トークンを無効にすることも困難な場合があります。この記事では、詳細と無効化の処理方法について詳しく説明します。

JWT

JSON Web Token (JWT) は、関係者間で情報を JSON オブジェクトとして安全に送信するためのコンパクトで自己完結型の方法を定義するオープン スタンダードです。この情報はデジタル署名されているため、検証および信頼できます。 JWT は、シークレット ( HMACアルゴリズムを使用)、またはRSAまたはECDSAを使用した公開/秘密キーのペアを使用して署名できます。

JWT構造

JSON Web トークンは、ドットで区切られた 3 つの部分で構成されます.


  • ヘッダ
{ "alg": "HS256", "typ": "JWT" }

ヘッダーは通常、トークンのタイプと使用されている署名アルゴリズムの 2 つの部分で構成されます。


  • ペイロード
{ "sub": "1234567890", "name": "John Doe", "admin": true }

ペイロードには、ユーザーに関するステートメントであるクレームが含まれています。ペイロードはBase64Url でエンコードされて、JSON Web トークンの 2 番目の部分を形成します。クレームとして使用される標準フィールドの説明は、ここで見つけることができます。


  • サイン
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

署名部分を作成するには、エンコードされたヘッダー、エンコードされたペイロード、シークレット、およびヘッダーで指定されたアルゴリズムを取得し、署名する必要があります。


通常、トークンは次のようになります。

xxxxx.yyyyy.zzzzz


jwt.ioに移動して、サンプル トークンまたは独自のトークンをデバッグできます。トークンを「エンコード済み」フィールドに貼り付けて、トークン署名のアルゴリズムを選択するだけです。

jwt.io デバッガー

.NETプロジェクト

JWT がどのように機能するかについての理論的な知識が得られたので、それを実際のプロジェクトに適用できます。コーヒー エンティティの CRUD 操作を表す単純な API があると仮定します。 Coffee API を表す ASP.NET Core API プロジェクトを作成します。その後、JWT を生成できる Identity API を表す別の ASP.NET Core API プロジェクトを作成します。実際には、認証/認可の目的でIdentity ServerまたはOkta 、またはAuth0を使用することになるでしょう。ただし、JWT の生成方法を示すために独自の Identity API を作成します。 Identity API が完了したら、そのコントローラーを呼び出し、ユーザーのデータに基づいて JWT を生成できます。また、リクエストごとに JWT を渡すことを必要とする認可構成を使用して、Coffee API を保護することもできます。

.NET プロジェクトの状況

コーヒーAPI

まず、Coffee API を表す単純な ASP.NET Core API プロジェクトを作成します。このプロジェクトの構造は次のとおりです。

Coffee API - プロジェクトの構造


Modelフォルダー内のCoffee.csから始めましょう。これは、 IdプロパティとNameプロパティを持つ単純なエンティティです。

 namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } }


API の操作中にエンティティを保存する必要があります。そこで、簡単なインメモリストレージを紹介しましょう。これは、 DataフォルダーのStorage.csファイルにあります。

 namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } }


Coffee API へのリクエストを表すクラスが必要です。そこで、 ContractsフォルダーにCoffeeRequest.csを作成しましょう。

 namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } }


それが完了すると、コーヒー エンティティの CRUD 操作を表すCoffeeController.cs Controllerフォルダーに実装できます。

 using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } }


Coffee API が完成したので、プロジェクトを実行して次のように Swagger UI を確認できます。

コーヒー API - Swagger UI

アイデンティティAPI

Identity API を表す別の ASP.NET Core API プロジェクトを作成してみましょう。このプロジェクトの構造は次のとおりです。

ID API - プロジェクト構造

まず、 Contractsフォルダー内のTokenGenerationRequest.csから始めましょう。これは、 EmailプロパティとPasswordプロパティを含む新しい JWT の生成のリクエストを表します。

 namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } }


JWT 生成のロジックを表すTokenController.csのみを実装する必要があります。ただし、その前にMicrosoft.AspNetCore.Authentication.JwtBearer NuGet パッケージをインストールする必要があります。

 using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } }


SecretKeyIssuerAudienceなどの機密性の高い const は構成内のどこかに配置する必要があることに注意してください。これらは、このテスト プロジェクトを簡素化するためだけにハードコーディングされています。 Lifetimeフィールドは 20 分に設定されています。これは、トークンがその時間有効であることを意味します。このパラメータを構成することもできます。


これで、プロジェクトを実行し、次のように Swagger UI を確認できるようになります。

ID API - Swagger UI


/tokenエンドポイントを呼び出して、新しい JWT を生成しましょう。次のペイロードを試してください。

 { "email": "[email protected]", "password": "password" }


Identity API は、対応する JWT を生成します。

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM

Coffee API での認証の有効化

Identity API の準備が整い、トークンが提供されると、承認によって Coffee API を保護できるようになります。再度、 Microsoft.AspNetCore.Authentication.JwtBearer NuGet パッケージをインストールする必要があります。


認証サービスにより必要なサービスを登録する必要があります。ビルダーを作成した直後に、次のコードをProgram.csファイルに追加します。

 var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization();


ミドルウェア内の順序が重要であることを覚えておくことが重要です。 AddAuthentication()メソッドを呼び出し、認証スキーマとしてJwtBearerDefaults.AuthenticationSchemeを指定することで、認証を有効にします。これは、 Bearer値を含む定数です。

 namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } }


認可中に JWT のどのパラメータが検証されるかを記述するTokenValidationParametersを指定する必要があります。また、Identity API のsigningCredentialsと同様にIssuerSigningKeyを指定して、JWT 署名を検証します。 TokenValidationParametersの詳細については、こちらをご覧ください。


次のコード部分は、認証および認可機能を有効にするミドルウェアをビルダーに追加します。 UseHttpsRedirection()メソッドとMapControllers()メソッドの間に追加する必要があります。

 app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();


これで、コントローラーまたはそのアクションに対してAuthorize属性を使用できるようになりました。このコードを適用すると、 CoffeeControllerのすべてのアクションが承認メカニズムで保護され、JWT をリクエストの一部として送信する必要があります。

 [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { ..


Coffee API のエンドポイントを呼び出すと、 HttpContext.Userデバッグして、JWT で指定したクレームを含むIdentityが設定されていることを確認できます。これは、ASP.NET Core が内部で承認をどのように処理するかを理解する上で重要です。

Coffee API - クレームは JWT から入力されます

Swagger UI に承認を追加する

私たちは認可によって Coffee API を保護するために素晴らしい仕事をしました。ただし、Coffee API プロジェクトを実行して Swagger UI を開いた場合、リクエストの一部として JWT を送信することはできません。これを修正するには、次のコードでProgram.csファイルを更新する必要があります。

 builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); });


その後、右上隅に[承認]ボタンが表示されるようになります。

Coffee API - 承認ボタンが表示されました


「承認」ボタンをクリックすると、次のように JWT を入力できるようになります。

Coffee API - JWT 値を入力

テストに Postman を使用する

Swagger UI の使用に限定する必要はなく、Postman ツールを通じて API のテストを実行できます。まず、Identity API の/tokenエンドポイントを呼び出してみましょう。 JSON をペイロードとして使用するため、 Headersセクションで値application/jsonを使用してContent-Typeヘッダーを指定する必要があります。

Identity API - ヘッダーの指定


その後、 /tokenエンドポイントを呼び出して、新しい JWT を取得できます。

ID API - JWT の生成


これで、JWT をコピーし、それを使用して Coffee API を呼び出すことができます。エンドポイントをテスト、作成、更新する場合は、Identity API と同様のContent-Typeヘッダーを指定する必要があります。 Authorizationヘッダーにも値Bearer [your JWT value]を設定する必要があります。その後、送信ボタンを押すだけで結果が表示されます。

Coffee API - すべてのエンティティを取得する

ロールベースの認可

覚えているとおり、JWT のペイロード部分は、正確にキーと値のペアの値を持つクレームのセットです。ロールベースの認可を使用すると、ユーザーが属するロールに応じてアプリケーション リソースへのアクセスを区別できます。


Identity API のTokenController.csファイルのCreate()メソッドを、ロールの新しいクレームを追加するコードで更新するとします。 Coffee API でロールベースの認証を処理できます。 ClaimTypes.Role 、ロール要求の事前定義された名前です。

 var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") };


CoffeeController.csファイルのAuthorize属性を更新して、ロール名を指定します。

 [Authorize(Roles = "Barista")]


現在、Coffee API を呼び出すすべてのユーザーは、 Barista値を含むロール要求を持っている必要があります。そうしないと、 403 Forbiddenステータス コードが返されます。

クレームベースの認可

Authorize属性は、ロールベースの認証を簡単に処理できます。しかし、それだけでは不十分で、年齢などのユーザーのプロパティに基づいてアクセスを区別したい場合はどうすればよいでしょうか?おそらく、クレームを JWT に追加し、それを使用して承認ロジックを構築できることはすでに推測されているでしょう。ロールが事前定義されたタイプの同じクレーム オブジェクトであるのと同様、ロール ベースの承認自体はクレーム ベースの承認の特殊なケースです。


新しいクレームIsGourmetを追加するコードを使用して、Identity API のTokenController.csファイルのCreate()メソッドを更新しましょう。

 var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") };


Coffee API の Program.cs ファイルでは、クレームを検証し、 Authorize属性で使用できるポリシーを作成する必要があります。次のコードは、 AddAuthentication()メソッド呼び出しの直後に追加する必要があります。

 builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); });


CoffeeController.csファイルのAuthorize属性を更新して、ポリシー名を指定します。

 [Authorize(Policy = "OnlyForGourmet")]

まとめ

おめでとう! .NET での JWT の学習に多大な努力を払いました。ここで、JWT の原則と、.NET アプリケーションで承認を実行するために JWT を使用することが重要である理由をしっかりと理解する必要があります。ただし、ASP.NET Core アプリケーションの認証と承認の領域では表面をなぞっただけです。


この記事で説明したトピックに関する Microsoft のドキュメントを参照することをお勧めします。 .NET プラットフォームには、承認とロール管理のための組み込み機能も多数あります。この記事に追加するとよいのは、承認に関する Microsoft のドキュメントです。