목표
- 로그인 시 Access Token과 Refresh Token을 모두 발급한다
- 사용자가 들어오면 토큰을 검사함과 동시에 각 경우에 대하여 토큰의 유효기간을 확인하여 재발급 여부를 검사한다.
- access token만 만료됐을 때 → refresh token 확인 후 access token 재발급 + 로그아웃 페이지
- refresh token만 만료됐을 때 → access token 확인 후 refresh token 재발급 + 로그아웃 페이지둘다 유효할 때
- 모든 토큰이 만료됐을 때 → 로그인 페이지로 redirect
- 로그아웃시 토큰을 모두 만료시킨다.
Dependencies
"dependencies": {
"body-parser": "^1.19.2",
"cookie-parser": "^1.4.6",
"dotenv": "^16.0.0",
"express": "^4.17.3",
"jsonwebtoken": "^8.5.1"
}
Node.js package 종속성이다. 본인 프로젝트의 package.json의 "dependencies" 부분에 넣고 npm install로 설치해주면 된다.
View
브라우저단에서 인증을 보기 위한 HTML 파일입니다.
/web/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<input type="text" name="id"> <br>
<input type="text" name="name"> <br>
<button>로그인</button>
</form>
</body>
</html>
/web/logout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Hello! Logined User
<form action="/logout" method="post">
<button>Logout</button>
</form>
</body>
</html>
설정파일
.env에 SECRET_KEY를 설정해 줄 것이다. 여기서 secretkey는 token의 구성 요소중 하나인데. secretkey로 토큰을 인코딩하면, 토큰을 디코딩하는 과정에서 key가 필요하게 된다. ( 물론 상황에 따라 다르다. )
.env
SECRET_KEY=d7dc0a02ab84686fa1da76896332901382c50372692bf317c80bcf5858ebae50b2934c5d30b00b308d0966385d25e6bc
필자는 적당히 해시값을 생성했다.
modules
CreateToken.js 토큰을 생성하는 미들웨어.
/modules/CreateToken.js
const jwt = require('jsonwebtoken')
module.exports = (type, bodyid=' ', bodypwd = ' ') => {
if(type == 'AccessKey'){
var AccessToken = jwt.sign({ // Create Access Token
id : bodyid,
name : bodypwd
},process.env.SECRET_KEY,{
expiresIn : '1h',
issuer : "YoonHyunWoo"
})
return AccessToken
}else if(type=='RefreshKey'){
var RefreshToken = jwt.sign({ // Create Refresh Token
},process.env.SECRET_KEY,{
expiresIn : '14d',
issuer : "YoonHyunWoo"
})
return RefreshToken
}
}
headCookieParser.js 는 헤더로 들어오는 쿠키를 파싱하지 못하길레 만들어준 모듈이다.
modules/headCookieParser.js
module.exports = (cookieInput) => {
var cookies = cookieInput.split(';');
return [cookies[0].split('=')[1],cookies[1].split('=')[1]]
}
OneCookieParser.js 는 쿠키가 하나만 들어갔을 때 ( 토큰이 하나만 있는 경우에 쓰는 모듈이다.
modules/OneCookieParser.js
module.exports = (cookieInput) => {
return cookieInput.split('=')[1]
}
verifyToken.js 는 토큰을 검증할 때 쓰는 모듈이다.
modules/verifyToken.js
const { decode } = require('jsonwebtoken');
const jwt = require('jsonwebtoken');
require('dotenv').config();
function verifyToken(access, refresh){
var AccessToken = jwt.verify(access, process.env.SECRET_KEY, (error, decoded) => {
if(error) {
console.error(error);
return;
}
var result = [decoded, refresh]
return result;
});
return AccessToken
}
module.exports = verifyToken;
Server
index.js에는 node 서버가 돌아간다. express로 서버를 열고, 라우팅 해줄 뿐인 코드다.
index.js
const express = require('express')
const app = express()
const router = express.Router()
const login = require('./router/login')
const logout = require('./router/logout')
const createToken = require('./token/createToken')
app.use(express.json())
app.use(express.urlencoded({ extends: true}))
app.use('/',login) // /login response login.html
app.use('/logout',logout) // Logout response logout.html
app.use('/login',createToken) // Create Access & Refresh Token
app.listen(3000,()=>{
console.log("server is running to http://localhost:3000")
})
login.js 는 localhost:3000/ 으로 들어갔을 때. 토큰을 검증한 후, 상황에 맞는 동작을 할 수 있게하는 역할을 해준다.
/router/login.js
const app = require('express')()
const router = require('express').Router()
const verifyToken = require('../token/verifyToken')
const headCookieParser = require('../middleware/headCookieParser')
const OneCookieParser = require('../middleware/OneCookieParser')
const CreateToken = require('../middleware/CreateToken')
router.get('/', (req,res)=>{
var ck = req.headers.cookie
if(req.headers.cookie == undefined){
res.sendFile('/workspace/Access-Refresh-Token-Example/web/login.html');
// 토큰이 둘 다 없을 때는 로그인 페이지로 리다이렉트, 본인 경로로 바꾸어주자
}else{
if(ck.includes(';')){
var cookies = headCookieParser(req.headers.cookie)
console.log(cookies)
var access = verifyToken(cookies[0], cookies[1]); //토큰이 둘 다 있다면 로그인 ( 본 예제에서는 id, name, refreshToken을 반환함 )
res.redirect('/logout')
}else{ //토큰이 하나만 있다면 취할 동작
var cookies = OneCookieParser(ck)
var WhatToken = verifyToken(cookies)
if(WhatToken[0].name == undefined && WhatToken[0].iss == 'YoonHyunWoo'){// Refresh Token만 있는 경우
var AccessToken = CreateToken('AccessKey', 'awda','awdad');
res.cookie('access',AccessToken,{maxAge:3600000, httpOnly:true})
res.redirect('http://localhost:3000/logout')
}else{ // Access Token만 있는 경우
var RefreshToken = CreateToken('RefreshKey');
res.cookie('RefreshToken',RefreshToken,{maxAge:3600000, httpOnly:true})
res.redirect('http://localhost:3000/logout')
}
}
}
})
module.exports = router;
logout.js는 로그인된 상태를 나타대며, 이 또한 토큰을 검증하며 , 로그아웃 버튼으로 로그아웃을 할 수 있는 logout.html 을 리턴해준다.
/router/logout.js
const app = require('express')()
const router = require('express').Router()
const verifyToken = require('../modules/verifyToken')
const headCookieParser = require('../modules/headCookieParser')
const OneCookieParser = require('../modules/OneCookieParser')
const CreateToken = require('../modules/CreateToken')
const removeToken = require('../modules/removeToken')
router.get('/',(req,res)=>{
res.sendFile('/workspace/Access-Refresh-Token-Example/web/logout.html'); //본인 경로로 바꾸어주자
var ck = req.headers.cookie
if(req.headers.cookie == undefined){
res.redirect('/'); // 토큰이 둘 다 없을 때는 로그인 페이지로 리다이렉트
}else{
if(ck.includes(';')){
var cookies = headCookieParser(req.headers.cookie)
console.log(cookies)
var access = verifyToken(cookies[0], cookies[1]); //토큰이 둘 다 있다면 로그인 ( 본 예제에서는 id, name, refreshToken을 반환함 )
res.send({
"id": access[0].id,
"name": access[0].name,
"RefreshToken": access[1]
})
}else{ //토큰이 하나만 있다면 취할 동작
var cookies = OneCookieParser(ck)
var WhatToken = verifyToken(cookies)
if(WhatToken[0].name == undefined && WhatToken[0].iss == 'YoonHyunWoo'){// Refresh Token만 있는 경우
var AccessToken = CreateToken('AccessKey', 'awda','awdad');
res.cookie('access',AccessToken,{maxAge:3600000, httpOnly:true})
res.redirect('http://localhost:3000/')
}else{ // Access Token만 있는 경우
var RefreshToken = CreateToken('RefreshKey');
res.cookie('RefreshToken',RefreshToken,{maxAge:3600000, httpOnly:true})
res.redirect('http://localhost:3000/')
}
}
}
})
router.post('/',(req,res)=>{
res.clearCookie('access');
res.clearCookie('refresh');
res.redirect('http://localhost:3000/')
})
module.exports = router
createToken.js 는 /login으로 요청을 보내면, 토큰을 만들어준다.
/router/createToken
const express = require('express')
const app = express()
const router = express.Router()
const login = require('./router/login')
const logout = require('./router/logout')
const createToken = require('./router/createToken')
app.use(express.json())
app.use(express.urlencoded({ extends: true}))
app.use('/',login) // /login response login.html
app.use('/logout',logout) // Logout response logout.html
app.use('/login',createToken) // Create Access & Refresh Token
app.listen(3000,()=>{
console.log("server is running to http://localhost:3000")
})
마치며
브라우저단에서 localhost:3000으로 들어간 후 f12 → 애플리케이션 → 쿠키 로 가보면 받은 쿠키 (토큰들) 을 조회하고 삭제할 수 있으니 삭제해보고, 들어가보며 테스트해보길 바란다.

지금은 쿠키에 인증 정보를 담아주었지만, 다른 여러 방법이 있으니 구글링해보며 찾아보길 바란다.