github-blog.png


✍️ Today I Learned


Session


  • HTTPS 프로토콜 서버를 만들고, 클라이언트에서 쿠키에 담긴 세션 정보를 확인해야 한다.

  • 세션 기반 인증은 서버(혹은 DB)에 유저 정보를 담는 인증 방식이다. 이때 웹사이트에서는 로그인을 유지하기 위한 수단으로 쿠키를 사용하며 쿠키에는 서버에서 발급한 세션 아이디를 저장되어 있다.



1-1. 사전 준비




환경 변수 설정

  • Sequelize 스프린트때 해보았기 때문에 별도로 mysql을 node.js에서 어떻게 사용하는지 구조를 뜯어보진 않았다. .env 파일을 내 로컬환경에 알맞게 설정해 주었다.

  • config/config.js 파일에 이미 데이터베이스 이름(database: 'authentication')이 명시되어 있었다. 해당 이름의 DB를 생성해주었다.



데이터베이스 마이그레이션

  • 이 또한 Sequelize 스프린트때 해보았기 때문에 modelsmigrations 폴더 내부의 파일들을 확인하지 않았다.

    npx sequelize-cli db:migrate

    위 sequelize 명령어로 이미 짜여진 DB 스키마를 내 mysql 환경으로 마이그레이션 해주었다.



인증서 발급

  • mkcert라는 프로그램을 이용해서 로컬 환경에서 인증서를 생성했다.


1-2. 서버 구현




쿠키 옵션 & HTTPS 옵션 설정

  • server-session/index.js 파일에서 쿠키 옵션 설정을 해주었다.

    // TODO: express-session 라이브러리를 이용해 쿠키 설정을 해줄 수 있습니다.
    app.use(
      session({
        secret: '@codestates',
        resave: false,
        saveUninitialized: true,
        cookie: {
          domain: 'localhost', // Domain : 쿠키의 Domain 옵션과 서버의 도메인이 일치해야한다.(스프린트는 로컬 환경)
          path: '/', // Path : default값은 '/'이다.
          maxAge: 24 * 6 * 60 * 10000, // maxAge : 해당 초만큼 유지되는 쿠키 유효기간 옵션이다.
          sameSite: 'none', // sameSite : 항상 쿠키를 보내 줄 수 있다.(secure : true 옵션과 함께 써야한다.)
          httpOnly: true, // httpOnly : 'true'이기 때문에, JS에서는 쿠키에 접근이 불가하다.
          secure: true,
        },
      }),
    );
  • 마찬가지로 server-session/index.js 파일에서 HTTPS 옵션 설정을 해주었다.

    // TODO: CORS 설정이 필요합니다. 클라이언트가 어떤 origin인지에 따라 달리 설정할 수 있습니다.
    // 메서드는 GET, POST, OPTIONS를 허용합니다.
    app.use(
      cors({
        origin: 'https://localhost:3000', // 출처(origin)는 클라이언트 주소를 준다.
        method: ['GET', 'POST', 'OPTIONS'], // 메소드는 제시되어 있다.
        credentials: true, // 인증서를 이용한 CORS 옵션.
      }),
    );


[POST] /users/login

  • controller/users/login.js에서 로그인에 관련 기능을 완성하자.

    // 해당 모델의 인스턴스를 models/index.js에서 가져옵니다.
    const { Users } = require('../../models'); // DB
    
    post: async (req, res) => {
      // userInfo는 유저정보가 데이터베이스에 존재하고, 완벽히 일치하는 경우에만 데이터가 존재합니다.
      // 만약 userInfo가 NULL 혹은 빈 객체라면 전달받은 유저정보가 데이터베이스에 존재하는지 확인해 보세요
      const userInfo = await Users.findOne({
        where: { userId: req.body.userId, password: req.body.password },
      });
    
      // TODO: userInfo 결과 존재 여부에 따라 응답을 구현하세요.
      // 결과가 존재하는 경우 세션 객체에 userId가 저장되어야 합니다.
      if (!userInfo) {
        res.json({ message: 'not authorized' });
      } else {
        // HINT: req.session을 사용하세요.
        req.session.save(function () {
          // 세션 객체에 저장하는 방법
          req.session.userId = userInfo.userId;
          res.json({ message: 'ok' });
        });
      }
    };
  • req.session 사용방법은 GitHub: express-session 문서를 통해 확인하였다.



