티스토리 뷰

Tokens 과제

JWT 생성 및 검증을 도와주는 헬퍼

require('dotenv').config();
//dotenv 패키지를 사용하여 환경 변수를 로드
const { sign, verify } = require('jsonwebtoken');
//Node.js 서버에서 JWT(Json Web Token)을 생성하고 검증하는 기능을 구현한 모듈
module.exports = {
  generateToken: (user, checkedKeepLogin) => {
    const payload = {
      id: user.id,
      email: user.email,
    };
    let result = {
      accessToken: sign(payload, process.env.ACCESS_SECRET, {
        expiresIn: '1d', // 1일간 유효한 토큰을 발행합니다.
      }),
    };

    if (checkedKeepLogin) {
      result.refreshToken = sign(payload, process.env.REFRESH_SECRET, {
        expiresIn: '7d', // 일주일간 유효한 토큰을 발행합니다.
      });
    }
    return result;
  },
//generateToken 함수는 사용자 정보를 전달받아 access token을 발행하고,
// keep login 옵션이 선택되어 있으면 refresh token도 발행
//access token은 1일간 유효하며, refresh token은 7일간 유효
  verifyToken: (type, token) => {
    let secretKey, decoded;
    switch (type) {
      case 'access':
        secretKey = process.env.ACCESS_SECRET;
        break;
      case 'refresh':
        secretKey = process.env.REFRESH_SECRET;
        break;
      default:
        return null;
    }

    try {
      decoded = verify(token, secretKey);
    } catch (err) {
      console.log(`JWT Error: ${err.message}`);
      return null;
    }
    return decoded;
  },
};
//verifyToken 함수는 token과 token의 타입을 전달받아 해당 token이 유효한지 검증
//access token인지 refresh token인지에 따라 검증에 사용되는 비밀키가 달라지며,
// 유효하지 않은 token이거나 검증에 실패한 경우 null을 반환

JWT는 클라이언트와 서버 간의 인증을 간편하게 처리하기 위한 방식 중 하나이다.

서버에서 발행한 token은 클라이언트로 전달되어 이후 요청에서 인증에 사용된다.

이 모듈은 Node.js 서버에서 JWT를 사용할 때 유용

 

환경 변수 설정

.env 

 

refresh Token은 access Token을 재발급하기 위한 용도로 쓰면 된다.

 

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;
  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],
  };

 
   // 로그인 성공 시에는 쿠키에 JWT를 담아 전송
   //로그인 상태가 유지되어야 한다면 Access Token과 Refresh Token 모두 보낸다.
   //Access Token은 Session 쿠키로 Refresh Token은 Persistent Cookie로 보내야합니다.
   //Access Token의 쿠키 아이디는 access_jwt, Refresh Token의 쿠키 아이디는 refresh_jwt로 작성하세요.
   //로그인 상태가 유지되길 원하지 않는다면 Access Token만 보낸다.
  
  if (!userInfo.id) {
    res.status(401).send("Not Authorized")
  } else{
   const {accessToken , refreshToken} = generateToken(userInfo , checkedKeepLogin)
   //generateToken 함수를 사용하여 accessToken과 refreshToken을 생성
   const cookieOptions = {
    domain: 'localhost',
    path: '/',
    httpOnly: true,
    sameSite: 'none',
    secure: true,
  }

   res.cookie('access_jwt',accessToken,cookieOptions)

   if(checkedKeepLogin){
    cookieOptions.maxAge = 1000* 60 * 60 * 24 * 7
    res.cookie('refresh_jwt',refreshToken,cookieOptions)
   }
   //checkedKeepLogin이 true이면, refreshToken도 쿠키에 담아 클라이언트로 전송
   //마지막으로, 생성한 accessToken과 refreshToken을 각각 access_jwt와 refresh_jwt라는 쿠키에 담아 클라이언트로 전송
   res.redirect('/userinfo')
   //그인 상태를 유지하기 위해서는 res.redirect()를 사용하여 /userinfo로 리다이렉트
  }
};

userInfo.js

