オウルです。
JWTトークンの復習も兼ね、JWTトークンを使用してASP.NET Core APIをセキュアにします。JWTトークンを処理する認証ミドルウェアではMicrosoft.AspNetCore.Authentication.JwtBearerパッケージを使用します。
APIサーバ自身(リソースサーバ)がJWTトークンを発行(認可サーバを兼)、クライアントから送信されたJWTトークンを検証、その後アクションが処理(認可)されるシンプルな構成にします。※シンプル構成のため署名鍵は自作した鍵(文字列)を使用します。
OAuthとOpenID Connectについては、こちらの記事がめちゃめちゃ分かり易いです。
一番分かりやすい OAuth の説明
一番分かりやすい OpenID Connect の説明
WSL v1 | Ubuntu18.04 LTS |
App | ASP.NET Core 3.1.302 |
JWS形式のJWTトークンの保証について
JWTには、JSON Web Signature(JWS) FC7515、JSON Web Encryption(JWE) RFC7516 があり、署名と暗号化の仕様があります。今回は署名です。
署名付きJWTトークンが保証するのは”改ざん”されていないことです。盗聴ではありません。そのため、JWTに機密情報を含めない、そしてHTTPSで必ず送信するなど別途対策が必要となります。
- おすすめのドキュメント
- RFC以外に、JSON Web TokensはJWTトークンについて体系的に学ぶことができるおすすめのドキュメントです。
JwtBearerOptionsの構成
JWTトークンを検証するようにするには、JwtBearerOptionsを構成する必要があります。
クレーム
まず、JwtBearerOptionsに関係するクレームです。詳細は、JWT(RFC7519)を参照してください。
- “iss” (Issuer) Claim
- JWT の発行者の識別子。
- “sub” (Subject) Claim
- JWTの件名。※アプリケーション固有、サンプルは下記のトークン生成を参照してください。
- “aud” (Audience) Claim
- 受信トークンの意図された受信者、トークンがアクセスを許可するリソースを表し、audクレームに自身の識別子が存在しない場合は拒否しなければならない。
JwtBearerOptionsの構成
JwtBearerOptionsのAuthorityプロパティには、トークンを発行する認証サーバのアドレスを指定しますが、今回はOpenSSLコマンドで生成した署名鍵を使用するため設定しません。
opensslでキーを生成
1 |
openssl rand -base64 128 |
appsettings.json
1 2 3 4 5 |
"JwtSettings" : { "Secret": "[ openssl generate key ]", "SiteUrl": "[ api url ]", "JwtExpireDay": "1" }, |
JwtSettings class
1 2 3 4 5 6 |
public class JwtSettings { public string Secret { get; set; } public string SiteUrl { get; set; } public string JwtExpireDay { get; set; } } |
ConfigureServices
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public void ConfigureServices(IServiceCollection services) { ・・・ var jwtSettings = new JwtSettings(); var section = Configuration.GetSection(nameof(JwtSettings)); section.Bind(jwtSettings); services.Configure<JwtSettings>(section); services.AddSingleton<JwtSettings>(sp => sp.GetRequiredService<IOptions<JwtSettings>>().Value ); var key = Encoding.ASCII.GetBytes(jwtSettings.Secret); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Audience = jwtSettings.SiteUrl; // HTTPSでのみJWTベアラートークンを渡す(既定: true) options.RequireHttpsMetadata = true; // AuthenticationPropertiesにトークンを保存するかどうか options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters { // トークンの検証中に発行者を検証するかどうか ValidateIssuer = true, // トークンに著名したSecurityKeyの検証が呼び出されるかどうか ValidateIssuerSigningKey = true, // 署名の検証に使用するキーを設定 IssuerSigningKey = new SymmetricSecurityKey(key), // トークンの発行者との照合に使用される有効な発行者を表す文字列を取得または設定 ValidIssuer = jwtSettings.SiteUrl, // トークンの検証中にオーディエンスを検証するかどうか ValidateAudience = true // トークンのオーディエンスとの照合に使用される有効なオーディエンスを表す文字列を取得または設定 ValidAudience = jwtSettings.SiteUrl, } } } |
JWTトークンの生成
次のサンプルコードではAllowAnonymous属性を使用したAuthenticateアクションで認証後にJWTトークンを生成しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
private readonly JwtSettings _settings; private readonly ILogger _logger; public AuthenticationsController(ILogger<AuthenticationsController> logger, JwtSettings settings) { _logger = logger; _settings = settings; } [AllowAnonymous] [HttpPost] public IActionResult Authenticate(Login model) { // ここに認証ロジック var successful = Authenticate(model); if(successful) { var token = GenerateToken(model.Email); return Ok(new { token = token }); } else { return Unauthorized(); } } private string GenerateToken(string email) { var key = Encoding.ASCII.GetBytes(_settings.Secret); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity ( // "jti" (JWT ID) Claim new Claim[] { new Claim(JwtRegisteredClaimNames.Jti, usersId), new Claim(JwtRegisteredClaimNames.Email, email) } ), Expires = DateTime.Now.AddDays(int.Parse(_settings.JwtExpireDay)), Audience = _settings.SiteUrl, Issuer = _settings.SiteUrl, SigningCredentials = new SigningCredentials ( key: new SymmetricSecurityKey(key), algorithm: SecurityAlgorithms.HmacSha256Signature ) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } |
JWTトークンを使用してAPIアクセス
PostmanからJWTトークンを取得後、ヘッダーにキー:”Authorization”、値:”Bearer token”をセットしてAPIにアクセスします。
JWTトークンの取得

JWTトークンの構成
https://jwt.ioのデバッガーで生成したJWTトークンの構成を確認します。
ピリオドで区切られた”ヘッダー(Header)”.”ペイロード(Payload)”.”署名(Sinature)”の3つから構成されていることが分かります。デバッガーを使用するとデコードされたヘッダーのアルゴリズム、ペイロードのクレーム情報も確認できます。

ヘッダーにある”alg”(署名のアルゴリズム)、”typ”(ここではJWSオブジェクトの種類。このパラメータはJWSの処理に影響を及ぼさない)はJWS(RFC 7515)に列挙されている、ヘッダーパラメータです。
冒頭で紹介した記事と同じ方の記事となりますが、とても分かり易いです。
IDトークンが分かれば OpenID Connect が分かる
APIにアクセス
次に検証用に用意したAuthorize属性(JWT ベアラー認証)アクションに、取得したJWTトークンを使ってアクセスします。承認されればアクセスが許可されるはずです。
API アクション
1 2 3 4 5 6 7 |
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [HttpGet] public IActionResult GetFruits() { var data = new string[2] { "apple", "grape" }; return Ok(data); } |
HTTP Get

承認され、正常にJSONデータが取得できました。