Jieunny의 블로그

S3) Unit 7. [실습] Token & Cookie 사용해서 로그인 기능 구현하기 본문

CodeStates/Training

S3) Unit 7. [실습] Token & Cookie 사용해서 로그인 기능 구현하기

Jieunny 2023. 3. 9. 11:52

📣  Cookie와 Token을 사용해서 로그인 기능 구현하기

- Client 부분은 Cookie 게시물과 같으므로 Server 부분만 설명.

 

𝟭. 서버 파일 구조

controllers

    ㄴ helper

        ㄴ tokenFunctions.js : 토큰 생성, 토큰 검증 함수 구현 

    ㄴ users

        ㄴ login.js : server의 login controller 구현

            1. request로 받은 id, password와 일치하는 정보가 DB에 있는지 확인

            2. 없으면 요청을 거절, 있으면 필요한 데이터를 담은 두 종류의 토큰 생성(access, refresh)

            3. request로 받은 checkedLoginKeep 여부 확인 

            4. true인 경우 accessToken, refreshToken 둘 다 쿠키로 설정하고, false인 경우 accessToken만 쿠키로 설정

            5. 클라이언트에 바로 응답을 보내지 않고 /userInfo로 리다이렉트

 

        ㄴ userInfo.js : server의 userinfo controller 구현

            1. 쿠키에 accessToken이 있으면 검증, 검증 되면 토큰에 담긴 정보를 이용해 DB에서 정보 조회 후 응답으로 전달

            2. accessToken이 검증되지 않으면 refreshToken 검증, 검증 되면 토큰에 담긴 정보를 이용해 DB에서 정보 조회 후 응답으로

전달, refreshToken을 이용해 accessToken 재발급 해서 쿠키에 담고 정보와 함께 응답으로 전달

            3. refreshToken도 검증되지 않았다면 요청 거절

            4. accessToken, refreshToken이 모두 존재하지 않는다면 요청 거절

 

    ㄴ logout.js : server의 userinfo controller 구현

            1. 클라이언트에 저장된 쿠키의 값을 초기화

 

ㄴ .env : 환경 변수 설정


𝟮. 코드 구현

📌 login.js

const { USER_DATA } = require('../../db/data');
// JWT는 generateToken으로 생성할 수 있습니다. 먼저 tokenFunctions에 작성된 여러 메서드들의 역할을 파악하세요.
const { generateToken } = require('../helper/tokenFunctions');

module.exports = async (req, res) => {
  const { userId, password } = req.body.loginInfo;
  // 입력한 id, password 받아오기
  const { checkedKeepLogin } = req.body;
  // 로그인 유지에 체크했는지 여부 받아오기
  // checkedKeepLogin이 false라면 Access Token만 보내야합니다.
  // checkedKeepLogin이 true라면 Access Token과 Refresh Token을 함께 보내야합니다.
  const userInfo = {
    ...USER_DATA.filter((user) => user.userId === userId && user.password === password)[0],
  // 입력한 로그인 정보와 같은 정보를 가진 유저 정보만 가져오기
  };

  const cookiesOption = {
  // 쿠키와 함께 전달해줄 쿠키 옵션 선언
    domain: 'localhost',
    path: '/',
    httpOnly: true,
    sameSite: 'none',
    secure: true,
  }

  if(userInfo.id === undefined){
    // 유저 정보가 없다면 -> 로그인 실패했을 때
    res.status(401).send('Not Authorized');
  } else {
  // 로그인 성공했을 때
      const accessToken = generateToken(userInfo, checkedKeepLogin).accessToken;
      const refreshToken = generateToken(userInfo, checkedKeepLogin).refreshToken;
      // 토큰 두 종류 발급받기

      if(checkedKeepLogin === true) {
        // 로그인 유지해야 하는 경우 쿠키 옵션에 영속성 주기
        cookiesOption.maxAge = 1000 * 60 * 30
        cookiesOption.expires = new Date(Date.now() + (1000 * 60 * 30) )

        res.cookie('access_jwt', accessToken, cookiesOption);
        res.cookie('refresh_jwt', refreshToken, cookiesOption);
        // 로그인 유지해야 하므로 두 종류 쿠키 모두 응답으로 전달

      } else {
      // 로그인 유지 안하므로 accessToken이 담긴 쿠키만 전달
        res.cookie('access_jwt', accessToken, cookiesOption);
      }
    }

  res.redirect("/userinfo");
  // userInfo로 리다이렉션(요청 다시 보내기)
};

 

