LoginSignup
16
22

More than 3 years have passed since last update.

ASP.NET(.NET5)のAPIでJWT利用

Last updated at Posted at 2021-01-29

.net環境でJWTつかう機会があるのでメモ。node番はこちら

主な手順

利用の流れは下記のような感じ。

  • Startup.csにてJWTの認証およびチェック基準を設定、有効化
  • Token発行ようのControllerを実装(簡易認証OKならToken発行)
  • 認証を付けたいAPIに[Authorize]を追加し動作チェック

前提

私は基本Mac + CLIです。

  • macでdotnet cli利用
  • ver 5.x(たぶん3.1でも動く)

準備

作業場の確保

では、作業場の確保と必要ライブラリのインストール。

dotnet new api -o dotnet-jwt
cd dotnet-jwt

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

リダイレクトの無効化

Startup.csのConfigure()中のapp.UseHttpsRedirection();をコメントアウトしておきましょう。
強制的にhttpsにリダイレクトされるのを無効化できます。

// app.UseHttpsRedirection();

動作確認

今回は最初から参考実装されているWeatherforecastControllerのGet()メソッドに認証を追加するので、その動作を確認しておきます。
起動して、

dotnet run

リクエストするとJSONが戻ってきます。

curl -X GET http://localhost:5000/weatherforecast

{"date":"2021-01-31T09:37:33.3741+09:00","temperatureC":44,"temperatureF":111,"summary":"Warm"},{"date":"2021-02-01T09:37:33.374102+09:00","temperatureC":28,"temperatureF":82,"summary":"Bracing"},{"date":"2021-02-02T09:37:33.374102+09:00","temperatureC":-8,"temperatureF":18,"summary":"Balmy"},{"date":"2021-02-03T09:37:33.374102+09:00","temperatureC":39,"temperatureF":102,"summary":"Warm"}

実装

では、まずJWT認証を設定し、有効化します。いろいろ記述していますが、大きな流れとしては下記の通り。

  • ConfigureServices()にてservices.AddAuthentication()を追加(設定)
  • Configure()にてapp.UseAuthentication()を追加(有効化)

Startup.cs

では、上記を意識しながら実装します。

Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;

//追加
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace dotnet_jwt
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //追加
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
                {
                    options.TokenValidationParameters =
                    new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = "hoge",
                        ValidAudience = "hoge",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("12345678901234567890"))
                    };
                });

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "dotnet_jwt", Version = "v1" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "dotnet_jwt v1"));
            }

            // app.UseHttpsRedirection();

            app.UseRouting();

            //追加
            app.UseAuthentication();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

TokenController.cs

次にTokenを発行するControllerを実装します。WeatherforecastControllerに実装してもいいのですが、ネットで多くの方が参考にしているAuth0のサンプルではこのような構造になっているようです。
いちおう、簡単な認証(静的なID,PWの一致)を実装していますが、ここを目的に応じた認証にかえるといいでしょう。

TokenController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

//追加
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

namespace dotnet_jwt.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class TokenController : ControllerBase
    {
        [AllowAnonymous]
        [HttpPost]
        public IActionResult Login([FromBody] LoginModel login)
        {
            //POST受け取り
            string username = login.Username;
            string password = login.Password;

            //ダミー認証
            if (username == "hoge" && password == "password")
            {   
                //token生成(下の関数で)
                var tokenString = BuildToken();
                var response = new
                {
                    token = tokenString
                };

                return Ok(response);
            }
            else
            {
                return BadRequest();
            }

        }

        //token生成関数
        private string BuildToken()
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("12345678901234567890"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken(
                issuer: "hoge",
                audience: "hoge",
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds
            );

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

    //loginモデル
    public class LoginModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }


}

Weatherforecast.cs(抜粋)

実装が完了したら認証を適用します。以下は抜粋です。

using Microsoft.AspNetCore.Authorization;を追加し、Get()に[Authorize]を追加。

weatherforecast.cs
        [Authorize]
        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }

 動作確認

まず、普通にリクエストしてみます。先述のものに-vオプションを追加してやりとりを表示してみます。
すると、認証エラーになっているのがわかります。

普通にリクエスト

curl -X GET http://localhost:5000/weatherforecast -v


Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying ::1:5000...
* TCP_NODELAY set
* Connected to localhost (::1) port 5000 (#0)
> GET /weatherforecast HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
-< HTTP/1.1 401 Unauthorized
< Date: Fri, 29 Jan 2021 00:45:50 GMT
< Server: Kestrel
< Content-Length: 0
< WWW-Authenticate: Bearer
< 
* Connection #0 to host localhost left intact

tokenの発行

では、認証用のtokenを取得してみます。
適切なusernameとpasswordを付与してPOSTするとtokenが得られます。

curl -X POST  http://localhost:5000/api/token  -H "Content-Type: application/json" -d '{"username":"hoge","password":"password"}'

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTE4ODMwNzgsImlzcyI6ImhvZ2UiLCJhdWQiOiJob2dlIn0.GytQKuz_lyeDU4RjkVEBpUFQ95JirYfA1PeYurquRn8"}

tokenを付与してリクエスト

tokenをヘッダーに付与してリクエストするとJSONが得られました。

curl -v -X GET  http://localhost:5000/weatherforecast -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTE4ODMwNzgsImlzcyI6ImhvZ2UiLCJhdWQiOiJob2dlIn0.GytQKuz_lyeDU4RjkVEBpUFQ95JirYfA1PeYurquRn8"


{"date":"2021-01-31T09:49:46.110224+09:00","temperatureC":30,"temperatureF":85,"summary":"Cool"},{"date":"2021-02-01T09:49:46.110225+09:00","temperatureC":2,"temperatureF":35,"summary":"Bracing"},{"date":"2021-02-02T09:49:46.110226+09:00","temperatureC":19,"temperatureF":66,"summary":"Bracing"},{"date":"2021-02-03T09:49:46.110226+09:00","temperatureC":28,"temperatureF":82,"summary":"Sweltering"}]

以上です。

16
22
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
16
22