.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
では、上記を意識しながら実装します。
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の一致)を実装していますが、ここを目的に応じた認証にかえるといいでしょう。
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]を追加。
[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"}]
以上です。