LoginSignup
15
28

More than 3 years have passed since last update.

[Microsoft] ASP.NET CoreでJWT認証するそこそこシンプルなサンプル(統合テスト付き)

Last updated at Posted at 2020-01-14

これはなに?

ASP.NET Coreにおいて、JWTトークンを発行してもらうコードと、JWTトークンを検証するコードのサンプルです。

dotnetのバージョンは、すでにサポート切れの2.2です。
たぶん3.1でもそんなに変わらないはずです。

統合テストも作っています。

サンプルプログラム

プログラム全体はこちらに置いています。
https://github.com/sengokyu/Ex.JwtAuth

git cloneしてdotnet test ExJwtAuth.Testsとしてもらえれば、テストが動きます。

JWTトークンを発行してもらうコード

ログインIDとパスワードを受け付けて、JWTトークンをJSONとして返しています。
ログインIDとパスワードを受け付けていますが、何もしておらず素通しです。

JWTトークンは、System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandlerクラスを使用すると生成できます。

(サンプルは、すべてをASP.NET MVCのコントローラの中でやっています。いわゆるファットコントローラというやつです。よいこは真似してはダメです)。

ExJwtAuth/Controllers/AuthenticationController.cs
        [AllowAnonymous]
        [HttpPost]
        public IActionResult Login(
            [Required][FromBody]LoginParam loginParam
        )
        {
            var handler = new JwtSecurityTokenHandler();
            // JWT内に入れるクレームです。
            var claims = new[] {
                new Claim(ClaimTypes.Name, loginParam.Username)
            };
            var subject = new ClaimsIdentity(claims);
            var credentials = new SigningCredentials(
                JwtSecurityConfiguration.SecurityKey,
                SecurityAlgorithms.HmacSha256);
            // ここでトークンを生成しています。
            var token = handler.CreateJwtSecurityToken(
                audience: JwtSecurityConfiguration.Audience,
                issuer: JwtSecurityConfiguration.Issuer,
                subject: subject,
                signingCredentials: credentials);
            var tokenText = handler.WriteToken(token);
            var result = new
            {
                token = tokenText
            };

            return Ok(result);
        }

JWTトークンを検証するコード

JWTトークンの検証は、あらかじめ用意されている機能を追加するだけです。
Startupクラスの中で有効化します。

AddJwtBearerメソッドに渡すオプションは、また別途DIするようにしています。

ExJwtAuth/Startup.cs
        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions: null);
// 省略
            // JwtBearerOptionsの設定は別クラスでやる
            services
                .AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerConfigureOptions>();

        }

追加した機能を有効にします。
Useする順番大事です。

ExJwtAuth/Startup.cs
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app
                .UseAuthentication()
                .UseMvc();
        }

JWTを検証する設定

Webサーバに渡されたJWTを検証する設定は別クラスにしました。
AudienceもIssurerも検証するようにしています。

Audienceの検証は、手抜きして何が来てもOKなようにしています。
実際はデータベースを検索したりします。

Configurations/JwtBearerConfigureOptions.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace ExJwtAuth.Configuratins
{
    public class JwtBearerConfigureOptions : IConfigureNamedOptions<JwtBearerOptions>
    {
        public void Configure(string name, JwtBearerOptions options)
        {
            if (name != JwtBearerDefaults.AuthenticationScheme)
            {
                return;
            }

            options.TokenValidationParameters = new TokenValidationParameters
            {
                AudienceValidator = this.AudienceValidatorDelegate,
                ValidIssuer = JwtSecurityConfiguration.Issuer,
                IssuerSigningKey = JwtSecurityConfiguration.SecurityKey,
                ValidateAudience = true,
                ValidateIssuer = true,
                ValidateIssuerSigningKey = true,
            };
        }

        public void Configure(JwtBearerOptions options)
        {
            Configure(JwtBearerDefaults.AuthenticationScheme, options);
        }

        public bool AudienceValidatorDelegate(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
        {
            // 実際は、データベースを見たりする。
            return true;
        }
    }
}

ログイン状態を検証するコード

ログイン状態を検証するために、ログインしているユーザ名をJSONで返すコードを追加しました。

ExJwtAuth/Controllers/AuthenticatinController.cs
        [Authorize()]
        [HttpGet]
        public IActionResult GetAction()
        {
            var username = HttpContext.User.Identity.Name;
            var result = new
            {
                username = username
            };

            return Ok(result);
        }

統合テスト

統合テストでは、以下の内容をテストしています。

  • JWTトークン無しでアクセスして HTTP 401 が返ること
  • ログインID/パスワードをPOSTしてJWTトークンが返ること
  • JWTトークン付きでアクセスしてログインIDが返ること

テストのアサーションには、FluentAssertionsを使用しています。Assert.Equal(期待値, 実際の値)みたいに書くよりは、書きやすく読みやすいのではないでしょうか。

テストクラスの雛形

テストクラスはIClassFixtureを実装します。xUnitテストランナーに対して、テスト間で情報を持ちまわるようにすることを指示しています。

コンストラクタでWebApplicationFactoryクラスを受け取ります。このクラスを使ってHTTPクライアントを生成します。

    public class AuthenticationApiTests
        : IClassFixture<WebApplicationFactory<Startup>>
    {
        private readonly WebApplicationFactory<Startup> factory;

        public AuthenticationApiTests(WebApplicationFactory<Startup> factory)
        {
            this.factory = factory;
        }
    }

JWTトークン無しでアクセスして HTTP 401 が返ることを確認するテスト

[Fact]をつけたものが、xUnitに認識されて実行されます。

HTTP Clientを作って、トップページへアクセスし、HTTPステータスコードを確認しています。

        [Fact]
        public async Task Test未認証だと401()
        {
            // Given
            var uri = "/";

            using (var client = factory.CreateClient())
            {
                // When
                var response = await client.GetAsync(uri);

                // Then
                response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
            }
        }

その他のテスト

長くなるので省略します。githubを見てください。
https://github.com/sengokyu/Ex.JwtAuth/blob/master/ExJwtAuth.Tests/AuthenticationApiTests.cs

JWT認証に必要なパッケージ

以下のパッケージが追加で必要でした。

リンク

おまけ

テストクラスのテストメソッド名を日本語にするのは、結構アリだと思うんですけどどうでしょう?

15
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
28