공부하기/React

[React] 블로그 만들기 9 - 로그아웃 구현 , posts에 회원 인증 API 적용

다섯자두 2022. 3. 15. 22:24

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


 

 

 

이제 마지막 회원 인증 관련 API인 로그아웃 구현이다.

이는 매우 간단한 작업으로, 쿠키를 지워주기만 하면 되었다.

 

1. 로그아웃 구현

- src/api/auth/auth.ctrl.js

/*
  POST /api/auth/logout
*/
export const logout = async (ctx) => {
  // 로그아웃
  ctx.cookies.set('access_token');
  ctx.status = 204; // No content
};

 

- Postman

로그아웃 테스팅

테스팅 결과 Set-Cookie의 access_token이 비워졌음을 확인하였다.

 


이렇게 회원 인증 시스템 작성은 끝이 났다. 

이제 기존에 구현했던 posts API에 회원 인증 시스템을 도입해보자 !

사용자가 로그인을 해야만 포스트를 작성할 수 있고, 삭제와 수정은 오직 작성자만 할 수 있도록 구현한다.

 

1. Post 스키마 수정

일단 각 포스트를 어떤 사용자가 작성했는지 알 수 있도록 기존 Post 스키마를 수정한다.

 

- src/models/post.js

import mongoose from 'mongoose';

const { Schema } = mongoose;

const PostSchema = new Schema({
  title: String,
  body: String,
  tags: [String], // 문자열로 이루어진 배열
  publishedDate: {
    type: Date,
    default: Date.now, // 현재 날짜를 기본값으로 설정
  },
  user: {
    _id: mongoose.Types.ObjectId,
    username: String,
  },
});

const Post = mongoose.model('post', PostSchema);
export default Post;

 

작성자 정보를 담을 user 필드를 추가했다.

 

