다음은 책 리액트를 다루는 기술을 읽고 공부한 내용을 바탕으로 작성된 글입니다.
이제 클라이언트에서 사용자 로그인 정보를 지닐 수 있도록 토큰을 발급하자.
JWT 토큰을 만들기 위해서 jsdonwebtoken 이라는 모듈을 설치한다.
$> yarn add jsonwebtoken
1. 비밀키 설정하기
.env 파일을 열어서 JWT 토큰을 만들 때 사용할 비밀키를 만든다.
비밀키는 문자열로 아무거나 상관없으며, 길이도 자유다.
이 비밀키는 나중에 토큰의 서명을 만드는 과정에서 사용되므로 외부에 공개되면 절대로 안 된다.
비밀키가 공개되면 누구든지 마음대로 토큰을 발급할 수 있게 된다.
- .env
PORT=4000
MONGO_URI= mongodb://localhost:27017/blog
JWT_SECRET= o3v3mvw834tynveskjfnvksdvuw93p8nywvjklfsbvyp98wyesrfbnjvsdg98wybvnwerslidjipw098ut2p04u8t9865v468374p83745nv6u3498p5435yvpt98laiw4faij4f8j3w8fi83f4hwuifhjasdhfuqh2if7hafa
2. 토큰 발급하기
user 모델 파일에서 generateToken 이라는 instance method를 만든다.
- src/models/user.js
UserSchema.methods.generateToken = function () {
const token = jwt.sign(
// 첫 번째 파라미터에는 토큰 안에 집어넣고 싶은 데이터를 넣는다.
{
_id: this.id,
username: this.username,
},
// 두 번째 파라미터에는 JWT 암호를 넣는다.
// eslint-disable-next-line no-undef
process.env.JWT_SECRET,
{
// 7일 동안 유효함
expiresIn: '7d',
},
);
return token;
};
이제 회원가입과 로그인에 성공했을 때 이 메서드를 통해 토큰을 생성해 사용자에게 전달하면 된다.
사용자 토큰을 쿠키에 담아서 사용하도록 하였다.
- src/api/auth/auth.ctrl.js
/*
POST /api/auth/register
{
username: 'subbni',
password: 'mypassword123',
}
*/
export const register = async (ctx) => {
// 회원가입
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(3).max(20).required(),
password: Joi.string().required(),
});
const result = schema.validate(ctx.request.body);
if (result.error) {
ctx.status = 400;
ctx.body = result.error;
return;
}
const { username, password } = ctx.request.body;
try {
//username이 이미 존재하는지 확인
const exists = await User.findByUsername(username);
if (exists) {
ctx.status = 409; // conflict
return;
}
const user = new User({
username,
});
await user.setPassword(password);
await user.save(); // 데이터베이스에 저장
// 응답할 데이터에서 hashedPassword 필드 제거
ctx.body = user.serialize();
const token = user.generateToken();
ctx.cookies.set('access_token', token, {
maxAge: 1000 * 60 * 60 * 24 * 7, // 7일
httpOnly: true,
});
} catch (e) {
ctx.throw(500, e);
}
};
/*
POST /api/auth/login
{
username: 'subbni',
password: 'mypassword123',
}
*/
export const login = async (ctx) => {
// 로그인
const { username, password } = ctx.request.body;
// username, password 없으면 에러 처리
if (!username || !password) {
ctx.status = 401;
return;
}
try {
const user = await User.findByUsername(username);
// 계정이 존재하지 않으면 에러 처리
if (!user) {
ctx.status = 401;
return;
}
const valid = await user.checkPassword(password);
// 비밀번호가 일치하지 않는 경우
if (!valid) {
ctx.status = 401;
return;
}
ctx.body = user.serialize();
const token = user.generateToken();
ctx.cookies.set('access_token', token, {
maxAge: 1000 * 60 * 60 * 24 * 7, //7일
httpOnly: true,
});
} catch (e) {
ctx.throw(500, e);
}
};
- Postman
테스팅 결과 성공적으로 Set-Cookie 라는 헤더로 토큰이 전달되는 것을 확인하였다.
3. 토큰 검증하기
이제 사용자의 토큰을 확인 후 검증하는 작업이다.
이 작업은 미들웨어를 생성하여 처리하였다.
- src/lib/jwtMiddleware.js
import jwt from 'jsonwebtoken';
import User from '../models/user';
const jwtMiddleware = async (ctx, next) => {
const token = ctx.cookies.get('access_token');
// 토큰이 없는 경우
if (!token) return next();
try {
// eslint-disable-next-line no-undef
const decoded = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = {
_id: decoded._id,
username: decoded.username,
};
// 토큰의 남은 유효기간이 3.5일 미만일 경우 재발급 처리
const now = Math.floor(Date.now() / 1000);
if (decoded.exp - now < 60 * 60 * 24 * 3.5) {
const user = await User.findById(decoded._id);
const token = user.generateToken();
ctx.cookies.set('access_token', token, {
maxAge: 1000 * 60 * 60 * 24 * 7, //7일
httpOnly: true,
});
}
return next();
} catch (e) {
// 토큰 검증 실패
return next();
}
};
export default jwtMiddleware;
사용자의 cookie로부터 token을 받아온다.
해당 token을 디코딩한다. (jsonwebtoken)
디코딩한 정보를 ctx.state에 넣어준다 -> API에서 ctx.state를 통해 디코딩 정보에 접근할 수 있게된다.
이제 이 미들웨어를 app에 적용한다.
- src/main.js
...
import jwtMiddleware from './lib/jwtMiddleware';
// 비구조화 할당을 통해 process.env 내부 값에 대한 레퍼런스 만들기
// eslint-disable-next-line no-undef
const { PORT, MONGO_URI } = process.env;
mongoose
.connect(MONGO_URI)
.then(() => {
console.log('Connected to MongoDB');
})
.catch((e) => {
console.log(e);
});
const app = new Koa();
const router = new Router();
// 라우터 설정
router.use('/api', api.routes()); // api 라우트 적용
// 라우터 적용 전에 bodyParser 적용
app.use(bodyParser());
// 라우터 적용 전에 jwtMiddleware 적용
app.use(jwtMiddleware);
// app 인스턴스에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());
...
이제 check API를 구현한다.
- src/api/auth/auth.ctrl.js
/*
GET /api/auth/check
*/
export const check = async (ctx) => {
// 로그인 상태 확인
const { user } = ctx.state;
if (!user) {
// 로그인 중 아님
ctx.status = 401; // Unauthorized
return;
}
ctx.body = user;
};
export const logout = async (ctx) => {
// 로그아웃
};
- Postman
check API 호출 결과 서버가 성공적으로 응답함을 확인하였다.
'Front-End > React' 카테고리의 다른 글
[React] 블로그 만들기 9 - 로그아웃 구현 , posts에 회원 인증 API 적용 (0) | 2022.03.15 |
---|---|
[React] 블로그 만들기 7 - 로그인 구현 (0) | 2022.03.15 |
[React] 블로그 만들기 6 - 회원가입 구현 (0) | 2022.03.15 |
[React] 블로그 만들기 5 - 페이지네이션 구현 (0) | 2022.03.14 |
[React] 블로그 만들기 4 - Request Body 검증 (0) | 2022.03.14 |