[React] 블로그 만들기 8 - 토큰 발급 및 검증

2022. 3. 15. 17:21·공부하기/React

다음은 책 리액트를 다루는 기술을 읽고 공부한 내용을 바탕으로 작성된 글입니다.


 

이제 클라이언트에서 사용자 로그인 정보를 지닐 수 있도록 토큰을 발급하자.

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 호출 결과 서버가 성공적으로 응답함을 확인하였다.

저작자표시 (새창열림)

'공부하기 > React' 카테고리의 다른 글

[React] 블로그 만들기 9 - 로그아웃 구현 , posts에 회원 인증 API 적용  (1) 2022.03.15
[React] 블로그 만들기 7 - 로그인 구현  (0) 2022.03.15
[React] 블로그 만들기 6 - 회원가입 구현  (1) 2022.03.15
[React] 블로그 만들기 5 - 페이지네이션 구현  (0) 2022.03.14
[React] 블로그 만들기 4 - Request Body 검증  (0) 2022.03.14
'공부하기/React' 카테고리의 다른 글
  • [React] 블로그 만들기 9 - 로그아웃 구현 , posts에 회원 인증 API 적용
  • [React] 블로그 만들기 7 - 로그인 구현
  • [React] 블로그 만들기 6 - 회원가입 구현
  • [React] 블로그 만들기 5 - 페이지네이션 구현
다섯자두
다섯자두
All I need is 💻 , ☕️ and a dash of luck
  • 다섯자두
    subbni
    다섯자두
  • 전체
    오늘
    어제
    • 전체 글 (88) N
      • 개발 이야기 (0)
      • 만들어보기 (17)
        • FromBookToBook (5)
        • Spring (5)
        • Node.js & React (3)
        • TroubleShooting (4)
      • 공부하기 (71) N
        • Network (3)
        • Cloud (1)
        • Database (5)
        • Java (13)
        • Javascript (0)
        • Spring (9)
        • React (18)
        • Algorithm (8)
        • 자료구조 (7)
        • ETC (7) N
      • 회고 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • velog
  • 공지사항

  • 인기 글

  • 태그

    aws
    서명알고리즘
    실시간 데이터 전송 기술
    HTTP
    SSE
    Spring
    mysql
    티스토리챌린지
    프로젝트
    재시도 로직
    pdf 자동 다운로드
    알고리즘
    알림 기능
    Express
    outbox 패턴
    network
    Til
    최단거리
    JPA
    outbox
    java
    자료구조
    springboot
    로그인
    SQS
    redis
    Database
    오블완
    SQL
    pdf 프리뷰 실패
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
다섯자두
[React] 블로그 만들기 8 - 토큰 발급 및 검증
상단으로

티스토리툴바