일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 로그인 로직
- react
- 인증처리
- register
- 로그인
- 파생상품평가
- stock option
- Typesciprt
- ui탬플릿
- Token
- Styled Components
- MRC
- 밸류즈 홈페이지
- Ajax
- RCPS
- 관리자페이지
- 달력 라이브러리
- 밸류즈
- mypage
- Update
- 캘린더 라이브러리
- userManagement
- 회원가입로직
- 공통메서드
- 마이페이지
- 배포
- 스프링시큐리티
- jsonwebtoken
- 빌드 및 배포
- 이미지 업로드
- Today
- Total
I T H
[MySchedule project] 6. JWT(json web token) / 로그인 로직(1) 본문
회원가입을 마치고, 이제 react with typescript + node.js(mongodb)환경에서의 로그인을 구현해보자!
로그인은 사용자로부터 id와 패스워드를 받아서 백앤드로 전달하고 백앤드에서 토큰을 만들어서 다시 프론트앤드로 보내주게 된다. 그리고 받은 토큰을 가지고 headers의 authorization에 토큰정보를 넣어서 백앤드로 보낸다.
백앤드에서 secret key를 이용해서 프론트에서 받은 토큰이 유효한 토큰인지 확인후 유효하다면 사용자 데이터를 보내게된다. 이번 챕터는 백앤드에서 토큰을 받아오는 부분까지, 다음챕터부터는 나머지부분을 다뤄보도록 하겠다.
- Frontend
[ pages / types / login.ts ]
- 로그인페이지의 타입들을 모아놓은 파일
export interface loginParams {
userId: string;
userPassword: string;
}
[ pages / Login / index.tsx ]
1. id 저장 체크시 id 값을 쿠키에 저장
- useEffect hook을 통해 페이지 랜더링시 쿠키에 아이디값이 있으면 값을 가져와 화면에 출력하게 하였다.
2. onsubmit 이벤트 발생시 id, password 유효성 체크가 완료되면 dispatch를 통해 값을 전달했다.
3. react-icons 라이브러리를 이용해 아이콘을 가져와 사용함.
import React, { ChangeEvent, useEffect, useState } from "react";
import { FaUnlockAlt } from "react-icons/fa";
import { LiaUserCheckSolid } from "react-icons/lia";
import { useCookies } from "react-cookie";
import { useForm } from "react-hook-form";
import { toast } from "react-toastify";
import { useAddDispatch } from "../../store/redux";
import { useNavigate } from "react-router-dom";
import { userLogin } from "../../store/thunkFunction";
import { loginParams } from "../../types/login";
const Login = () => {
const [userId, setUserId] = useState("");
const [userPassword, setUserPassword] = useState("");
const [idSaveCheckbox, setIdSaveCheckbox] = useState(false); // id저장 체크박스 체크유무
const [cookies, setCookie, removeCookie] = useCookies(["CookieUserId"]); // cookie이름설정
const { handleSubmit } = useForm();
const dispatch = useAddDispatch();
const navigate = useNavigate();
//페이지 랜더링시 쿠키에 아이디값이 있으면 가져옴
useEffect(() => {
if (cookies.CookieUserId !== undefined) {
//값이 있다면 id값을 가져오고, 체크박스유무를 true로 넣어줌.
setUserId(cookies.CookieUserId);
setIdSaveCheckbox(true);
}
}, []);
//ID를 쿠키에 저장 OR 삭제하는 함수
const handleIdCheck = (e: ChangeEvent<HTMLInputElement>) => {
setIdSaveCheckbox(e.target.checked);
if (e.target.checked) {
//체크 O -> 쿠키에 id값 저장, 시간은 10만 초 -> 27시간정도
setCookie("CookieUserId", userId, { maxAge: 100000 });
} else {
//체크 X -> 쿠키 삭제
removeCookie("CookieUserId");
}
};
//submit
const onSubmit = () => {
if (userId === null || userId === "") {
toast.error("아이디를 입력해주세요.");
return;
}
if (userPassword === null || userPassword === "") {
toast.error("비밀번호를 입력해주세요.");
return;
}
const body: loginParams = {
userId,
userPassword,
};
dispatch(userLogin(body)).then((response) => {
console.log(response);
if (response.payload.user) {
reset();
navigate("/");
} else {
reset();
}
});
};
const reset = () => {
setUserId("");
setUserPassword("");
};
return (
<section>
{/* <div className="m-auto max-w-[1100px]">
<img src="" alt="" />
</div> */}
<div className="max-w-[450px] m-auto p-4 my-6 mt-16">
<div className="rounded-t-md bg-gray-100 p-3">
<h2 className="text-2xl font-bold text-center mt-4">LOGIN</h2>
</div>
<div className="rounded-b-md bg-gray-100 p-3">
<form className="w-full py-2" onSubmit={handleSubmit(onSubmit)}>
<div className="text-center m-auto border-gray-300 border-b-[1px] w-10/12 mb-5">
<LiaUserCheckSolid className="w-2/12 float-left h-6 mt-4" />
<input
className="w-10/12 placeholder-gray-400 text-gray-700 text-md font-medium border-none bg-transparent mt-2 h-[35px] p-2 focus:focus:outline-none"
autoComplete="off"
type="text"
placeholder="ID"
id="userId"
value={userId}
onChange={(e) => {
setUserId(e.target.value);
}}
/>
</div>
<div className="text-center m-auto border-gray-300 border-b-[1px] w-10/12 mb-5">
<FaUnlockAlt className="w-2/12 float-left h-5 mt-4" />
<input
className="w-10/12 placeholder-gray-400 text-gray-700 text-md font-medium border-none bg-transparent mt-2 h-[35px] p-2 focus:focus:outline-none"
autoComplete="off"
type="password"
placeholder="PASSWORD"
id="password"
value={userPassword}
onChange={(e) => {
setUserPassword(e.target.value);
}}
/>
</div>
<div className="text-left m-auto w-10/12 mb-5">
<input
className="rounded-md"
type="checkbox"
id="idSave"
onChange={handleIdCheck}
checked={idSaveCheckbox}
/>{" "}
<label
htmlFor="idSave"
className="text-gray-500 font-semibold text-sm hover:cursor-pointer"
>
아이디 저장
</label>
</div>
<div className="text-center m-auto w-10/12 my-3">
<button
type="submit"
className="w-full bg-blue-700 text-gray-100 p-2 h-[50px] font-bold text-md py-2 px-2 rounded-md hover:ring hover:ring-blue-700"
>
로그인
</button>
</div>
</form>
</div>
</div>
</section>
);
};
export default Login;
[ src / store / thunkFunction.tsx ]
- axios를 통해 id, password 값을 백엔드로 전달한다.
- 파라미터 타입은 위에서 미리 정해논 타입 loginParams를 사용하였음.
//로그인
export const userLogin = createAsyncThunk(
"userLogin",
async (body: loginParams, thunkAPI) => {
try {
const response = await axiosInstance.post(`/login`, body);
return response.data; //payload
} catch (error: any) {
console.log(error);
return thunkAPI.rejectWithValue(error.response.data || error.message);
}
}
);
- Backend
[ .env ]
- ".env"파일은 최상위 루트에 위치하여야함.
- jwt를 만들기 위해선 백앤드에서 지정해놓을 secret key가 필요하다. ".env"에 secret key변수를 만들어 놓는다.
- 보안상 중요한 코드는 .env에 저장해놓고 가져와 쓰도록 하자!
[ src / index.js ]
- route 경로 추가
//route start
...생략
app.use("/login", require("./routes/login"));
//route end
[ src / routes / login.js ]
1. front에서 보내준 id를 통해 회원정보가 있는지 확인
2. 해당하는 회원정보가 있다면 해당 회원에 대한 db의 password와 사용자가 입력한 password를 비교함.
3. password까지 일치하면 토큰(jwt)을 생성한다.
- jwt의 구조: header + payload + signature
header: 사용알고리즘, 타입
payload: 중복안되는요소를 넣어야됨
signature: header와 payload의 암호화된 값과 백앤드에서 지정한 비밀키(secret key)를 합침.
4. frontend쪽으로 token을 보내준다.
const express = require("express");
const User = require("../models/User");
const router = express.Router();
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
router.post("/", async (req, res, next) => {
try {
// 사용자아이디가 db에 있는지 확인
// 있으면 찾아서 해당아이디의 데이터를 user에 넣음
const user = await User.findOne({ userId: req.body.userId });
if (!user) {
return res.status(400).send("userId is not found");
}
//사용자로부터 입력받은 password와 db에 있는 user컬렉션의 password를 비교함.
const Match = await bcrypt.compare(
req.body.userPassword,
user.userPassword
);
if (!Match) {
return res.status(400).send("userPassword is not found");
}
// token 생성 start
// jwt의 구조: header + payload + signature
const payload = {
userId: user.userId,
};
const accessToken = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "1h",
});
//token 생성 end
return res.json({ user, accessToken });
} catch (error) {
next(error);
}
});
module.exports = router;
- Frontend
[ src / store / userSlice.tsx ]
- 백앤드에서 받아온 response 데이터를 extraReducers에 추가해준다.
- 토큰을 localStorage에 저장한다.
.addCase(userLogin.pending, (state) => {
state.isLoading = true;
})
.addCase(userLogin.fulfilled, (state, action) => {
state.isLoading = false;
state.userData = action.payload;
state.isAuth = true;
localStorage.setItem("accessToken", action.payload.accessToken);
toast.info("메인페이지로 이동합니다.");
})
.addCase(userLogin.rejected, (state, action: any) => {
state.isLoading = false;
state.error = action.payload;
toast.error("아이디 또는 비밀번호를 한번더 확인해주세요.");
})
[결과화면]
- 리덕스와 localStorage에 토큰이 잘 들어왔으면 정상작동!
'React + Node.js' 카테고리의 다른 글
[MySchedule project] 8. 인증에 의한 header, Route 로직 / 로그아웃 (2) | 2024.01.31 |
---|---|
[MySchedule project] 7. JWT(json web token) / 로그인 로직(2) (2) | 2024.01.30 |
[MySchedule project] 5. backend - 회원가입 로직 (0) | 2024.01.25 |
[MySchedule project] 4. frontend - 회원가입 로직 (1) | 2024.01.25 |
[MySchedule project] 3. frontend - redux 세팅(redux-toolkit사용) + typescript (0) | 2024.01.25 |