ASP.NET Core + React + Typescriptで認証付きWebアプリのひな型を作る⑤jwtトークン機能追加
概要
④までで、ログイン機能は備わりましたが、このままだとログイン状態に合わせて操作を制御するということがまだできません。
サーバー側ではトークンを生成してクライアントに返す機能を、クライアントではトークンを取得し、まずは表示する機能を追加します。
(最終的には取得したトークンをサーバー問い合わせの際に付けて送るようにし、かつサーバー側では認証が必要な処理は適切なトークンが来た場合にのみ許可する仕様にすることで、認証機能の一通りは完成となります。その段階までは、後の記事で紹介します)
トークン機能を追加します。
作りこむと複雑になってしまうので、まずはサーバー側にトークンを生成してログイン成功時には返す機能を、クライアント側ではトークンを取得したら表示する機能を付けていきます。
コード追加
JwtService.cs
Jwtトークンを生成する機能を新たに追加します
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using server_app.Models;
namespace server_app.Services
{
public class TokenService
{
private readonly IConfiguration _config;
public TokenService(IConfiguration config)
{
_config = config;
}
public string CreateToken(ApplicationUser user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Email, user.Email),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["TokenKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
}
AccountController.cs
生成したトークンをログイン成功時に返す機能を追加します
using System.Security.Claims;
using System.Threading.Tasks;
using server_app.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using server_app.Services;
namespace server_app.Controllers
{
[AllowAnonymous]
[ApiController]
[Route("[controller]")]
public class AccountController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signinManager;
+ private readonly TokenService _tokenService;
public AccountController(UserManager<ApplicationUser> userManager
,SignInManager<ApplicationUser> signinManager
+ ,TokenService tokenService
)
{
+ _tokenService = tokenService;
_signinManager = signinManager;
_userManager = userManager;
}
[HttpPost("login")]
public async Task<ActionResult<UserModel>> Login(LoginModel loginModel)
{
var user = await _userManager.FindByEmailAsync(loginModel.Email);
if(user == null) return Unauthorized();
var result = await _signinManager.CheckPasswordSignInAsync(user, loginModel.Password, false);
if(result.Succeeded)
{
return CreateUserObject(user);
}
return Unauthorized();
}
[HttpPost("register")]
public async Task<ActionResult<UserModel>> Register(RegisterModel registerModel)
{
if(await _userManager.Users.AnyAsync(x => x.Email == registerModel.Email))
{
ModelState.AddModelError("email", "Email taken");
return ValidationProblem();
}
if(await _userManager.Users.AnyAsync(x => x.UserName == registerModel.Username))
{
ModelState.AddModelError("username", "Username taken");
return ValidationProblem();
}
var user = new ApplicationUser
{
Email = registerModel.Email,
UserName = registerModel.Username
};
var result = await _userManager.CreateAsync(user, registerModel.Password);
if(result.Succeeded)
{
return CreateUserObject(user);
}
return BadRequest("Problem regist User");
}
private UserModel CreateUserObject(ApplicationUser user)
{
return new UserModel
{
+ Token = _tokenService.CreateToken(user),
Username = user.UserName
};
}
}
}
UserModel.cs
namespace server_app.Models
{
public class UserModel
{
+ public string Token { get; set; }
public string Username {get; set; }
}
}
Startup.cs
トークン生成機能の追加と、Cros関連の変更を行います
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using server_app.Data;
using server_app.Models;
+using server_app.Services;
namespace server_app
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
- readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(opt =>
{
opt.UseSqlite(Configuration.GetConnectionString("DefaultConnection"));
} );
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "server_app", Version = "v1" });
});
services.AddDefaultIdentity<ApplicationUser>(
options => {
options.SignIn.RequireConfirmedAccount = false;
}
)
.AddEntityFrameworkStores<ApplicationDbContext>();
//for jwt token
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddScoped<TokenService>();
//--------------------------------------------------------------------------------------
- services.AddCors(o => o.AddPolicy(MyAllowSpecificOrigins, builder =>
- {
- builder.AllowAnyOrigin() // Allow CORS Recest from all Origin
- .AllowAnyMethod() // Allow All Http method
- .AllowAnyHeader(); // Allow All request header
- }));
+ services.AddCors();
}
// 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", "server_app v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
- app.UseCors(MyAllowSpecificOrigins); // Add For CORS
+ app.UseCors(builder => builder
+ .WithOrigins(new []{"http://localhost:3000","https://localhost:3000"})
+ .AllowAnyHeader()
+ .AllowAnyMethod()
+ .AllowCredentials()
+ );
+
+ app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
appsettings.json
トークン生成用のキーを追加します
{
"ConnectionStrings": {
"DefaultConnection":"Data Source=database.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
+ "TokenKey":"key for jwt token"
}
Login.tsx
ログイン成功時に帰ってくるトークンを表示できるように変更します
import React, { SyntheticEvent, useState } from 'react';
const Login = (
) =>
{
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [resultcode, setResultcode] = useState(0);
const [resultTitle, setResultTitle] = useState('');
+ const [token, setToken] = useState('');
const submit = async (e: SyntheticEvent) => {
e.preventDefault();
const response = await fetch('https://localhost:5001/account/login',
{
method : 'POST',
headers:{'Content-Type' : 'application/json'},
+ credentials: 'include',
body: JSON.stringify({
email,
password
})
});
const content = await response.json();
const status = await response.status
setResultcode(status);
setResultTitle(content.title);
if(status==200){
setName(content.username);
+ setToken(content.token);
}
}
return (
<>
<form onSubmit={submit}>
<h2>Sign in</h2>
<ul>
<li>
<label>email</label>
<input type="email" placeholder="[email protected]" required
onChange = {e => setEmail(e.target.value)}
/>
</li>
<li>
<label>password</label>
<input type="password" placeholder="Password" required
onChange = {e => setPassword(e.target.value)}
/>
</li>
</ul>
<button type="submit">Sign in</button>
</form>
<h2>Response</h2>
<ul>
<li>
{resultcode!=0 && <>{resultcode==200 ? <>Login Success</> : <>Login Fail</>}</>}
</li>
<li>
{resultcode==200 && <>Name:{name}</>}
</li>
<li>
{resultcode!=0 && <>Code:{resultcode}</>}
</li>
<li>
{resultcode!=0 && <>msg:{resultTitle}</>}
</li>
+
+ <li>
+ {resultcode!=0 && <p>token : {token}</p>}
+ </li>
</ul>
</>
);
}
export default Login;
実行結果
実行すると以下の様に取得されたトークンが表示されるようになります
Author And Source
この問題について(ASP.NET Core + React + Typescriptで認証付きWebアプリのひな型を作る⑤jwtトークン機能追加), 我々は、より多くの情報をここで見つけました https://zenn.dev/mosuma/articles/ebfb55b9b7d629著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol