Node.js AccessToken / RefreshToken
2024. 5. 21. 14:02ㆍNode.js
728x90
반응형
AccessToken에 개념을 이해하고 사용을 하고 있었지만, 이번에 내일배움캠프를 수강 중 RefreshToken을 배워 개념과 간단하게 사용하는 방법을 알아보려고 합니다.
JWT(Json Web Token)?
Header
JWT를 검증하는 데 필요한 정보를 가진 JSON을 Base64 알고리즘 기반으로 인코딩한 문자열입니다. 검증을 하기 위한 내용을 가지고 있습니다.
Payload
JWT에 저장된 값입니다. (name, value)의 쌍으로 이루어져 있고, JWT에는 이 값들을 여러 개 할당할 수 있습니다. Payload의 값은 암호화되지 않기에, 비밀번호와 같은 민감한 값을 넣으면 안 됩니다.
Signature
JWT를 인코딩하거나 유효성 검증을 할 때 사용하는 암호화 된 코드입니다.
1. Acess Token만을 이용한 서버 인증 방식
- 사용자가 로그인
- 서버에서 사용자 확인 후, Access Token(JWT)에 권한 인증을 위한 정보를 Payload에 넣고 생성
- 생성한 토큰을 클라이언트에게 반환하고, 클라이언트는 받은 토큰을 저장
- 클라이언트는 권한 인증이 필요한 요청을 할 때마다 이 토큰을 헤더(쿠키)에 전송
- 서버는 헤더의 토큰을 검증하고, Payload의 값을 디코딩하여 사용자의 권한을 확인하고 데이터를 반환합니다.
- 만약, 토큰이 valid하지 않거나 만료되었다면 새로 로그인을하여 토큰을 발급받아야합니다.
2. Refresh Token
Refresh Token은 Access Token이 탈취당하거나 유효기간이 만료되었을 때 사용됩니다. Access Token은 유효기간이 짧은 대신에, Access Token이 만료되면 클라이언트에서는 Refresh Token을 서버에 전송해 사용자인증을 하고 Access Token을 재발급받아 전송받습니다.
import express from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import { userLoginSchema } from '../middlewarmies/validation.middleware.js';
import { prisma } from "../utils/prisma.util.js";
import { catchAsync } from '../middlewarmies/error-handler.middleware.js';
import { AUTH_MESSAGES } from '../constants/auth.constant.js';
import { SECRET_KEY, JWT_EXPIRATION_TIME, REFRESH_SECRET_KEY, REFRESH_TOKEN_EXPIRATION_TIME} from '../constants/env.constant.js';
authRouter.post('/sign-in', catchAsync(async (req, res) => {
const data = req.body;
const { error } = userLoginSchema.validate(data);
if (error) return res.status(400).json({ message: error.message });
const userData = await prisma.user.findFirst({ where: { email: data.email } });
if(!userData) return res.status(401).json({ message: AUTH_MESSAGES.INVALID_CREDENTIALS });
const isMatched = await bcrypt.compare(data.password, userData.password);
if (!isMatched) return res.status(401).json({ message: AUTH_MESSAGES.INVALID_CREDENTIALS });
const accessToken = jwt.sign(
{ id: userData.id,
role: userData.role
},
SECRET_KEY,
{ expiresIn: JWT_EXPIRATION_TIME }
);
// RefreshToken 생성
const refreshToken = jwt.sign(
{ id: userData.id, role: userData.role },
REFRESH_SECRET_KEY,
{ expiresIn: REFRESH_TOKEN_EXPIRATION_TIME }
);
// DB에 리프레시 토큰 저장
await prisma.user.update({
where: { id: userData.id },
data: {
refreshToken: refreshToken
}
});
return res.status(200).json({ accessToken, refreshToken});
}));
위에 코드는 현재 사용자가 email, password를 작성하였을 때 토큰을 생성하고 발급하는 로그인API이다.
acessToken 미들웨어
/** 엑세스 토큰 검증 API **/
const authMiddleware = catchAsync(async (req, res, next) => {
const accessToken = req.cookies.accessToken;
if (!accessToken || !accessToken.startsWith('Bearer ')) {
return res.status(400).json({ errorMessage: AUTH_MESSAGES.NO_AUTH_INFO });
}
const token = accessToken.split(' ')[1];
const payload = await validateToken(token, SECRET_KEY);
console.log(payload)
if (payload === null) {
// 액세스 토큰이 만료되었을 경우
const refreshToken = req.headers.authorization;
if (!refreshToken || refreshToken === null) {
// 리프레시 토큰 요청
return res.status(401).json({ errorMessage: AUTH_MESSAGES.REFRESH_TOKEN_REQUIRED });
} else {
// 헤더에 리프레시 토큰이 있을 경우, 리프레시 미들웨어로 이동
return refreshTokenMiddleware(req, res, next);
}
}
if (!payload) {
return res.status(401).json({ errorMessage: AUTH_MESSAGES.INVALID_AUTH });
}
const user = await prisma.user.findUnique({
where: { id: payload.id }
});
if (!user) {
return res.status(404).json({ errorMessage: AUTH_MESSAGES.USER_NOT_FOUND });
}
req.user = user;
next();
});
const validateToken = async (token, secretKey) => {
try {
const payload = jwt.verify(token, secretKey);
return payload;
} catch (error) {
return null;
}
}
export { authMiddleware, validateToken };
Refresh Token 미들웨어
/** 리프래시 토큰 검증 및 재발급 미들웨어 **/
const refreshTokenMiddleware = catchAsync(async (req, res, next) => {
// 쿠키에서 리프래시 토큰을 가져옴
const refreshToken = req.headers.authorization;
if (!refreshToken) {
return res
.status(400)
.json({ errorMessage: AUTH_MESSAGES.NO_REFRESH_TOKEN });
}
// 리프래시 토큰 검증
const payload = jwt.verify(refreshToken, REFRESH_SECRET_KEY);
if (!payload) {
return res
.status(401)
.json({ errorMessage: AUTH_MESSAGES.INVALID_REFRESH_TOKEN });
}
const data = await prisma.user.findFirst({
where: {
refreshToken: refreshToken,
},
});
if (!data)
return res.status(400).json({ errorMessage: AUTH_MESSAGES.TOKEN_END });
// 사용자 조회
const user = await prisma.user.findUnique({
where: { id: payload.id },
});
if (!user) {
return res.status(404).json({ errorMessage: AUTH_MESSAGES.USER_NOT_FOUND });
}
// 액세스 토큰 발급 (새로고침)
const newAccessToken = jwt.sign({ id: user.id }, REFRESH_SECRET_KEY, {
expiresIn: '15m',
});
// 새로운 액세스 토큰을 쿠키에 설정
// 12시간을 밀리초로 변환
// const maxAge = 12 * 60 * 60 * 1000;
res.cookie('accessToken', newAccessToken);
// 요청에서 사용자 정보를 전달
req.user = user;
// 다음 미들웨어로 이동
next();
});
export { refreshTokenMiddleware };
728x90
반응형
'Node.js' 카테고리의 다른 글
[Node.js] nodemailer로 이메일가입 및 인증 기능 구현 (0) | 2024.05.31 |
---|---|
[Node.js] 에러 메시지를 만났을 때 대처법 (0) | 2024.05.22 |
Prisma 시작하기 (0) | 2024.05.20 |
package.json dependencies devdependencies 차이 (0) | 2024.05.16 |
JavaScript 제너레이터(Generator) 알아보자! (0) | 2024.04.23 |