index.js를 풀었으면 사실, 모든 문제를 해결한 것과 다름이 없다. 이후는 앞서 코드를 짰던 것에 연장선이다.
앞서 index.js를 해결하였으니 accessTokenRequest.js파일을 채워넣자.
채워넣기전 여기서는 무슨 역할을 하는지 생각해보자. http-header로 AccessToken이 들어오게 되는데, 이 AccessToken이 진짜 서버에서 발행한 토큰이 맞는지 확인하고, 발행한 토큰이 맞으면, 유저가 요구한 데이터를 넘겨주면 되겠다.
자, 그럼 하는 역할을 알았으니, 데이터가 어떻게 들어오고 있는지를 먼저 파악해보자.
req를 console로 찍어 확인하고 싶지만, 데이터가 너무 많이 들어오므로 하나를 찝어서 데이터를 가지고 와야한다. express 공식문서를 찾아 보았지만, headers라는 것은 찾을 수 없었다. 하여 http-header로 들어오는 것은 알고 있었으니 검색해 보았다.
참조
[node.js] express.js 라우트 요청 객체(req), 응답 객체(res) 정리
commend
console.log(req.headers)
Result
{
host: '127.0.0.1:4000',
'accept-encoding': 'gzip, deflate',
authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcklkIjoia2ltY29kaW5nIiwiZW1haWwiOiJraW1jb2RpbmdAY29kZXN0YXRlcy5jb20iLCJjcmVhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJ1cGRhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJpYXQiOjE2NDc1MjI0Mjl9.mb9oFx8atHIauTJRkjDjEry1C6JxEAINwJVnMfjm0wU',
connection: 'close'
}
위와 같은 결과가 나오게 된다. 그렇다면 여기서 우리가 보낸 AccessToken은 무엇일까?
그렇다 authorization에 들어있는 값이다. 이 값을 다시 console로 찍어보자.
commend
console.log(req.headers.authorization)
Result
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcklkIjoia2ltY29kaW5nIiwiZW1haWwiOiJraW1jb2RpbmdAY29kZXN0YXRlcy5jb20iLCJjcmVhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJ1cGRhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJpYXQiOjE2NDc1MjI1NzJ9.sgUeOz-rF8kOt4ZDN1w05gUfKQ0EMPwEh-pFe2Wsg6Q
뭔가 이상하다. 분명 이전에 console을 찍어보았을 때에는 Bearer라는 값이 없었다. AccessToken의 값을 이 뒤에 있는 값일 텐데 저건 뭘까?
참조
JWT 혹은 OAuth에 대한 토큰을 사용한다는 표시이다.
그렇다면 여기서 문제가 생긴다. 저 쓸데 없는 값을 없애고 어떻게 순수한 AccessToken에 접근할 수 있을까? 저 Token의 값을 복호화 하여야 클라이언트가 맞는지 확인할텐데 말이다.
하여 생각한것이 split이다. “ ”공백문자열로 띄우고 1번째 인덱스에 있는 값이 Token이 될테니 말이다.
const realtoken = token.split(" ")[1]
그렇다면 realtoken이 잘 가져오고 있는지 console을 찍어보자.
commend
console.log(realtoken)
Result
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcklkIjoia2ltY29kaW5nIiwiZW1haWwiOiJraW1jb2RpbmdAY29kZXN0YXRlcy5jb20iLCJjcmVhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJ1cGRhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJpYXQiOjE2NDc1MjMwNjd9.KfABGhHplrnL02QZEV3nn_92SF9xMhOaH9F8uK-Eqao
위와 같은 결과로 Token의 값만 잘 가져오는 것을 볼 수 있겠다.
위에서 많은 것을 했다. 코드로 정리해보자.
먼저, req.headers.authorization에 값이 들어있지 않을 경우를 제외해야한다.
const token = req.headers.authorization
if(!token){
res.status(400).json({data : null, message :'invalid access token'})
}
다음으로 위에서 만든 realtoken을 복호화 해야한다. 그렇다면 방법을 알아야 겠으니, 공식문서를 찾아보자.
참조
jwt.verify(token, secretOrPublicKey, [options, callback])
복호화 하는 방법은 위와 같다. 앞서 sign으로 Token을 만들었던 것과 같이 첫 번째 인자로 해싱된 Token을 넣고 두 번째 인자로 salt를 넣고 세 번째 인자로 options을 설정한다. options은 어떠한 알고리즘으로 복호화 할 것인지 등을 설정해 줄 수 있다.
Example
// 예제1
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
// 예제2
try {
var decoded = jwt.verify(token, 'wrong-secret');
} catch(err) {
// err
}
위와 같은 방식으로 Token을 복호화 해보자.
const realtoken = token.split(" ")[1]
const vat = jwt.verify(realtoken, process.env.ACCESS_SECRET)
vat가 잘 나오는지 console을 찍어보자.
commend
console.log(vat)
Result
{
id: 1,
userId: 'kimcoding',
email: 'kimcoding@codestates.com',
createdAt: '2020-11-18T10:00:00.000Z',
updatedAt: '2020-11-18T10:00:00.000Z',
iat: 1647523666
}
위와 같이 잘 복호화 되는 모습을 볼 수 있다. 헌데 내가 넣어주지 않은 값이 있다. iat는 무엇일까?
⇒ 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age가 얼마나 되었는지 판단 할 수 있다.
참조
vat의 복호화가 성공하였으니 조건을 또 만들 수 있겠다.
위의 3가지를 코드로써 구현해보자. 사실 이부분은 앞서 코드를 짠 부분의 반복인걸 볼 수 있다.
첫 번째 경우
const realtoken = token.split(" ")[1]
const vat = jwt.verify(realtoken, process.env.ACCESS_SECRET)
if(!vat) {
res.status(400).json({data : null, message :'invalid access token'})
}
두 번째 경우에서 부터는 데이터베이스에서 값을 비교해와야 한다. 하여 index.js에서 사용했던 findOne을 사용하여 데이터베이스에 접근해야 한다.
const userInfo = await Users.findOne({
where : {id : vat.id, userId : vat.userId, email : vat.email, createdAt : vat.createdAt, updatedAt : vat.updatedAt}
})
if(userInfo){
res.status(200).json({data : { userInfo : {id : userInfo.id, userId : userInfo.userId, email : userInfo.email, createdAt : userInfo.createdAt, updatedAt : userInfo.updatedAt}}, message : 'ok'})
}
else{
res.status(400).json({data : null, message :"access token has been tempered"})
}
인증이 된 클라이언트에게 데이터를 전달해 줄때 data객체에 담아준다. 그 값을 또 userInfo로 담아 보내주면 되겠다.
const { Users } = require('../../models');
const jwt = require('jsonwebtoken');
module.exports = async (req, res) => {
// TODO: urclass의 가이드를 참고하여 GET /accesstokenrequest 구현에 필요한 로직을 작성하세요.
// console.log(req.headers)
// console.log(req.headers.authorization)
const token = req.headers.authorization
if(!token){
res.status(400).json({data : null, message :'invalid access token'})
}
const realtoken = token.split(" ")[1]
// console.log(realtoken)
const vat = jwt.verify(realtoken, process.env.ACCESS_SECRET)
// console.log(vat)
if(!vat) {
res.status(400).json({data : null, message :'invalid access token'})
}
else{
const userInfo = await Users.findOne({
where : {id : vat.id, userId : vat.userId, email : vat.email, createdAt : vat.createdAt, updatedAt : vat.updatedAt}
})
// console.log(result)
if(userInfo){
res.status(200).json({data : { userInfo : {id : userInfo.id, userId : userInfo.userId, email : userInfo.email, createdAt : userInfo.createdAt, updatedAt : userInfo.updatedAt}}, message : 'ok'})
}
else{
res.status(400).json({data : null, message :"access token has been tempered"})
}
}
};
이제 accessTokenRequest.js도 끝이 났다. 다음으로 넘어가보자.
refreshTokenRequest.js 파일도 앞서 만들었던 accessTokenRequest.js와 개념이 많이 비슷하다.
코드를 만들기전에 무엇을 하는 파일인지 생각해보자.
refreshToken이 시간 만료로 사라졌는데 데이터 요청이 들어오면 서버는 클라이언트에게 로그인을 요청을 해야한다. 하지만 refreshToken이 시간이 남아있다면 accessToken을 만들어 클라이언트로 보내야 할 것이다.
코드로 구현해 보자.
먼저 refreshToken이 req로 들어와서 잘 있는지 확인해보자. 헌데 우리가 cookie에 refreshToken을 넣어주었으니 req.cookie로 살펴보자.
commend
console.log(req.cookie)
Result
undefined
이상하다 분명 cookie로 잘 보냈는데 말이다. 공식문서를 찾아보자.
참조
내가 틀린것이다. cookie가 아니라 cookies이다. 다시 찍어보자
commend
console.log(req.cookies)
Result
{ refreshToken: 'invalidtoken' }
{
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcklkIjoia2ltY29kaW5nIiwiZW1haWwiOiJraW1jb2RpbmdAY29kZXN0YXRlcy5jb20iLCJjcmVhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJ1cGRhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJpYXQiOjE2NDc1MjUyMDd9.J7r85tOP71eJuBbgZORyW_wZ_T5Bu4mD02MxSRdW_rc'
}
위와 같은 값이 나오게 된다. 밑에 있는 값은 refreshToken이 확실한데 난 위의 값을 넣어준적이 없다. 뜻을 해석해보면 무효한 토큰이라는 말이다.
하면 위의 값을 가지고 있는 토큰이라면 기간이 만료되었다는 뜻인가 보다. 위의 값으로 refreshToken이 유효한지 아닌지 판별할 수 있겠다.
그럼 여기서 2가지의 경우가 생긴다.
위의 두 가지 경우로 코드를 짜보자.
if(!token){
res.status(400).json({data:null, message:'refresh token not provided'});
}
if(token === 'invalidtoken'){
res.clearCookie("refreshToken",{path: '/users/refreshTokenRequest'})
res.status(400).json({data:null, message:'invalid refresh token, please log in again'})
res.redirect('https://localhost:3000')
}
위와 같은 조건을 둘 수 있겠다. 사실 위의 clearCookie와 redirect는 내가 마음대로 넣어본 값이다. 될지 안될지는 모르겠다. 클라이언트가 완성이 되면 확인해야하는 부분이다. clearCookie도 path를 확인해봐야한다.
위의 두 경우를 찾았으니 이제 데이터베이스에서 refreshToken을 복호화 하고, 복호화 한 값이 데이터베이스에 있는지를 찾아봐야 한다. 복호화 한 값이 데이터베이스에 없으면 이부분에서도 클라이언트에 처리를 해주어야겠다.
const vrt = jwt.verify(token, process.env.REFRESH_SECRET)
// console.log(vrt)
const userInfo = await Users.findOne({
where : { id : vrt.id, userId : vrt.userId }
})
if(!userInfo){
res.status(400).json({ "data": null, "message": "refresh token has been tempered" })
}
이제 이후, 데이터베이스에도 값이 있다면 AccessToken을 생성해주고 다시 클라이언트로 Token을 넘겨주면 되겠다.
else{
const payload = {id : userInfo.id, userId : userInfo.userId, email : userInfo.email, createdAt : userInfo.createdAt, updatedAt : userInfo.updatedAt}
const AccessToken = jwt.sign(payload, process.env.ACCESS_SECRET,{expiresIn:"30s"})
res.status(200).json({data:{"accessToken" : AccessToken, "userInfo" : {id : userInfo.id, userId : userInfo.userId, email : userInfo.email, createdAt : userInfo.createdAt, updatedAt : userInfo.updatedAt}}, message: 'ok'})
}
const { Users } = require('../../models');
const jwt = require('jsonwebtoken');
module.exports = async (req, res) => {
// TODO: urclass의 가이드를 참고하여 GET /refreshtokenrequest 구현에 필요한 로직을 작성하세요.
console.log(req.cookies)
const token = req.cookies.refreshToken
// console.log(req.cookies.refreshToken)
if(!token){
res.status(400).json({data:null, message:'refresh token not provided'});
}
if(token === 'invalidtoken'){
res.clearCookie("refreshToken",{path: '/users/refreshTokenRequest'})
res.status(400).json({data:null, message:'invalid refresh token, please log in again'})
res.redirect('https://localhost:3000')
}
const vrt = jwt.verify(token, process.env.REFRESH_SECRET)
// console.log(vrt)
const userInfo = await Users.findOne({
where : { id : vrt.id, userId : vrt.userId }
})
if(!userInfo){
res.status(400).json({ "data": null, "message": "refresh token has been tempered" })
}else{
const payload = {id : userInfo.id, userId : userInfo.userId, email : userInfo.email, createdAt : userInfo.createdAt, updatedAt : userInfo.updatedAt}
const AccessToken = jwt.sign(payload, process.env.ACCESS_SECRET,{expiresIn:"30s"})
res.status(200).json({data:{"accessToken" : AccessToken, "userInfo" : {id : userInfo.id, userId : userInfo.userId, email : userInfo.email, createdAt : userInfo.createdAt, updatedAt : userInfo.updatedAt}}, message: 'ok'})
}
};