이전에 생성한 포스트 데이터 (#1~#40) 들은 더이상 유효하지 않으므로 전부 삭제 처리하였다.

 

2. 로그인시에만 API 사용

 

checkLoggedIn 이라는 미들웨어를 만들어, 로그인시에만 글쓰기, 수정, 삭제가 가능하도록 구현한다.

 

- src/lib/checkLoggedIn.js

const checkLoggedIn = (ctx, next) => {
  if (!ctx.state.user) {
    ctx.status = 401; //Unauthorized
    return;
  }
  return next();
};

export default checkLoggedIn;

 

로그인 상태를 확인하고, 로그인된 경우에만 다음 미들웨어를 실행한다.

이를 posts 라우터에 적용한다.

 

- src/api/posts/index.js

import Router from 'koa-router';
import * as postsCtrl from './posts.ctrl';
import checkLoggedIn from '../../lib/checkLoggedIn';

const posts = new Router();

posts.get('/', postsCtrl.list);
posts.post('/', checkLoggedIn, postsCtrl.write);

const post = new Router(); // /api/posts/:id

post.get('/', postsCtrl.read);
post.delete('/:id', checkLoggedIn, postsCtrl.remove);
post.patch('/:id', checkLoggedIn, postsCtrl.update);
//module.exports = posts;

posts.use('/:id', postsCtrl.checkObjectId, post.routes());

export default posts;

 

3. 포스트 작성시 사용자 정보 넣기

 

이제 포스트를 작성할 때 사용자 정보를 넣어 데이터베이스에 저장하도록 구현해야한다.

 

- src/api/posts/posts.ctrl.js

/*
POST /api/posts
{
  title: "제목",
  body: "내용",
  tags: ['태그1','태그2']
}
*/
export const write = async (ctx) => {
  const schema = Joi.object().keys({
    // 객체가 다음 필드를 가지고 있음을 검증
    title: Joi.string().required(), // required가 있으면 필수항목을 의미
    body: Joi.string().required(),
    tags: Joi.array().items(Joi.string()).required(), // 문자열로 이루어진 배열임을 뜻함
  });

  // 검증하고 나서 검증 실패인 경우 에러 처리한다.
  const result = schema.validate(ctx.request.body);
  if (result.error) {
    ctx.status = 400; //Bad Request
    ctx.body = result.error;
    return;
  }

  const { title, body, tags } = ctx.request.body;
  const post = new Post({
    title,
    body,
    tags,
    /* 추가한 부분 */
    user: ctx.state.user,
 
  });

  try {
    await post.save();
    ctx.body = post;
  } catch (e) {
    ctx.throw(500, e);
  }
};

 

- Postman

포스트 작성시 사용자 정보 넣기 테스팅

 

테스팅 결과, 성공적으로 포스트에 user의 정보가 담겨 저장되는 것을 확인하였다.

 

4. 포스트 수정 및 삭제 시 권한 확인하기

 

이제 마지막으로 포스트 작성자에 한해서만 포스트를 수정/삭제가 가능하도록 구현한다.

이 작업을 미들웨어로 처리하고 싶다면 id로 포스트를 조회하는 작업 또한 미들웨어로 해주어야 한다.

따라서 기존의 checkObjectId(Id 유효성 검사 미들웨어)를 getPostById로 바꾸고, 해당 미들웨어에서 id로 포스트를 찾은 후 ctx.state에 담는다.

 

- src/api/posts/posts.ctrl.js

export const getPostById = async (ctx, next) => {
  const { id } = ctx.params;
  if (!ObjectId.isValid(id)) {
    // id의 유효성 검사
    ctx.status = 400; // Bad Request
    return;
  }

  try {
    const post = await Post.findById(id);
    // 포스트가 존재하지 않을 때
    if (!post) {
      ctx.status = 404; // Not Found
      return;
    }

    ctx.state.post = post;
    return next();
  } catch (e) {
    ctx.throw(500, e);
  }
};

 

이후 posts 라우터에도 이를 반영한다.

 

- src/api/posts/index.js

import Router from 'koa-router';
import * as postsCtrl from './posts.ctrl';
import checkLoggedIn from '../../lib/checkLoggedIn';

const posts = new Router();

posts.get('/', postsCtrl.list);
posts.post('/', checkLoggedIn, postsCtrl.write);

const post = new Router(); // /api/posts/:id

post.get('/', postsCtrl.read);
post.delete('/:id', checkLoggedIn, postsCtrl.remove);
post.patch('/:id', checkLoggedIn, postsCtrl.update);
//module.exports = posts;

posts.use('/:id', postsCtrl.getPostById, post.routes());
/* 반영된 부분 */
export default posts;

 

그 다음, read 함수 내에서 id로 포스트를 찾는 코드를 간소화해준다.

 

- src/api/posts/posts.ctrl.js

/*
GET /api/posts/:id
*/
export const read = async (ctx) => {
  ctx.body = ctx.state.post;
};

 


이제 checkOwnPost 라는 미들웨어를 만든다. 이 미들웨어가 현재 수정/삭제 하려는 포스트가 사용자 본인의 포스트인지 확인하는 역할을 할 것이다.

 

- src/api/posts/posts.ctrl.js

export const checkOwnPost = (ctx, next) => {
  const {user, post} = ctx.state;
  if( user._id !== post.user._id.toString()) {
    ctx.status = 403;
    return;
  }
  return next();
}

 

이제 이 미들웨어를 수정 및 삭제 API에 적용해주면 된다.

 

- src/api/posts/index.js

post.delete('/:id', checkLoggedIn, postsCtrl.checkOwnPost, postsCtrl.remove);
post.patch('/:id', checkLoggedIn, postsCtrl.checkOwnPost, postsCtrl.update);

 

- Postman

테스팅을 위해 username:user1 으로 회원가입 후, 포스트를 하나 생성했다.

이후 로그아웃하고, username:subbni로 로그인 한 다음 위의 포스트를 삭제하는 요청을 보내보았다.

근데 띠용 .. Method Not Allowed가 떴다.

뭐가 잘못됐나 .. 하고 코드를 확인해보던 중, 

 

- src/api/posts/index.js 에서

post.get과 post.delete의 경로를 './:id'에서 '/'로 바꾸는 것을 빼먹은 것을 확인했다.

 

- src/api/posts/index.js

import Router from 'koa-router';
import * as postsCtrl from './posts.ctrl';
import checkLoggedIn from '../../lib/checkLoggedIn';

const posts = new Router();

posts.get('/', postsCtrl.list);
posts.post('/', checkLoggedIn, postsCtrl.write);

const post = new Router(); // /api/posts/:id

post.get('/', postsCtrl.read);
post.delete('/', checkLoggedIn, postsCtrl.checkOwnPost, postsCtrl.remove);
post.patch('/', checkLoggedIn, postsCtrl.checkOwnPost, postsCtrl.update);
//module.exports = posts;

posts.use('/:id', postsCtrl.getPostById, post.routes());

export default posts;

 

 

 

수정 후 다시 요청을 보내자 성공적으로 Forbidden이 뜨는 것을 확인하였다.