본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
Access Token은 사용자 인증(로그인)이 완료된 후 해당 사용자를 인증하는 용도로 발급하는 토큰
쿠키에 jwt를 설정하고, 만료 시간이 지나면 인증이 만료되는 구조 또한 Access Token이라고 할 수 있음
Access Token은 서버에 상태를 저장하지 않는 Stateless(무상태)이기 때문에 Node.js 서버가 재시작되더라도 동일하게 동작함
jwt를 이용해 사용자의 인증 여부를 확인할 수 있지만, 처음 토큰을 발급한 사용자가 그 사용자인지 확인할 수 없음
Access Token은 사용자 인증에 필요한 모든 정보를 가지고 있기 때문에 토큰을 가지고 있는 시간이 길수록 탈취되었을 때 큰 피해를 입음
그래서 Access Token의 유효 기간은 짧고, 평소 API 통신할 때 사용
Refresh Token은 특정 사용자가 Access Token을 발급받기 위한 목적으로만 사용됨
사용자의 인증 정보를 검증하는데 사용되며, 이를 서버에서 관리함
Refresh Token은 JWT 토큰의 탈취 위험(피해)을 최소화하고, 사용자 경험을 높이기 위해(빈번한 재로그인 방지를 위해) 사용
Refresh Token은 사용자가 서버와 최초 인증시에 발급받게 됨
Refresh Token의 유효 기간은 길고, Access Token 재발급 받을 때 사용
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!');
});
const tokenStorages = {}; // 리프레시 토큰을 관리할 객체
// 여기서는 변수를 사용했지만 데이터베이스에 넣어서 관리할 수 있음
// 엑세스, 리프레시 토큰 발급 API
app.post('/tokens', async (req, res) => {
// ID 전달
const { id } = req.body;
// 엑세스 토큰과 리프레시 토큰 발급
const accessToken = createAccessToken(id);
const refreshToken = jwt.sign({ id: id }, REFRESH_TOKEN_SECRET_KEY, {
expiresIn: '7d',
});
tokenStorages[refreshToken] = {
id: id,
ip: req.ip,
userAgent: req.headers['user-agent'],
};
console.log(tokenStorages);
// 클라이언트에게 쿠키(토큰) 할당
res.cookie('accessToken', accessToken);
res.cookie('refreshToken', refreshToken);
return res
.status(200)
.json({ message: 'Token이 정상적으로 발급되었습니다.' });
});
// Access Token 검증 API
app.get('/tokens/validate', async (req, res) => {
const { accessToken } = req.cookies;
// 엑세스 토큰이 존재하는지 확인
if (!accessToken) {
return res
.status(400)
.json({ errorMessage: 'Access Token이 존재하지 않습니다.' });
}
const payload = validationToken(accessToken, ACCESS_TOKEN_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: 'Access Token이 정상적이지 않습니다.' });
}
const { id } = payload;
return res.status(200).json({
message: `${id}의 Payload를 가진 Token이 정상적으로 인증되었습니다.`,
});
});
// Token을 검증하고, Payload를 조회하기 위한 함수
function validationToken(token, secretKey) {
try {
return jwt.verify(token, secretKey);
} catch (err) {
return null;
}
}
function createAccessToken(id) {
return jwt.sign({ id }, ACCESS_TOKEN_SECRET_KEY, {
expiresIn: '10s',
});
}
// Refresh Token을 이용해서, Access Token을 재발급하는 API
app.post('/tokens/refresh', async (req, res) => {
const { refreshToken } = req.cookies;
if (!refreshToken) {
return res
.status(400)
.json({ errorMessage: 'Refresh Token이 존재하지 않습니다.' });
}
const payload = validationToken(refreshToken, REFRESH_TOKEN_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: 'Refresh Token이 정상적이지 않습니다.' });
}
const userInfo = tokenStorages[refreshToken];
if (!userInfo) {
return res.status(409).json({
errorMessage: 'Refresh Token의 정보가 서버에 존재하지 않습니다.',
});
}
const newAccessToken = createAccessToken(userInfo.id);
res.cookie('accessToken', newAccessToken);
return res
.status(200)
.json({ message: 'Access Token을 정상적으로 새롭게 발급되었습니다.' });
});
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
2주차 남은 강의를 시청
최대한 시청 후 과제 문서를 작성할 수 있도록 노력할 것!!
내일도 시간이 많지 않기 때문에 오전, 오후에 최대한 시청할 예정
예비군으로 밀린 강의를 시청함
원래는 2주차를 오늘 안에 다 시청하려고 했으나, 생각보다 내용이 어려웠음
완전히 막힌다기 보다는 내용이 어려워서 검색하면서 이해하느라 진행이 느렸음
현재 2주차 1/3 정도 완료한 상태
주말을 이용해서 최대한 강의를 들을 예정