認証に ASP.NET Core Identity を使うけど、アプリ向けに JwtBearer の認証もサポートしたい。JwtBearer でトークンを発行するときは、ユーザーを認証するコードを書くことになるわけなので、そこで Identity の SignInManager や UserManager を使えばいいかもしれない。早速試してみた。
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace IdentityJwtSample { public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) { // 構成にはメモリ内プロバイダーを使う var dict = new Dictionary<string, string> { ["ConnectionStrings:DefaultConnection"] = "Server=(localdb)\\mssqllocaldb;Database=aspnet-IdentityJwtSample-550CDEDB-AB81-4711-8A92-0988E2505FD1;Trusted_Connection=True;MultipleActiveResultSets=true", ["JwtKey"] = "9AC93E2B-8C4F-46C3-B9E5-F7E7CB37A73A", ["JwtIssuer"] = "http://yourdomain.com", }; var builder = new ConfigurationBuilder(); builder.AddInMemoryCollection(dict); return WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseConfiguration(builder.Build()) .Build(); } } public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddAuthentication(options => { // JWT Bearer をデフォルトにする options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = Configuration["JwtIssuer"], ValidAudience = Configuration["JwtIssuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])), }; }); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); // データベースが無ければ作る dbContext.Database.EnsureCreated(); } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } } // アプリケーション用ユーザー public class ApplicationUser : IdentityUser { } // ログインに使うパラメーターを格納 public class LoginViewModel { [Required] [EmailAddress] public string Email { get; set; } [Required] [DataType(DataType.Password)] public string Password { get; set; } } // ユーザー登録に使うパラメーターを格納 public class RegisterViewModel { [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } // 発行したアクセストークンを格納 public class TokenViewModel { public string Token { get; set; } } // API で取得できるユーザー情報を格納 public class UserViewModel { public string Id { get; set; } public string UserName { get; set; } public string Email { get; set; } } [Route("api/user")] public class AccountController : Controller { private readonly UserManager<ApplicationUser> _userManager; private readonly SignInManager<ApplicationUser> _signInManager; private readonly IConfiguration _configuration; public AccountController( UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IConfiguration configuration) { _userManager = userManager; _signInManager = signInManager; _configuration = configuration; } // ユーザーを新規登録してアクセストークンを取得 [HttpPost("register")] public async Task<IActionResult> Register([FromBody]RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { await _signInManager.SignInAsync(user, isPersistent: false); var token = await GenerateJwtTokenAsync(user); return Ok(token); } AddErrors(result); } return BadRequest(ModelState); } // メールアドレスとパスワードでログインしてアクセストークンを取得 [HttpPost("login")] public async Task<IActionResult> Login([FromBody]LoginViewModel model) { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, lockoutOnFailure: false); if (result.Succeeded) { var user = await _userManager.FindByEmailAsync(model.Email); var token = await GenerateJwtTokenAsync(user); return Ok(token); } } return BadRequest(ModelState); } // アクセストークンを生成 async Task<TokenViewModel> GenerateJwtTokenAsync(ApplicationUser user) { var principal = await _signInManager.CreateUserPrincipalAsync(user); var claims = new List<Claim>(principal.Claims); claims.Add(new Claim(JwtRegisteredClaimNames.Sub, user.UserName)); claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"])); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expires = DateTime.Now.AddDays(7); var token = new JwtSecurityToken( _configuration["JwtIssuer"], _configuration["JwtIssuer"], claims, expires: expires, signingCredentials: credentials ); var jwt = new JwtSecurityTokenHandler().WriteToken(token); return new TokenViewModel() { Token = jwt }; } // ログインユーザーの情報を取得 [HttpGet("current")] [Authorize] public async Task<IActionResult> Current() { var user = await _userManager.GetUserAsync(User); var viewModel = new UserViewModel() { Id = user.Id, UserName = user.UserName, Email = user.Email, }; return Ok(viewModel); } private void AddErrors(IdentityResult result) { foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } } }
curl で動作確認してみる。 まずはユーザー登録。
ユーザーの登録に成功し、アクセストークンが返ってきた。
次はログイン。
ログインに成功すればアクセストークンが返ってくる。
そのアクセストークンを使って、認証必須の API を呼び出せるかテスト。
ログインユーザーの情報が返ってきた。テスト成功。