React
자료구조
- client
- …
- pages
- Login.js
- Mypage.js
- src
- App.js
- index.js
- server
- controllers
- helper
- tokenFunctions.js
- users
- login.js
- logout.js
- userInfo.js
- index.js
- helper
- db
- data.js
- index.js
- .env
- .gitignore
- controllers
서버 만들기
server/.env
1
2
ACCESS_SECRET=(시크릿 값)
REFRESH_SECRET=(시크릿 값)
- 서버에서 추가할 SALT 값이다.
- 환경 변수이며, 사용자가 입력한 password에 salt값을 더한다.
server/.gitgnore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/node_modules
/.pnp
.pnp.js
/coverage
/build
key.pem
cert.pem
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
- .gitgnore 파일에 key.pem과 cert.pem 파일을 담는다.
server/controllers/index.js
1
2
3
4
5
module.exports = {
login: require("./users/login"),
logout: require("./users/logout"),
userInfo: require("./users/userInfo"),
};
- 작성한 users 폴덩 있는 js 파일을 모듈의 이름으로 가져온다.
- Route
server/controllers/helper/tokenFunction.js
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
require("dotenv").config();
const { sign, verify } = require("jsonwebtoken");
module.exports = {
generateToken: (user, checkedKeepLogin) => {
const payload = {
id: user.id,
email: user.email,
};
let result = {
accessToken: sign(payload, process.env.ACCESS_SECRET, {
expiresIn: "1d",
}),
};
if (checkedKeepLogin) {
result.refreshToken = sign(payload, process.env.REFRESH_SECRET, {
expiresIn: "7d",
});
}
return result;
},
verifyToken: (type, token) => {
let secretKey, decoded;
switch (type) {
case "access":
secretKey = process.env.ACCESS_SECRET;
break;
case "refresh":
secretKey = process.env.REFRESH_SECRET;
break;
default:
return null;
}
try {
decoded = verify(token, secretKey);
} catch (err) {
return null;
}
return decoded;
},
};
require("dotenv").config()
- Node.js 애플리케이션에서 환경 변수를 로드하기 위한 코드이다.
- dotenv는 Node.js에서 환경 변수를 설정하는 모듈이며, 이 모듈을 사용하면 .env파일을 만들어서 환경 변수를 설정하고, process.env객체를 통해 사용할 수 있다.
- process.ev를 사용하여 .env파일에서 작성했던 salt 값을 숨겨서 사용할 수 있다.
const {sign, verify} = require("jsonwebtoken")
- Node.js에서 JWT를 생성하고(sign) 검증(verify)하기 위한 모듈을 불러온다.
- sign 함수는 JWT생성하며, 인자로 전달된 payload와 secret key를 사용하여 JWT를 생성한다.
- verify 함수는 JWT가 유효한지 검증하며, 인자로 전달된 JWT와 secret key를 사용하여 검증을 수행한다.
generateToken: (user, checkedKeepLogin) => {}
- 토큰을 생성하는 함수이며 유저에 대한 정보와 로그인에 대한 정보를 인자로 받는다.
const payload = {...}
- 우리가 인자로 넘긴 user의 id와 email을 객체로 사용한다.
result = {...}
- accessToken을 만드는데, 우리가 입력한 payload와 salt키를 합쳐 만든다.
- expiresIn은 토큰의 유효 기간을 정한다.
if(checkedKeepLogin){...}
- 만약 자동로그인을 선택했을 경우 refreshToken을 새롭게 만들어, 기존에 만들었던 accessToken에다가 넣어준다.
- 마찬가지로 유효기간을 설정한다.
verifyToken: (type, token) => {...}
- 만약 전달받은 type이 access일 경우, secretKey는 access를 만들 때 썼던 salt를 secretKey에 할당한다.
- 만약 전달받은 type이 refresh일 경우, secretKey는 refresh를 만들 때 썼던 salt를 secretKey에 할당한다.
try{...}
- try는 에러가 발생할 가능성이 있는 코드 블록을 정의할 때 사용한다.
- decoded에 secretKey와 token이 일치하는지 확인한 값을 할당한다.
- 만약 에러가 발생하면, error메세지를 출력한다.
server/controllers/users/login.js
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
31
32
const { USER_DATA } = require("../../db/data");
const { generateToken } = require("../helpers/tokenFunctions");
module.exports = async (req, res) => {
const { userId, password } = req.body.loginInfo;
const { checkedKeepLogin } = req.body;
const exUser = {
...USER_DATA.find(
(user) => user.userId === userId && user.password === password
),
};
if (!exUser.id) return res.status(401).send("Not Authorized");
const { accessToken, refreshToken } = generateToken(exUser, checkedKeepLogin);
const cookieOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
res.cookie("access_jwt", accessToken, cookieOption);
if (checkedKeepLogin) {
cookieOption.maxAge = 1000 * 60 * 24;
res.cookie("refresh_jwt", refreshToken, cookieOption);
}
res.redirect("/userinfo");
};
const {USER_DATA} = require("../../db/data")
- 기존에 작성했던 데이터를 불러온다.
const {generateToken} = require("../helper/tokenFunctions")
- tokenFunctions에서 정의한 generateToken 함수를 불러온다.
const {userId, password} = req.body.loginInfo
- 우리가 입력한 loginInfo의 userId와 password를 구조분해할당한다.
const {checkedKeepLogin} = req.body
- 우리가 설정한 자동 로그인을 구조분해할당한다.
const exUser = {...}
- 기존의 데이터인 USER_DATA에서 userId가 우리가 입력한 userId와 맞는지, password가 맞는지 확인하여, 일치하는 데이터를 exUser에 담는다.
if(!exUser.id) return res.status(401).send("Not Authorized")
- 만약 입력한 데이터와 기존의 데이터가 일치하지 않아 exUser에 아무런 값도 없다면 에러를 내보낸다.
const {accessToken, refreshToken} = generateToken(exUser, checkedKeepLogin)
- 만약 데이터가 일치하여 if문에 걸리지 않았을 시, 새로운 토큰을 만드는데 exUser와 checkedKeepLogin을 갖고 있는 Token의 accessToken과 refreshToken을 구조분해할당한다.
const cookieOption = {...}
- 만들어 줄 쿠키를 정의하고, domain과 path를 설정한다.
res.cookie("access_jwt",accessToken,cookieOption)
- 응답에 대한 쿠키를 만들어주는데 cookie는 메서드로, access_jwt라는 이름으로 accessToken과 cookieOption이 담긴 쿠키를 만든다.
if(checkedKeepLogin){...}
- 만약 자동로그인을 선택했을 경우, refreshToken의 유효기간을 설정하고, refresh_jwt라는 이름으로 토큰을 만들어준다.
res.redirect("/userinfo")
- /userinfo 경로로 리다이렉트 해준다.
- 로그인한 유저에 응답하기 위한 endpoint로 각자의 목적에 맞는 처리를 하기 위해 사용한다.
server/controllers/users/logout.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = (req, res) => {
const cookieOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
res
.clearCookie("access_jwt", cookieOptions)
.clearCookie("refresh_jwt", cookieOptions)
.status(205)
.send("logout");
};
res.clearCookie("access_jwt", cookieOptions). ...
- 로그아웃 시, 부여됐던 쿠키를 모두 삭제한다.
server/controllers/users/userInfo.js
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const { USER_DATA } = require("../../db/data");
const tokenFunctions = require("../helper/tokenFunctions");
const { verifyToken, generateToken } = require("../helper/tokenFunctions");
module.exports = async (req, res) => {
const { access_jwt, refresh_jwt } = req.cookies;
if (access_jwt) {
const accessTokenPayload = verifyToken("access", access_jwt);
if (accessTokenPayload) {
const exUser = {
...USER_DATA.find((user) => user.id === accessTokenPayload.id),
};
delete exUser.password;
return res.send(exUser);
}
if (refresh_jwt) {
const refreshTokenPayload = verifyToken("refresh", refresh_jwt);
if (!refreshTokenPayload) return res.status(401).send("Not Authorized");
const exUser = {
...USER_DATA.find((user) => user.id === refreshTokenPayload.id),
};
if (!refreshTokenPayload) return res.status(404).send("Not Authorized");
const cookieOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
const { accessToken } = generateToken(exUser, false);
res.cookie("access_jwt", accessToken, cookieOption);
}
return res.status(401).send("Not Authorized");
}
};
const {USER_DATA} =require("../../db/data")
- 기존에 작성했던 USER_DATA를 받아온다.
const tokenFunctions = require("../helper/tokenFunctions")
const {verifyToken, generateToken} = require("../helper/tokenFunctions")
- tokenFunctions에서 작성했던 verifyToken과 generateToken을 불러온다.
const {access_jwt, refresh_jwt} = req.cookies
- 브라우저의 요청(로그인)에서 쿠키의 access_jwt와 refresh_jwt를 구조분해할당한다.
if(access_jwt){const accessTokenPayload = verifyToken("access",access_jwt)}
- accessToken이 있다면 access가 유효한지 확인한다.
if(accessTokenPayload){const exUser = {...USER_DATA.find((user)=> user.id === accessTokenPayload.id)}}
- 기존의 데이터에서 일치하는 유저정보를 exUser에 담는다.
- accessTokenPayload는 accessToken이 유효하다면 실행된다.
- delete exUser.password를 통해 예민한 정보인 비밀번호를 제외하고 일치하는 유저 정보를 return한다.
if(refresh_jwt){const refreshtokenPayload = verifyToken("refresh",refresh_jwt)}
- 만약 accesToken이 없다면 refreshToken을 확인한다.
- 들어있는 refreshToken과 우리가 부여한 refreshToken이 맞는지 확인한다.
if(!refreshToken) return res.status(401).send("Not Authorized.")
- 만약 refreshToken이 일치하지 않는다면 에러를 내보낸다.
const exUser = {...}
,if(!refreshToken) return res.status(401).send("Not Authorized.")
- 일치하는 유저가 있는지 다시 확인하여, 없다면 에러를 내보낸다.
const cookieOption = {...}
- 만약 위 if문을 모두 지나쳐, refreshToken이 유효하다는 것이 확인되면 쿠키에 새로운 accessToken을 담아낸다.