LoginSignup
0
0

More than 1 year has passed since last update.

タスク管理ツールをReact + ASP.Coreで作る018トークン機能追加

Last updated at Posted at 2022-11-07

前回の続きです。
#016より数回に分けて認証機能を導入しています

ガイド

全体Index:タスク管理ツールをReact + ASP.Coreで作る - Index

認証機能追加の一連の流れ

概要

前回までで、ログイン機能は備わりましたが、このままだとログイン状態に合わせて操作を制御するということがまだできません。

サーバー側ではトークンを生成してクライアントに返す機能を、クライアントではトークンを取得し、まずは表示する機能を追加します。

(最終的には取得したトークンをサーバー問い合わせの際に付けて送るようにし、かつサーバー側では認証が必要な処理は適切なトークンが来た場合にのみ許可する仕様にすることで、認証機能の一通りは完成となります。その段階までは、後の記事で紹介します)

トークン機能を追加します。

作りこむと複雑になってしまうので、まずはサーバー側にトークンを生成してログイン成功時には返す機能を、クライアント側ではトークンを取得したら表示する機能を付けていきます。

構築

本記事で実施する内容のサマリー

  • サーバーサイド
    • jwtトークン生成用の機能を追加
    • ログイン時にjwtトークンを返す処理を追加
  • クライアントサイド
    • ログイン時にjwtトークンを受け取るようにコードを変更

サーバーサイドのコード追加

JwtService.cs

Jwtトークンを生成する機能を新たに追加します

JwtService.cs
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

生成したトークンをログイン成功時に返す機能を追加します

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

UserModel.cs

namespace server_app.Models
{
    public class UserModel
    {
+        public string Token { get; set; }
        public string Username {get; set; }
        
    }
}

Startup.cs

トークン生成機能の追加と、Cros関連の変更を行います

Startup.cs
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.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.Models.EDM;
+ using server_app.Services;

namespace server_app
{
    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.AddDbContext<DataContext>(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<DataContext>();
            
+           //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
           }));
        }

        readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

        // 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.UseAuthentication();
            
            app.UseAuthorization();

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

appsettings.json

トークン生成用のキーを追加します

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

ログイン成功時に帰ってくるトークンを表示できるように変更します

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'},
            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="name@example.com" 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;

実行結果

実行すると以下の様に取得されたトークンが表示されるようになります

image.png

今回は以上です。
続きは次回です

0
0
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
0
0