express 를 활용해서,jwt를 활용한 유저 인증 구현
npm install express typeorm mysql reflect-metadata cors dotenv
npm install -D @types/cors @types/express typescript
package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --exec ts-node src/app.ts",
"build": "tsc"
},
tsconfig.json
{
"compilerOptions": {
"lib": ["es5", "es6", "dom"],
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"outDir":"./dist",
},
}
db.ts
import { DataSource } from "typeorm"
export const myDataBase = new DataSource({
type: "mysql",
host: "db서버 주소",
port: 3306,
username: "유저이름",
password: "비밀번호",
database: "mydb", // db 이름
entities: ["src/entity/*.ts"], // 모델의 경로
logging: true, // 정확히 어떤 sql 쿼리가 실행됐는지 로그 출력
synchronize: true,
})
app.ts
import express from "express"
import { myDataBase } from "./db"
import cors from 'cors'
myDataBase
.initialize()
.then(() => {
console.log("DataBase has been initialized!")
})
.catch((err) => {
console.error("Error during DataBase initialization:", err)
})
const app = express()
app.use(express.json())
app.use(express.urlencoded())
app.use(cors({
origin: true // 모두 허용
}))
app.listen(3000, () => {
console.log("Express server has started on port 3000")
})
JWT 구현에 필요한 모듈 설치한다.
npm install jsonwebtoken bcrypt
npm install -D @types/jsonwebtoken @types/bcrypt
User 모델은 이메일,유저이름, 비밀번호를 입력받도록 작성한다.
src/entity/User.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true }) // 이메일은 중복되지 않도록 설계
email: string
@Column({ unique: true }) // 유저이름은 중복되지 않도록 설계
username: string
@Column()
password: string
@CreateDateColumn()
createdAt: Date
@UpdateDateColumn()
updatedAt: Date;
}
이제 토큰을 생성하기 위한 함수를 작성한다.
우선은 JWT토큰의 암호화, 복호화 시에 사용할 secret key를 정의해줘야한다.
secret key를 만들기 위해서, 아래 hex값 생성기를 이용한다.
https://www.browserling.com/tools/random-hex
랜덤으로 키를 생성해서 나온 값 2개를 .env
로 만들어서 저장한다.
SECRET_ATOKEN="랜덤 키값1"
SECRET_RTOKEN="랜덤 키값2"
백엔드에서 토큰을 발급할 때는, 우리가 정확히 어떤 토큰을 발급했는지를 저장해둬야한다. 백엔드에도 발급 정보가 남아있어야, 정확히 우리가 발급한 토큰이 맞는지를 검증할 수 있기 때문이다.
src/app.ts
// 캐시 형태로 발급된 토큰을 저장하기 위한 객체
// 실제로는 redis 를 활용함
export const tokenList = {};
app.ts 파일에 위처럼 토큰을 저장하기 위한 객체를 하나 추가한다. (실제로는 redis 를 사용해서 캐시 형태로 발급한 토큰들을 저장해둔다.)
유저 생성 시에 활용할 함수들을 미리 선언한다.
src/util/Auth.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import dotenv from 'dotenv';
import { tokenList } from '../app';
dotenv.config();
// 비밀번호 암호화를 위한 함수
export const generatePassword = async (pw: string) => {
const salt = await bcrypt.genSalt(10); // 암호화에 사용할 임의의 문자열 (salt) 생성
const password = await bcrypt.hash(pw, salt); // 해당 salt 를 활용해서 비밀번호 암호화
return password
};
// 엑세스 토큰 생성을 위한 함수
export const generateAccessToken = (id: number, username: string, email: string) => {
return jwt.sign(
{ id: id, username: username, email: email },
process.env.SECRET_ATOKEN,
{
expiresIn: '1h',
}
); // jwt.sign(유저 payload, 시크릿 키, 옵션) 형태로 명시 (만료 시간 1시간으로 설정)
}
// 리프레시 토큰 생성을 위한 함수
export const generateRefreshToken = (id: number, username: string, email: string) => {
return jwt.sign(
{ id: id, username: username, email: email },
process.env.SECRET_RTOKEN,
{
expiresIn: '30d',
}
); // 만료시간 30일로 설정
}
// 우리가 어떤 리프레시 토큰을 발급했는 저장하기 위한 함수
export const registerToken = (refreshToken: string, accessToken: string) => {
tokenList[refreshToken] = {
status: 'loggedin',
accessToken: accessToken,
refreshToken: refreshToken,
};
} // 토큰과 해당 토큰의 상태를 저장
// 발급한 리프레시 토큰 삭제를 위한 함수
export const removeToken = (refreshToken: string) => {
delete tokenList[refreshToken]
}
이제 회원가입을 위한 controller 를 작성한다.
회원가입 controller는
src/controller/UserController.ts
import { verify } from 'jsonwebtoken'
import bcrypt from 'bcrypt'
import { myDataBase } from '../db'
import { User } from '../entity/User'
import { generateAccessToken, generatePassword, generateRefreshToken, registerToken } from '../util/Auth'
import { Request, Response } from 'express'
export class UserController {
static register = async (req: Request, res: Response) => {
const { email, password, username } = req.body
// 중복 유저 체크
const existUser = await myDataBase.getRepository(User).findOne({
where: [
{ email }, // 이메일이 일치하거나
{ username }, // 유저네임이 일치하는 데이터가 있는지 확인
],
})
if (existUser) {
// 일치하는 유저가 있다면 400 리턴
return res.status(400).json({ error: 'Duplicate User' })
}
// return 이 안됐으면? 유저가 없다는 뜻!
// 유저 생성
const user = new User()
user.email = email
user.password = await generatePassword(password) // 암호화된 비밀번호 생성
user.username = username
// 익숙해지셨다면 여기서 try catch 로 db 에 접근 못했을 때의 에러 처리도 해주는 것이 좋음!
// save 의 경우 insert 와 다르게 해당 id 의 데이터가 이미 있다면 업데이트하는 로직을 가지고 있음 (사용 시 주의 필요)
const newUser = await myDataBase.getRepository(User).save(user) // 유저 생성 완료
// 만약 회원가입 시 자동 로그인 구현하고 싶다면, 여기서 토큰 발급까지 구현
// 액세스 토큰 및 리프레시 토큰 발급
const accessToken = generateAccessToken(newUser.id, newUser.username, newUser.email)
const refreshToken = generateRefreshToken(newUser.id, newUser.username, newUser.email)
// 어떤 토큰을 발급했는지 저장해놓기
registerToken(refreshToken, accessToken)
// 토큰을 복호화해서, 담겨있는 유저 정보 및 토큰 만료 정보도 함께 넘겨줌
const decoded = verify(accessToken, process.env.SECRET_ATOKEN)
res.send({ content: decoded, accessToken, refreshToken })
}
}
해당 controller 를 사용하는 router를 생성해서, app.ts에 등록해준다.
회원가입 롲기은 /register
로 post 요청을 보냈을 때 작동하도록 작성한다.
router/auth.ts
import {Router} from "express";
import {UserController} from "../controller/UserController";
const routes = Router();
routes.post('/register', UserController.register);
export default routes;
app.ts
app.use('/auth', AuthRouter)
json 형태로 email,username,password를 보내면 토큰 발급이 제대로 오는 것을 확인할수 있다.
로그인을 위한 controller를 작성해준다.
로그인 controller는
하는 순서로 이루어진다.
src/controller/UserController.ts
//내용 추가
import { verify } from 'jsonwebtoken'
import bcrypt from 'bcrypt'
import { myDataBase } from '../db'
import { User } from '../entity/User'
import { generateAccessToken, generatePassword, generateRefreshToken, registerToken } from '../util/Auth'
import { Request, Response } from 'express'
export class UserController {
static register = async (req: Request, res: Response) => {
const { email, password, username } = req.body
// 중복 유저 체크
const existUser = await myDataBase.getRepository(User).findOne({
where: [
{ email }, // 이메일이 일치하거나
{ username }, // 유저네임이 일치하는 데이터가 있는지 확인
],
})
if (existUser) {
// 일치하는 유저가 있다면 400 리턴
return res.status(400).json({ error: 'Duplicate User' })
}
// return 이 안됐으면? 유저가 없다는 뜻!
// 유저 생성
const user = new User()
user.email = email
user.password = await generatePassword(password) // 암호화된 비밀번호 생성
user.username = username
// 익숙해지셨다면 여기서 try catch 로 db 에 접근 못했을 때의 에러 처리도 해주는 것이 좋음!
// save 의 경우 insert 와 다르게 해당 id 의 데이터가 이미 있다면 업데이트하는 로직을 가지고 있음 (사용 시 주의 필요)
const newUser = await myDataBase.getRepository(User).save(user) // 유저 생성 완료
// 만약 회원가입 시 자동 로그인 구현하고 싶다면, 여기서 토큰 발급까지 구현
// 액세스 토큰 및 리프레시 토큰 발급
const accessToken = generateAccessToken(newUser.id, newUser.username, newUser.email)
const refreshToken = generateRefreshToken(newUser.id, newUser.username, newUser.email)
// 어떤 토큰을 발급했는지 저장해놓기
registerToken(refreshToken, accessToken)
// 토큰을 복호화해서, 담겨있는 유저 정보 및 토큰 만료 정보도 함께 넘겨줌
const decoded = verify(accessToken, process.env.SECRET_ATOKEN)
res.send({ content: decoded, accessToken, refreshToken })
}
static login = async (req: Request, res: Response) => {
// 로그인
const { email, password } = req.body
//db에 유저가 있는지 확인
const user = await myDataBase.getRepository(User).findOne({
where:{email},
})
if (!user) {
return res.status(400).json({error: 'User not found'})
}
// 유저가 있네?
const validPassword = await bcrypt.compare(password, user.password)
// 근데 비밀번호가 틀리네?
if (!validPassword) {
return res.status(400).json({error: "invalid Password"})
}
// 액세스 토큰 및 리프레시 토큰 발급
const accessToken = generateAccessToken(user.id, user.username, user.email)
const refreshToken = generateRefreshToken(user.id, user.username, user.email)
// 어떤 토큰을 발급했는지 저장해놓기
registerToken(refreshToken, accessToken)
// 노큰을 복호화해서, 담겨있는 유저 정보도 함께 넘겨준다
const decoded = verify(accessToken, process.env.SECRET_ATOKEN)
res.send({constent: decoded, accessToken, refreshToken})
}
}
routes.post('/login', UserController.login)
이메일과 비밀번호를 사용해서 로그인 해보면 응답이 잘 오는 것을 확인할수 있다.
지금까지는 access token과 refresh token을 모두 응답 데이터에 담아서 보내주는데,
이제 refresh token은 cookie 형태로 내려주도록 작성한다.
백엔드에서 쿠키를 설정할 때는 res.cookie(키, 값, 옵션)
형태로 작성해주면 된다.
src/UserController
// 코드 추가 refister와 login 둘다 작성할것
res.cookie('refreshToken', refreshToken, { path: '/', httpOnly: true, maxAge: 60 * 60 * 24 * 30 * 1000 } )
이제 로그인 요청을 해보면 refresh token은 cookie 형태로 응답이 오는 것을 확인할수 있다
React에서 요청을 보낼 경우, withCredentials를 꼭 true로 해주어야한다는 것을 명심하자.
post 모델을 작성하는데, 작성자 부분은 user 모델과 연결을 시켜줘야한다.
우선 제목과 내용을 받도록 설꼐하고, author부분은 ManyToOne 관계로 user 모델과 연결시켜보자
entity/Post.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne } from "typeorm"
import { User } from "./User"
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
body: string
@CreateDateColumn()
createdAt: Date
@UpdateDateColumn()
updatedAt: Date;
@ManyToOne(type => User, user => user.posts) // author를 User의 posts와 연결
author: User;
}
user 모델 쪽에서도 posts 라는 속성을 만들어서, oneTomany 관계로 post의 작성자 부분과 연결시켜준다. 한명의 유저가 다수의 게시글을 쓸수 있으므로
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne } from "typeorm"
import { User } from "./User"
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
body: string
@CreateDateColumn()
createdAt: Date
@UpdateDateColumn()
updatedAt: Date;
@ManyToOne(type => User, user => user.posts) // author를 User의 posts와 연결
author: User;
}
이제 token을 검증하는 로직을 구현해놓고, post 와 관련한 요청을 할 때 해당 로직이 먼저 작동한 후에, 토큰이 검증된 경우에만 요청을 처리하도록 코드를 작성한다.
token 을 검증하는 로직은
src/middleware/AuthMiddleware.ts
import { verify } from 'jsonwebtoken';
import dotenv from 'dotenv';
import { NextFunction, Request, Response } from 'express';
dotenv.config();
export interface TokenPayload { // token decode 하면 무엇이 들어가 있는지 작성
email: string;
username: string;
id: number;
}
export interface JwtRequest extends Request { // payload 를 포함한 request 타입을 생성
decoded?: TokenPayload
}
export class AuthMiddleware {
static verifyToken = (req: JwtRequest, res: Response, next: NextFunction) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(403).send('A token is required for authentication');
}
try {
const decoded = verify(token, process.env.SECRET_ATOKEN) as TokenPayload; // 타입 표명 (보통은 사용하지 않는 것이 좋지만, verify 함수 자체를 수정할 수 없고, 개발자가 타입을 더 정확히 알고 있으므로 사용)
req.decoded = decoded;
} catch (err) {
return res.status(401).send('Invalid Token');
}
return next(); // 다음 로직으로 넘어가라는 뜻
};
}
이제 post controller 에 글쓰기 로직을 작성한다.
글쓰기 로직은 이전처럼 title, body 를 받아서 db 에 저장하고 끝이 아니라, 중간에 유저를 찾아서 해당 유저를 작성자로 등록하는 과정이 추가된다.
사용자가 글쓰기 요청을 보내면, 아까 작성했던 verify token 로직이 먼저 수행되면서 아래의 순서대로 글쓰기 로직이 동작한다.
controller/PostController.ts
import { Request, Response } from "express";
import {Post} from "../entity/Post";
import { myDataBase } from "../db"
import { User } from "../entity/User";
import { JwtRequest } from "../middleware/AuthMiddleware";
export class PostController {
static createPost = async (req: JwtRequest, res: Response) => {
const {title, body} = req.body;
// 토큰 복호화를 통해 찾아낸 유저 번호
const {id: userId} = req.decoded;
// 해당 번호를 토대로 db 에서 유저 찾아냄
// (토큰 검증 시에 예외처리를 하고 있어서 여기서는 따로 안했지만, 필요하다면 여기서 추가적인 예외처리를 해주면 됩니다.)
const user = await myDataBase.getRepository(User).findOneBy({
id: userId
});
const post = new Post();
post.title = title;
post.body = body;
post.author = user; // 해당 user 를 author 로 등록
const result = await myDataBase.getRepository(Post).save(post);
res.status(201).send(result);
}
}
이제 해당 controller 의 로직과 verify token 로직을 아래처럼 등록한다.
router/posts.ts
import {Router} from "express";
import {PostController} from "../controller/PostController";
import { AuthMiddleware } from "../middleware/AuthMiddleware";
const routes = Router();
routes.post('', AuthMiddleware.verifyToken, PostController.createPost);
export default routes;`
이제 마지막으로 app.ts에 해당 router를 등록해주면 끝!
app.use('/posts', PostsRouter)
글 작성 기능을 테스트 해보자 여기서 추가해야할 정보는 authorization에서 로그인 하면서 얻은 accesstoken값을 입력해준 뒤에 글을 작성해줘야한다.
글 작성시에는 result 객체 대신 성공 여부만 응답으로 내려주도록 변경해보자. (result 객체 자체를 수정해도 무방함)
글을 가져올 때는 ~.find({select:{필요한 정보}}
의 형태로 작성해주시면, 해당 정보만 db에서 가져오게 된다. select 구문을 포함시켜서, 글을 가져올 때 작성자 부분에는 유저 번호, 이름 정도만 포함되도록 작성해준다.
import { Request, Response } from "express";
import {Post} from "../entity/Post";
import { myDataBase } from "../db"
import { User } from "../entity/User";
import { JwtRequest } from "../middleware/AuthMiddleware";
export class PostController {
static createPost = async (req: JwtRequest, res: Response) => {
const {title, body} = req.body;
// 토큰 복호화를 통해 찾아낸 유저 번호
const {id: userId} = req.decoded;
// 해당 번호를 토대로 db 에서 유저 찾아냄
const user = await myDataBase.getRepository(User).findOneBy({
id: userId
});
const post = new Post();
post.title = title;
post.body = body;
post.author = user; // 해당 user 를 author 로 등록
// !!글 썼을 때는 성공 여부만 보여줄 예정이기 때문에, 그냥 insert 로 변경
const result = await myDataBase.getRepository(Post).insert(post);
res.status(201).send({ message: 'success' }); // !!성공 여부만 응답으로 주도록
}
static getPosts = async (req: Request, res: Response) => {
const results = await myDataBase.getRepository(Post).find({
select: {
author: { // !!author 에서 필요한 것만 갖고오도록 (즉, 비밀번호 등은 표시되지 않도록)
id: true,
username: true,
email: true
}
},
relations: { // !!데이터 가져올 때 author 도 표시하도록 설정
author: true
}
});
res.send(results);
}
static getPost = async (req: Request, res: Response) => {
const results = await myDataBase.getRepository(Post).findOne({
where: { id: Number(req.params.id) },
select: {
author: {
id: true,
username: true,
email: true
}
},
relations: {
author: true
}
});
res.send(results);
}
}
라우터 다시 작성
import {Router} from "express";
import {PostController} from "../controller/PostController";
import { AuthMiddleware } from "../middleware/AuthMiddleware";
const routes = Router();
routes.post('', AuthMiddleware.verifyToken, PostController.createPost);
routes.get('', PostController.getPosts);
routes.get('/:id', PostController.getPost);
export default routes;
update와 delete 요청을 처리할때는, 요청을 보낸 유저가 해당 글의 작성자와 일치하는지 검증해야한다.
controller/PostController.ts
import { Request, Response } from "express";
import {Post} from "../entity/Post";
import { myDataBase } from "../db"
import { User } from "../entity/User";
import { JwtRequest } from "../middleware/AuthMiddleware";
export class PostController {
static createPost = async (req: JwtRequest, res: Response) => {
const {title, body} = req.body;
// 토큰 복호화를 통해 찾아낸 유저 번호
const {id: userId} = req.decoded;
// 해당 번호를 토대로 db 에서 유저 찾아냄
const user = await myDataBase.getRepository(User).findOneBy({
id: userId
});
const post = new Post();
post.title = title;
post.body = body;
post.author = user; // 해당 user 를 author 로 등록
const result = await myDataBase.getRepository(Post).insert(post);
res.status(201).send({ message: 'success' }); // 성공 여부만 응답으로 주도록
}
static getPosts = async (req: Request, res: Response) => {
const results = await myDataBase.getRepository(Post).find({
select: {
author: { // author 에서 필요한 것만 갖고오도록 (즉, 비밀번호 등은 표시되지 않도록)
id: true,
username: true,
email: true
}
},
relations: { // 데이터 가져올 때 author 도 표시하도록 설정
author: true
}
});
res.send(results);
}
static getPost = async (req: Request, res: Response) => {
const results = await myDataBase.getRepository(Post).findOne({
where: { id: Number(req.params.id) },
select: {
author: {
id: true,
username: true,
email: true
}
},
relations: {
author: true
}
});
res.send(results);
}
static updatePost = async (req: JwtRequest, res: Response) => {
const {id: userId} = req.decoded;
const currentPost = await myDataBase.getRepository(Post).findOne({
where: {id: Number(req.params.id)},
relations: {
author: true // 데이터 가져올 때 author 도 표시하도록 설정 / ['author'] 라고 작성해도 됨
}
});
if (userId !== currentPost.author.id) { // 글 작성자와 요청 보낸 사람이 일치하지 않으면
return res.status(401).send('No Permission') // 거부
}
const {title, body} = req.body;
const newPost = new Post();
newPost.title = title;
newPost.body = body;
const results = await myDataBase.getRepository(Post).update(
Number(req.params.id),
newPost
);
res.send(results);
}
static deletePost = async (req: JwtRequest, res: Response) => {
const {id: userId} = req.decoded;
const currentPost = await myDataBase.getRepository(Post).findOne({
where: {id: Number(req.params.id)},
relations: {
author: true
}
});
if (userId !== currentPost.author.id) { // 글 작성자와 요청 보낸 사람이 일치하지 않으면
return res.status(401).send('No Permission') // 거부
}
const results = await myDataBase.getRepository(Post).delete(Number(req.params.id));
res.send(results);
}
}
router/Posts
import {Router} from "express";
import {PostController} from "../controller/PostController";
import { AuthMiddleware } from "../middleware/AuthMiddleware";
const routes = Router();
routes.post('', AuthMiddleware.verifyToken, PostController.createPost);
routes.get('', PostController.getPosts);
routes.get('/:id', PostController.getPost);
routes.put('/:id', AuthMiddleware.verifyToken, PostController.updatePost);
routes.delete('/:id', AuthMiddleware.verifyToken, PostController.deletePost);
export default routes;
id 가 9인 글을 수정/제거 해보자
수정이 잘 된다. 그럼 제거도 해보자!
오호! 말끔하게 없어졌다!
이제 토큰 재발급을 위한 refresh, 토큰 검증을 위한 verify API를 작성해보자
요청으로 들어온 쿠키를 백엔드에서 처리할수 있도록 cookie-parser를 설치해보자.
npm instal cookie-parser
install -D @types/cookie-parser
해당 cookie-parser 를 사용한다고 app.ts에 명시
app.use(cookieParser()) // cookie-parser 사용
refresh 요청을 처리하기 위해서는 우선 refresh token을 검증하는 로직을 추가해놓는다.
refresh token의 검증은
middleware/AuthMiddleware.ts
// 코드 추가
static verifyRefreshToken = (
req: JwtRequest,
res: Response,
next: NextFunction,
) => {
const cookies = req.cookies
// 쿠키에 refreshToken 있니?
if (!cookies.refreshToken) {
return res.status(403).json({ error: 'No Refresh Token' })
}
// 우리꺼 토큰이 맞니?
if (!(cookies.refreshToken in tokenList)) {
return res.status(401).json({error:'Invalid Refresh Token'})
}
// 우리꺼 맞네?
try {
// 기존 리프레시 토큰 복호화
const decoded = verify(cookies.refreshToken, process.env.SECRET_RTOKEN) as TokenPayload
req.decoded = decoded
} catch (err) {
return res.status(401).send('Invalid Refresh Token')
}
return next()
}
이제 userController 에 refresh 함수를 추가한다
이 refresh 함수는
controller/UserController.ts
// 코드 추가
static refresh = async (req: JwtRequest, res: Response) => {
const { id, username, email } = req.decoded
// 기존에 발급한 토큰은 메모리에서 삭제
removeToken(req.cookies.refreshToken)
// 액세스 토큰 및 리프레시 토큰 새롭게 발급
const accessToken = generateAccessToken(id, username, email)
const refreshToken = generateRefreshToken(id, username, email)
// 새롭게 발급한 토큰 저장
registerToken(refreshToken, accessToken)
// 토큰을 복호화해서, 담겨있는 유저 정보 및 토큰 만료 정보도 함께 넘겨줌
const decoded = verify(accessToken, process.env.SECRET_ATOKEN)
res.cookie('refreshToken', refreshToken, {
path: '/',
httpOnly: true,
maxAge: 60 * 60 * 24 * 30 * 1000,
})
res.send({ content: decoded, accessToken })
}
refresh 요청은 리프레시 토큰 검증 로직을 거쳐야 하므로, 아래와 같이 작성한다.
router/auth.ts
//코드 추가
routes.get('/refresh', AuthMiddleware.verifyRefreshToken, UserController.refresh);
이제 accessToken 이 만료되어서 헤더에 아무것도 없어도, cookie에 refreshToken 만 있으면 토큰을 재발급해준다!
토큰 검증을 위한 api는 특별히 더 추가할 것이 없다.
기존에 만들어놓은 verify token 로직을 거쳐서, 해당 토큰 정보를 리턴하도록 작성해주면 된다.
controller/UserController.ts
//코드 추가
static verify = async (req: JwtRequest, res: Response) => {
// 필요하다면 verify 할때마다 토큰을 다시 발급해줄수도 있을것
// 미들웨어를 거치면서 req에 이미 decoded가 들어있음
res.send({ content: req.decoded })
}
router/auth.ts
//코드 추가
routes.get('/verify', AuthMiddleware.verifyToken, UserController.verify)
엑세스 토큰을 헤더에 포함시켜서 verify로 요청을 보내니 현재 요청을 보내는 사람이 누구인지, 현재 토큰의 발행, 만료시간은 어떠한지를 알 수 있다.