📌 userInfo.js

const { USER_DATA } = require('../../db/data');
// JWT는 verifyToken으로 검증할 수 있습니다. 먼저 tokenFunctions에 작성된 여러 메서드들의 역할을 파악하세요.
const { verifyToken, generateToken } = require('../helper/tokenFunctions');

module.exports = async (req, res) => {
  const accessToken = req.cookies.access_jwt;
  const refreshToken = req.cookies.refresh_jwt;
  // 생성한 쿠키(토큰이 담김) 받아오기

  if(accessToken){
    // 액세스 토큰 있으면
    if(verifyToken('access', accessToken)===null){
      // 액세스 토큰 검증 -> 검증 안됨.
      if(verifyToken('refresh', refreshToken)===null){
        // 리프레시 토큰 검증 -> 검증 안됨.
        res.status(401).send('Not Authorized');
        // 로그인 실패!
      }
      else {
        //리프레시 토큰 검증 됨.
        const cookiesOption = {
        // 액세스토큰 재발급 시 필요한 쿠키 옵션 선언
          domain: 'localhost',
          path: '/',
          httpOnly: true, 
          sameSite: 'none',
          secure: true,
        }

        const decoded = verifyToken('refresh', refreshToken);
        // 리프레시 토큰 검증으로 복호화하기 -> 필요한 정보 가져오기
        const findUser = {
          ...USER_DATA.filter((user) => user.id === decoded.id)[0],
          // DB에서 일치하는 정보를 가진 요소만 받아오기
        };
        let payload = {
        // 액세스 토큰 발급시 필요한 정보 선언(아까 뽑아온 유저 정보 이용)
          id: findUser.id,
          userId: findUser.userId,
          password: findUser.password,
          email: findUser.email,
          name: findUser.name,
          position: findUser.position,
          location: findUser.location,
          bio: findUser.bio,
        }
        let checkedKeepLogin = req.checkedKeepLogin;
        
        const accessToken = generateToken(payload, checkedKeepLogin).accessToken;
        // 액세스 토큰 재발급
        res.cookie('access_jwt', accessToken, cookiesOption);
        // 재발급한 토큰 쿠키로 보내주기
        delete findUser.password;
        // 응답으로 유저 정보 보내기 전에 민감한 정보인 password 삭제
        res.send(findUser);
  
      }
    } else {
      // 액세스 토큰 검증 됨.
      const decoded = verifyToken('access', accessToken);
      // 액세스 토큰 검증으로 복호화 하기
      const findUser = {
        ...USER_DATA.filter((user) => user.id === decoded.id)[0],
      };
      delete findUser.password;
      res.send(findUser);
    } 
  }
  else{
      // 액세스 토큰 없음.
      if(!refreshToken) res.status(401).send('Not Authorized');
      // 리프레시 토큰도 없음 -> 로그인 실패!
  }
};

 

📌 logout.js

module.exports = (req, res) => {
  const cookiesOption = {
  // 삭제할 쿠키 옵션 선언
    domain: 'localhost',
    path: '/',
    httpOnly: true,
    sameSite: 'none',
    secure: true,
  }

  const refreshToken = req.cookies.refreshToken;
  // 리프레시 토큰이 있으면 얘도 같이 삭제해주어야 하므로 받아오기
  res.status(205).clearCookie('access_jwt', cookiesOption).send("logout");
  // 액세스 토큰이 담긴 쿠키 삭제
  if(refreshToken){
  // 리프레시 토큰이 있다면
    res.status(205).clearCookie('refresh_jwt', cookiesOption).send("logout");
    // 리프레시 토큰이 담긴 쿠키도 삭제
  }
};

𝟯. 시연화면