[POST] /users/logout

  • controller/users/logout.js에서 로그아웃에 관련된 기능을 완성하자.

    post: (req, res) => {
      // TODO: 세션 아이디를 통해 고유한 세션 객체에 접근할 수 있습니다.
      // 앞서 로그인시 세션 객체에 저장했던 값이 존재할 경우, 이미 로그인한 상태로 판단할 수 있습니다.
      // 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
    
      if (!req.session.userId) {
        // 로그인 전에 로그아웃 요청
        res.sendStatus(400); // 400번 에러코드
      } else {
        // TODO: 로그아웃 요청은 세션을 삭제하는 과정을 포함해야 합니다.
        req.session.destroy(function () {
          // 세션 객체 파괴
          req.session;
        });
        /* 세션 삭제는 req.session = null 또한 가능한듯하다... 세션 객체를 null값으로 초기화 */
        res.sendStatus(200);
      }
    };


[POST] /users/userinfo

  • controller/userinfo.js에서 마이페이지 관련된 기능을 완성하자.

    const { Users } = require('../../models'); // DB
    
    get: async (req, res) => {
      // TODO: 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
      // HINT: 세션 객체에 담긴 정보가 궁금하다면 req.session을 콘솔로 출력해보세요
    
      if (!req.session.userId) {
        res.status(400).json({ message: 'not authorized' });
      } else {
        // TODO: 데이터베이스에서 로그인한 사용자의 정보를 조회한 후 응답합니다.
        Users.findOne({
          // Users 테이블에서 조회 후
          where: {
            userId: req.session.userId,
          },
        }).then((data) => {
          // 응답에 조회값을 넘겨준다.
          res.status(200).json({ data: data, message: 'ok' });
        });
      }
    };


1-3. 클라이언트 구현




Mypage

  • components/Mypage.js에서 마이페이지 관련된 기능을 완성하자.

  • 서버의 API를 활용하기 위해서는 Axios 라이브러리를 사용해야 한다.

    • Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리이다.

    • 자바스크립트에 내장되어 있는 fetch api와 유사한 기능이지만, url을 제공하는 등 차이점이 있다.

  • Mypage에서는 로그인 이후 userinfo가 보여지며, 로그아웃 버튼이 존재한다.

    import axios from 'axios';
    
    function Mypage(props) {
    	const handleLogout = () => {
    		// TODO: 서버에 로그아웃 요청을 보낸다음 요청이 성공하면 props.logoutHandler를 호출하여 로그인 상태를 업데이트 해야 합니다.
    		axios.post('https://localhost:4000/users/logout', null,  {withCredentials: true})  // url은 서버의 endpoint, data는 null값을 그리고 cors옵션중 Credential값이 'true'이므로 withCredentials 옵션을 준다.
    		.then(()=>{
    			props.logoutHandler(); // post 요청 후 logoutHandler 함수 호출하여 isLogin 상태를 업데이트 한다.
    		});
    
    	/* 생략 */
    }


Login

  • components/Login.js 에서는 loginRequestHandler() 함수를 완성해주면 된다.

    loginRequestHandler() {
    	// TODO: 로그인 요청을 보내세요.
    
    	// 로그인에 성공하면
    	// - props로 전달받은 함수를 호출해, 로그인 상태를 변경하세요.
    	// - GET /users/userinfo 를 통해 사용자 정보를 요청하세요
    
    	//   // 사용자 정보를 받아온 후
    	// - props로 전달받은 함수를 호출해, 사용자 정보를 변경하세요.
    
    	axios.post('https://localhost:4000/users/login',{userId: this.state.username, password: this.state.password}, {withCredentials: true})  // 로그인 요청
    	.then(()=>{
    		this.props.loginHandler();  // 로그인 요청에 성공하면, 전달받은 함수를 호출해 로그인 상태를 변경
    		return axios.get('https://localhost:4000/users/userinfo', {withCredentials: true})  // GET /users/userinfo 를 통해 사용자 정보를 요청
    	})
    	.then((data)=>{  // 사용자 정보를 받아온 후
    		const {userId, email} = data.data.data; // 사용자 정보는 controller/userinfo.js에서 data: data 형식으로 보냈다.
    		this.props.setUserInfo({  // props로 전달받은 함수를 호출 (상태변경 함수)
    			userId,
    			email
    		});
    	});
    }


🤔 Understanding

  • 어렵다 어렵다 어렵다…

  • axios 의 등장.. node.js에서 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리 이다. 앞으로 자주 볼 듯 하다…

    반복해서 해봐야 할 듯 하다…