Access Token과 Refresh Token을 발급해보자.
# yarn을 이용해 프로젝트를 초기화합니다.
yarn init -y
# express, jsonwebtoken, cookie-parser 패키지를 설치합니다.
yarn add express jsonwebtoken cookie-parser
- package.json에 "type":"module" 추가
app.js ( 기본적인 틀 )
// app.js
import express from 'express';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';
const app = express();
const PORT = 3019;
// 비밀 키는 외부에 노출되면 안되겠죠? 그렇기 때문에, .env 파일을 이용해 비밀 키를 관리해야함
const ACCESS_TOKEN_SECRET_KEY = `HangHae99`; // Access Token의 비밀 키를 정의한다.
const REFRESH_TOKEN_SECRET_KEY = `Sparta`; // Refresh Token의 비밀 키를 정의한다.
app.use(express.json());
app.use(cookieParser());
app.get('/', (req, res) => {
return res.status(200).send('Hello Token!');
});
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
Refresh Token과 Access Token을 발급하는 API 만들어보기
POST /tokens 의 주소를 갖는 API 생성
// app.js
import express from 'express';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';
const app = express();
const PORT = 3019;
// 비밀 키는 외부에 노출되면 안되겠죠? 그렇기 때문에, .env 파일을 이용해 비밀 키를 관리해야함
const ACCESS_TOKEN_SECRET_KEY = `HangHae99`; // Access Token의 비밀 키를 정의한다.
const REFRESH_TOKEN_SECRET_KEY = `Sparta`; // Refresh Token의 비밀 키를 정의한다.
app.use(express.json());
app.use(cookieParser());
app.get('/', (req, res) => {
return res.status(200).send('Hello Token!');
});
let tokenStorage = {}; // Refresh Token을 저장할 객체
/** Access Token, Refresh Token 발급 API **/
app.post('/tokens', (req, res) => {
// id 전달
const { id } = req.body;
// accessToken 발급
const accessToken = createAccessToken(id);
// refreshToken 발급
const refreshToken = createRefreshToken(id);
// Refresh Token을 가지고 해당 유저의 정보를 서버에 저장합니다.
tokenStorage[refreshToken] = {
id: id, // 사용자에게 전달받은 ID를 저장합니다.
ip: req.ip, // 사용자의 IP 정보를 저장합니다.
// 사용자의 User Agent 정보를 저장합니다.
// 특정 클라이언트가 어떤 방식으로 서버에 요청했는지를 알수 있는 정보
// 예를 들어 firefox를 이용했는지, chrome을 이용했는지, 모바일로 chrome을 실행했는지 등
userAgent: req.headers['user-agent'],
};
//console.log(tokenStorage);
res.cookie('accessToken', accessToken); // Access Token을 Cookie에 전달한다.
res.cookie('refreshToken', refreshToken); // Refresh Token을 Cookie에 전달한다.
return res
.status(200)
.json({ message: 'Token이 정상적으로 발급되었습니다.' });
});
// Access Token을 생성하는 함수
function createAccessToken(id) {
const accessToken = jwt.sign(
{ id: id }, // JWT 데이터
ACCESS_TOKEN_SECRET_KEY, // Access Token의 비밀 키
{ expiresIn: '10s' }, // Access Token이 10초 뒤에 만료되도록 설정한다.
);
return accessToken;
}
// Refresh Token을 생성하는 함수
function createRefreshToken(id) {
const refreshToken = jwt.sign(
{ id: id }, // JWT 데이터
REFRESH_TOKEN_SECRET_KEY, // Refresh Token의 비밀 키
{ expiresIn: '7d' }, // Refresh Token이 7일 뒤에 만료되도록 설정한다.
);
return refreshToken;
}
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
Refresh Token의 정보를 어디서 관리해야할까?
위 예시에서는 tokenStorage 라는 변수에서 관리했지만, 이는 인 메모리 방식을 사용하기 때문에 서버가 재시작 또는 종료될 경우 모든 정보가 사라지게 된다.
따라서 별도의 테이블에서 Refresh Token을 저장하고 관리해야한다.
이렇게 하면, Refresh Token 검증 작업을 MySQL과 같은 데이터베이스를 조회함과 동시에 함께 처리할 수 있다.
Refresh Tokens Table
Name | 타입 ( Type ) | NULL | default | 비고 |
tokenId ( PK ) | INTEGER | NOT NULL | AUTO_INCREMENT | 토큰의 기본 키 |
userId ( FK ) | INTEGER | NOT NULL | 사용자의 기본 키 | |
token | STRING | NOT NULL | 리프레시 토큰 | |
expiresAt | DATETIME | NOT NULL | 만료 날짜 | |
createdAt | DATETIME | NOT NULL | 현재 시간 | 생성 날짜 |
- 이 외에도 ip 또는 user-Agent와 같은 정보를 추가할 수 있다.
Access Token 검증 API
서버에서 발급받은 Access Token을 검증하는 GET /tokens/validate API를 만들어보자.
// app. js
...
/** 엑세스 토큰 검증 API **/
app.get('/tokens/validate', (req, res) => {
const accessToken = req.cookies.accessToken;
if (!accessToken) {
return res
.status(400)
.json({ errorMessage: 'Access Token이 존재하지 않습니다.' });
}
const payload = validateToken(accessToken, ACCESS_TOKEN_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: 'Access Token이 유효하지 않습니다.' });
}
const { id } = payload;
return res.json({
message: `${id}의 Payload를 가진 Token이 성공적으로 인증되었습니다.`,
});
});
// Token을 검증하고 Payload를 반환합니다.
function validateToken(token, secretKey) {
try {
const payload = jwt.verify(token, secretKey);
return payload;
} catch (error) {
return null;
}
}
ValidateToken 함수 추가 설명
ValidaeToken 함수는 제공된 토큰이 유효한지 여부를 검증하는 역할을 담당한다.
- secretkey를 전달받아, 서버에서 검증할 비밀 키를 설정한다.
- Access Token 이나 Refresh Token이 우리가 발급한 것인지 검증한다.
- Access Token 이나 Refresh Token의 만료 여부를 검증한다.
사용자가 Cookie를 전달할 때, Access Token이 없다면 에러가 발생한다.
- { "message": "Access Token이 존재하지 않습니다." }
사용자가 전달한 Access Token이 유효하지 않을 경우 에러가 발생한다.
- { "message": "Access Token이 유효하지 않습니다." }
사용자가 Access Token을 이용해 API를 호출하면, 해당 토큰의 상태에 따라 적절한 응답 ( Response )이 반환되는 것을 확인 할 수 있다.
Access Token이 정상적으로 인증되면, 응답에서 페이로드 값을 확인할 수 있다.
Refresh Token으로 Access Token을 재발급하는 API
서버에서 발급받은 Access Token을 검증하는 POST /tokens/refresh API를 만들어보자.
// app. js
...
/** 리프레시 토큰 검증 API **/
app.post('/tokens/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken)
return res
.status(400)
.json({ errorMessage: 'Refresh Token이 존재하지 않습니다.' });
const payload = validateToken(refreshToken, REFRESH_TOKEN_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: 'Refresh Token이 유효하지 않습니다.' });
}
const userInfo = tokenStorage[refreshToken];
if (!userInfo)
return res.status(419).json({
errorMessage: 'Refresh Token의 정보가 서버에 존재하지 않습니다.',
});
const newAccessToken = createAccessToken(userInfo.id);
res.cookie('accessToken', newAccessToken);
return res.json({ message: 'Access Token을 새롭게 발급하였습니다.' });
});
// Token을 검증하고 Payload를 반환합니다.
function validateToken(token, secretKey) {
try {
const payload = jwt.verify(token, secretKey);
return payload;
} catch (error) {
return null;
}
}
사용자가 Cookie를 전달할 때, Refresh Token이 없다면 에러가 발생한다.
- { "message": "Refresh Token이 존재하지 않습니다." }
사용자가 전달한 Refresh Token이 유효하지 않을 경우 에러가 발생한다.
- { "message": "Refresh Token이 유효하지 않습니다." }
Refresh Token이 유효하지만, 서버에 해당 토큰 정보가 없을 경우 에러가 발생한다.
- { "message": "Refresh Token의 정보가 서버에 존재하지 않습니다." }
사용자가 Refresh Token을 이용해 API를 호출하면, 해당 토큰의 상태에 따라 적절한 응답 ( Response )이 반환되는 것을 확인할 수 있다.
만약, Refresh Token이 정상적으로 인증되면, 사용자에게 Access Token을 담은 Cookie를 전달하는 것을 확인할 수 있다.
'Javascript > 실습' 카테고리의 다른 글
[Javascript][실습][게시판 프로젝트] 로그인, 회원가입 API (0) | 2024.09.09 |
---|---|
[Javascript][실습] JWT을 이용해 데이터 암호화 (0) | 2024.09.08 |