accessToken과 refreshToken의 흐름
[Server_tokenFunctions.js / 흐름0: 토큰 생성과 인증이 어떻게 만들어지는지 확인]
require('dotenv').config();
//환경변수를 가져와서 솔트값으로 사용
const { sign, verify } = require('jsonwebtoken');
//jwt에서 사용할 수 있는 모듈인 sign, verify 함수를 받아옴
//sign은 토큰을 만드는 함수, verify는 토큰을 검증하는 함수
module.exports = {
generateToken: (user, checkedKeepLogin) => {
//user의 정보를 받아와서 payload를 만들어줌
const payload = {
id: user.id,
email: user.email,
};
let result = {
//accessToken이라는 키값안에 sign을 사용해서 토큰을 만들고 그 안에 payload와 환경변수로 관리중인 ACCESS 솔트값을 가져오고 있음
accessToken: sign(payload, process.env.ACCESS_SECRET, {
//옵션으로 유효기간을 설정해줌
expiresIn: '1d', // 1일간 유효한 토큰을 발행합니다.
}),
};
//로그인 유지 여부에 따라 처리해주고 있음
if (checkedKeepLogin) {
//refreshToken이라는 키값안에 sign을 사용해서 토큰을 만들고 그 안에 payload와 환경변수로 관리중인 REFRESH 솔트값을 가져오고 있음
result.refreshToken = sign(payload, process.env.REFRESH_SECRET, {
//옵션으로 유효기간을 설정해줌
expiresIn: '7d', // 일주일간 유효한 토큰을 발행합니다.
});
}
//로그인 유지 여부가 체크되어있으면 accessToken , refreshToken 둘다 들어있는 토큰이 만들어짐
//로그인 유지 여부가 체크되어있지 않으면 accessToken만 들어있는 토큰이 만들어짐
return result;
},
verifyToken: (type, token) => {
//type으로 case의 여부를 확인하는 인자와 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 {
//token 안에는 header와 payload가 있기때문에 솔트값만 알 수 있으면 검증할 수 있음
//그래서 사용된 secretKey를 받아와서 검증하려고 함.
decoded = verify(token, secretKey);
} catch (err) {
console.log(`JWT Error: ${err.message}`);
return null;
}
return decoded;
},
};
[Server_login.js / 흐름1: 들어오는 정보에 맞게 토큰을 생성해주고 쿠키로 전달하기]
const { USER_DATA } = require('../../db/data');
// JWT는 generateToken으로 생성할 수 있습니다. 먼저 tokenFunctions에 작성된 여러 메서드들의 역할을 파악하세요.
const { generateToken } = require('../helper/tokenFunctions');
module.exports = (req, res) => {
//로그인정보가 잘들어오는지 확인해보기
console.log(req.body)
const { userId, password } = req.body.loginInfo;
const { checkedKeepLogin } = req.body;
// checkedKeepLogin이 false라면 Access Token만 보내야합니다.
// checkedKeepLogin이 true라면 Access Token과 Refresh Token을 함께 보내야합니다.
const userInfo = {
...USER_DATA.filter((user) => user.userId === userId && user.password === password)[0],
};
//가지고 있는 정보와 들어온 유저정보가 같은지 확인하고 아니면 권한 없음
if(userInfo.id === undefined) {
res.status(401).send('Not Authorized')
}
//정보가 일치한다면 generateToken에 userInfo, checkedKeepLogin를 넣어주기
else {
const cookieOptions = {
//어떤 도메인에 저장할것인지 정해주기
domain: 'localhost',
path: '/',
//https 프로토콜을 사용할때만 쿠키를 전달해준다는 옵션(localhost는 개발용이기에 안적어주는 경우도 많음, 탈취의 위험이 없기때문, 사용하게된다면 항상 true로 하기)
secure: true,
//쿠키는 consloe창에서 확인을 할 수 있는데 httpOnly 옵션을 true로 주면 console에서 쿠키 확인을 막을 수 있음 보안을 위해 항상 true로 주기
httpOnly: true,
//프로토콜과 포트가 가르고 사이트가 같은곳만 전달하는 옵션
sameSite: 'strict'
//expir, maxAge 설정이 없기때문에 브라우저를 닫으면 쿠키가 없어짐
}
const {accessToken, refreshToken } = generateToken(userInfo, checkedKeepLogin)
//토큰들이 잘 들어오는지 확인하기
console.log(accessToken)
console.log(refreshToken)
//쿠키를 만들어주고 쿠키이름, 저장할 값, 저장할때 옵션
res.cookie('access_jwt', accessToken, cookieOptions)
//로그인 상태 유지가 체크되어있다면
if(checkedKeepLogin) {
//쿠키를 만들어주고 쿠키이름, 저장할 값, 저장할때 옵션
res.cookie('refresh_jwt', refreshToken, cookieOptions)
cookieOptions.maxAge = 1000 * 60 * 60 * 24 * 7
}
res.redirect('/userinfo')
}
};
로그인 정보가 들어오는 부분과 accessToken, refreshToken을 콘솔로 확인
//흐름2: login.js로 부터 토큰을 전달 받았다면 토큰의 유효여부에 따라 유저의 정보를 보내주기
const { USER_DATA } = require('../../db/data');
// JWT는 verifyToken으로 검증할 수 있습니다. 먼저 tokenFunctions에 작성된 여러 메서드들의 역할을 파악하세요.
const { verifyToken, generateToken } = require('../helper/tokenFunctions');
module.exports = (req, res) => {
//cookies를 잘받아오고있는지 확인해보기
console.log(req.cookies)
//구조분해할당으로 받아오고 있는 쿠키의 토큰 가져오기
const {access_jwt, refresh_jwt} = req.cookies
//access_jwt을 받아오고 있다면 검증을 해줄텐데, verifyToken의 인자로 type과 token을 받아오고 있기때문에 그에 맞게 넣어주기
//if(access_jwt) {
//console.log(verifyToken('access', access_jwt))
//console.log로 확인해보면 넣었던 payload를 가져오는것을 알 수 있음
//}
//따라서 access_jwt일때 검증해서 가져오는 payload를 변수로 저장해서 사용
const accessPayload = verifyToken('access', access_jwt)
const refreshPayload = verifyToken('refresh', refresh_jwt)
//값이 맞다면 저장된 유저정보와 들어온 유저정보와 같은값의 정보를 보내줌
if (accessPayload) {
const userInfo = {
...USER_DATA.filter((user) => user.id === accessPayload.id)[0],
};
//유저정보가 제대로 들어오지 않은 경우
if(!userInfo.id) {
return res.status(401).send('Not Authorized')
}
delete userInfo.password
return res.send(userInfo)
}
//access_jwt이 제대로 들어오지 않았다면 refresh_jwt여부를 확인하기
else if(refreshPayload) {
//값이 맞다면 저장된 유저정보와 들어온 유저정보와 같은값의 정보를 보내줌
const userInfo = {
...USER_DATA.filter((user) => user.id === refreshPayload.id)[0],
};
//유저정보가 제대로 들어오지 않은 경우
if(!userInfo.id) {
return res.status(401).send('Not Authorized')
}
//값이 잘 들어왔다면 accessToken을 만들어주기
const {accessToken} = generateToken(userInfo)
const cookieOptions = {
domain: 'localhost',
path: '/',
secure: true,
httpOnly: true,
sameSite: 'strict'
}
res.cookie('access_jwt', accessToken, cookieOptions)
return res.redirect('/userinfo')
}
//refresh_jwt가 없다면
else {
return res.status(401).send('Not Authorized')
}
};
cookies로 어떻게 들어오는지 확인해보면 access_jwt, refresh_jwt이 들어오는것을 알 수 있다.
access_jwt을 받아올때 (verifyToken('access', access_jwt))로 검증을 해본다면 콘솔에 payload를 가져오는것을 알 수 있음
//흐름3: 로그아웃 구현
module.exports = (req, res) => {
//로그아웃할때는 쿠키를 삭제해주면 됨 쿠키를 삭제할때는 만들었던 쿠키의 옵션을 그대로 적어야함
const cookieOptions = {
domain: 'localhost',
path: '/',
secure: true,
httpOnly: true,
sameSite: 'strict'
}
//clearCookie로 삭제할 수 있으며 삭제해줄 토큰 이름과 옵션을 적어주기
res.clearCookie('access_jwt', cookieOptions)
res.clearCookie('refresh_jwt', cookieOptions)
return res.status(205).send('logout')
};