const { USER_DATA } = require('../../db/data');
//이 배열은 데이터베이스에서 가져오는 것을 모방하는 더미 데이터로 구성
const { verifyToken, generateToken } = require('../helper/tokenFunctions');
//tokenFunctions.js 파일에서 verifyToken과 generateToken 함수를 가져온다.
//이 함수들은 JWT 생성과 검증을 담당
module.exports = async (req, res) => {
  /*
   * TODO: 토큰 검증 여부에 따라 유저 정보를 전달하는 로직을 구현하세요.
   *
   * Access Token에 대한 검증이 성공하면 복호화된 payload를 이용하여 USER_DATA에서 해당하는 유저를 조회할 수 있습니다.
   * Access Token이 만료되었다면 Refresh Token을 검증해 Access Token을 재발급하여야 합니다.
   * Access Token과 Refresh Token 모두 만료되었다면 상태 코드 401을 보내야합니다.
   */
  console.log(req.cookies)
  // req.cookies 객체를 통해 요청에 포함된 쿠키들을 확인
  const {access_jwt , refresh_jwt} = req.cookies
  const accessPayload = verifyToken('access',access_jwt)
  //verifyToken 함수를 사용하여 access_jwt를 검증

  if(accessPayload){ //검증이 제대로 됨
 
    const userInfo = {
      ...USER_DATA.filter((user) => user.id === accessPayload.id)[0],
    };
    console.log(userInfo)
    // //검증에 성공하면 토큰의 페이로드에서 사용자의 ID를 추출하여 
    //USER_DATA 배열에서 해당 사용자를 찾아 정보를 가져온다.
    //해당 사용자의 정보를 반환하고, 만약 ID가 없을 경우 401 Unauthorized 응답을 반환 

    if(!userInfo.id){
      res.status(401).send('Not Authorized');
    }

    delete userInfo.password
    res.json(userInfo)

  }
  else if (refresh_jwt){
  //access_jwt 검증이 실패하면 refresh_jwt 쿠키를 검증
    const refreshPayload = verifyToken('refresh',refresh_jwt)

    if(!refreshPayload){
      res.status(401).send('Not Authorized');
    }

    const userInfo = {
      ...USER_DATA.filter((user) => user.id === refreshPayload.id)[0],
    };

    if(!userInfo.id){
      res.status(401).send('Not Authorized');
    }

    const {accessToken} = generateToken(userInfo)
    //이 검증에 성공하면, generateToken 함수를 사용하여 새로운 Access Token을 생성
  
    const cookieOptions = {
      domain: 'localhost',
      path: '/',
      httpOnly: true,
      sameSite: 'none',
      secure: true,
    }

    res.cookie('access_jwt',accessToken,cookieOptions)
    res.redirect('/userInfo');
    //이를 access_jwt 쿠키에 저장하고 /userInfo로 리다이렉


    }
    else{
    res.status(401).send('Not Authorized'); 

  }
};

    const userInfo = {
      ...USER_DATA.filter((user) => user.id === refreshPayload.id)[0],
    }; 에서

accessPayload.id라고 써서 오류가 떳다.

Cannot read properties of null (reading 'id')라고 떠서 

원인을 찾는라 시간이 많이 걸렸다...ㅜㅜ

 

logiout.js

module.exports = (req, res) => {
  /*
   * 로그아웃 요청은 쿠키에 저장된 토큰을 삭제하는 과정을 포함
   *
   * cookie-parser의 clearCookie('쿠키의 키') 메서드로 해당 키를 가진 쿠키를 삭제
   */
  const cookieOptions = {
    domain: 'localhost',
    path: '/',
    httpOnly: true,
    sameSite: 'none',
    secure: true,
  }
  res.clearCookie('access_jwt',cookieOptions);
  res.clearCookie('refresh_jwt',cookieOptions);
  res.status(205).send("logout")
};

 

1. GitHub에 내 앱 등록

https://www.oauth.com/oauth2-servers/accessing-data/create-an-application/

 

Create an Application - OAuth 2.0 Simplified

Before we can begin, we'll need to create an application on GitHub in order to get a client ID and client secret. On GitHub.com, from the "Settings" page,

www.oauth.com

*Authorization callback URL:인증 과정이 끝난 후 리디렉션을 통해 다시 내 앱으로 이동하는 원리

 

소셜 로그인 로직 플로우

client.js 수정

Login.js

export default function Login() {
 // const CLIENT_ID = process.env.REACT_APP_CLIENT_ID;
 const CLIENT_ID = '7ab4d2eec61d26f818b4'
  
  //FILL_ME_IN은 실제로 사용할 값을 대체해야 하는 placeholder 문자열
  //이 값을 GitHub OAuth 애플리케이션에서 발급 받은 클라이언트 ID 값으로 대체
 // FILL_ME_IN 대신에 발급 받은 클라이언트 ID 값을 입력

  const loginRequestHandler = (type) => {
    //GitHub로부터 사용자 인증을 위해 GitHub로 이동
    // OAuth 인증이 완료되면 authorization code와 함께 callback url로 리디렉션
    // 참고: https://docs.github.com/en/free-pro-team@latest/developers/apps/identifying-and-authorizing-users-for-github-apps
    return window.location.assign(
      `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}`
    );
  };

GitHub OAuth 인증을 위한 클라이언트 ID 값을 사용하여 사용자 인증 요청을 처리

loginRequestHandler 함수: 이 함수는 type 매개변수를 입력 받으며, GitHub OAuth 인증 절차를 시작하기 위해 클라이언트 ID 값을 사용하여 GitHub 로그인 페이지로 이동

GitHub에서 사용자가 인증을 완료하면 authorization code가 함께 callback url로 전송되고,

이를 이용하여 사용자 인증을 마무리

window.location.assign() 메서드: 이 메서드는 현재 창의 URL을 다른 URL로 변경하여 지정된 URL로 이동하는 역할

이 코드는 사용자가 GitHub OAuth 인증 절차를 시작하기 위해 https://github.com/login/oauth/authorize로 이동

 

 

server.js에서

callback.js는

authorization code를 이용해 access token을 발급받기 위한 post 요청을 보낸다.

 

App.js

import './App.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Login from './pages/Login';
import Mypage from './pages/Mypage';
import { useEffect, useState } from 'react';
import axios from 'axios';

function App() {
  const [isLogin, setIsLogin] = useState(false);
  const [accessToken, setAccessToken] = useState('');

  const getAccessToken = async (authorizationCode) => {
    
    // \서버의 /callback 엔드포인트로 Authorization Code를 보내주고 Access Token을 받아준다.
    axios.post('http://localhost:4000/callback', {authorizationCode })
    .then(res=> {
      setAccessToken(res.data.accessToken);
      setIsLogin(true);
    } );
  }; 


  useEffect(() => {
    const url = new URL(window.location.href);
    const authorizationCode = url.searchParams.get('code');
    console.log(authorizationCode)
    if (authorizationCode) {
      getAccessToken(authorizationCode);
    }
  }, []);
  
  return (
    <BrowserRouter>
      <div className='main'>
        <div className='container'>
          <Routes>
            <Route
              path='/'
              element={
                isLogin ? <Mypage accessToken={accessToken} 
                 setIsLogin={setIsLogin}
                 setAcessToken={setAccessToken}
                 /> : <Login />
              }
            />
          </Routes>
        </div>
      </div>
    </BrowserRouter>
  );
}

export default App;

서버쪽 userInfo.js는 클라이언트에서 전달받은 access token를 이용해 사용자의 정보를 가져온다.

 

Mypage.js

export default function Mypage({ accessToken, setIsLogin,setAcessToken }) {
  const [githubUser, setGithubUser] = useState(null);
  const [serverResource, setServerResource] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  const logoutHandler =() => {
    axios
    .delete("http://localhost:4000/logout",{data:{accessToken}})
    .then((res) => {
    //  로그아웃에 성공했다면 App의 상태를 변경하세요.
    
    setIsLogin(false);
    setAcessToken(null);
    setGithubUser(null);
    setServerResource(null);
    })  
  };

  
useEffect(() => {
  axios.post('http://localhost:4000/userinfo', { accessToken })
    .then(res => {
      const { githubUserData, serverResource } = res.data
      setGithubUser(githubUserData);
      setServerResource(serverResource);
      setIsLoading(false);
    })
}, []);

  return (
    <>
      <div className='left-box'>
        {!isLoading && (
          <span>
            {`${githubUser.login}`}님,
            <p>반갑습니다!</p>
          </span>
        )}
      </div>
      <div className='right-box'>
        <div className='input-field'>
          {isLoading ? (
            <Loading />
          ) : (
            <User
              githubUser={githubUser}
              serverResource={serverResource}
              logoutHandler={logoutHandler}
            />
          )}
        </div>
      </div>
    </>
  );
}

 

 

서버쪽 logout.js는 클라이언트에서 전달받은 access token를 이용해 사용자의 권한 부여를 취소

 

 

 

'codestates > section3' 카테고리의 다른 글

cookie와 session 튜토리얼  (0) 2023.03.08
과제 - 웹 표준 & 접근성 개선  (0) 2023.03.02
알고리즘rotatedArraySearch  (0) 2023.03.02
알고리즘power  (0) 2023.02.28
SEO에 영향을 미치는 요소  (0) 2023.02.28
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함