# 과거 "모두의 개발"의 로그인과 인증 방식(비밀번호, 토큰) 로그인과 인증은 웹 어플리케이션을 구현하는 시작점입니다.(물론 요구하지 않는 사이트도 많습니다) 어떤 로그인 방식을 구현할지 고민하고 구현하는것은 많은 예제가 있고 그 중 하나의 방식을 이해하고 적용하는 것은 정말 중요한 일이라 생각합니다. 이 글은 "모두의 개발" 사이트가 어떤방식의 로그인을 가지고 있었고 어떻게 변경했으며 어떤 문제가 발생했고 어떻게 구현되어 있는지를 이야기해보는 글입니다. ## 기본적인 구조 과거에도 지금도 모두의 개발의 클라이언트 - 서버 구조는 동일하며 동일한 서버 아키텍쳐를 가지고 있습니다. ![img](https://i.imgur.com/FCBpSVe.png) <p style="font-size: 14px; color: grey">*이미지 좌측의 client단은 기존의 csr - gitpages 에서 ssr - vercel로 변경되었습니다.</p> 회원가입, 로그인, 인증과 관련된 모든 로직은 AuthServer에서 동작하여 user정보를 DB에 저장합니다. 만약 ApiServer를 향하는 요청 중 인증이 필요한 요청이 있다면 gateway는 nginx의 auth_request 기능을 통해 AuthServer를 거쳐 인증을 받게끔 합니다. ```nginx server { location /api { /** auth를 통해야하는 path */ auth_request /authoization; /** auth 서버 path */ /** 아래는 토큰의 인증정보(payload)를 cusom-header에 담아 보내는 로직입니다 */ auth_request_set $user $upstream_http_x_user; proxy_set_header x-user $user; proxy_pass http://apiserver/api; } } ``` 차후 서버 구조는 더 자세히 설명할 예정이고 지금은 모든 관련로직을 AuthServer에서 동작함과 구조가 위와 같다는 것을 알면 이해에 도움이 될 것입니다. ## 과거 "모두의 개발"의 로직 지금과 같은 형태를 이루기 전 "모두의 개발"은 아이디, 비밀번호를 통해 로그인하고 이를 통해 access_token, refersh_token, 두 개의 jwt(json web token)을 발급받는 방식을 취했습니다. 발급받은 토큰은 각각 redux store(전역 상태)와 cookie에 저장되어 인증 필요한 요청에 사용되었습니다. 이 과정은 **아이디, 비밀번호 방식으로 로그인하고 토큰을 전역적으로 저장하려 할 때 도움이 될 것입니다.** ### 회원가입 회원가입시 클라이언트는 아이디와 비밀번호를 보내고 서버는 해당 값을 DB에 저장하였습니다. 과정을 표현하면 아래와 같습니다. ![](https://i.imgur.com/BIgVynK.png) 암호화는 cryto 모듈을 이용하였고 salt를 활용하였습니다. 적어도 이 과정에서 **단방향 암호화**, **Salt** 에 대한 개념을 잡고 가는것이 좋습니다. 아래 글을 참고해보세요. [패스워드의 암호화와 저장](https://st-lab.tistory.com/100) ```typescript /** 암호화된 비밀번호를 반환하는 비동기 함수(프로미스) 예시 */ const createPassword: tCreatePassword = (pw) => ( new Promise((resolve, reject) => { try { const salt = crypto.randomBytes(64).toString('base64'); crypto.pbkdf2(pw, salt, 9999, 64, 'sha512', (err, key) => { if (err) reject(({ code: 500, err: 'Hashing Error' })); resolve({ hashpassword: key.toString('base64'), salt }); }); } catch(err) { console.log('ERROR LOG(LOGIC)', err); reject({ code: 500, msg: 'Crypto Error' }); } }) ); ``` ### 로그인과 토큰 관리 "모두의 개발"의 로그인은 아이디, 비밀번호로 유저를 확인하고 토큰을 발급하는 방식입니다. jwt에 대해서 이해하고 jwt를 어떻게 보관하면 좋은지 이해하면 좋습니다. 토큰은 cookie나 localStorage등에 저장되어 서버로의 요청을 할 때 포함시켜 보내집니다. 방식에 따라 보안성이나 클라이언트와 서버의 로직이 바뀔 수 있기 때문에 적절한 방식을 취하는 것이 좋습니다. [JWT 토큰 인증이란?](https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC) [JWT 이해 및 적용](https://backend-intro.vlpt.us/4/) [쉽게 알아보는 서버인증2(Access Token + Refresh Token)](https://tansfil.tistory.com/59) "모두의 개발"은 access_token은 redux store에만 저장하고 refresh_token을 cookie에 저장합니다. access_token을 redux store에 저장하기 때문에 새로고침과 같이 페이지를 새로 그리면 store는 초기화됩니다. 따라서 새로 접속시마다 refresh_token을 사용해서 서버에 요청을 하여 새로운 access_token을 받아옵니다. 이 방법은 매번 새로 접속시 마다 로그인을 하므로 서버 부하는 늘지만 access_token이 페이지 단에서만 유지되므로 토큰이 탈취되더라도 안전하다는 장점이 있습니다. ![](https://i.imgur.com/dwgfpwL.png) 클라이언트는 로그인 시 아이디, 비밀번호를 입력하여 서버로 요청하면 서버는 토큰을 jsonwebtoken 모듈로 생성하고 access_token은 응답(response)에, refresh_token은 cookie에 저장하여 클라이언트로 보냅니다. ```typescript import * as jwt from 'jsonwebtoken'; /** devHashKey = token option 값 */ const makeJwt = (id: string): { accessToken: string, refreshToken: string } => { try { return ({ accessToken: jwt.sign({ id }, devHashKey.secret, { ...devHashKey.option }), /* TODO: Have to end token */ refreshToken: jwt.sign({ id }, devHashKey.secret, { ...devHashKey.option, expiresIn: '96h' }) }); } catch(err) { ERROR.logicError(err); } }; const login = (req, res, next) => { ... const { access_token, refresh_token } = makeJwt(email); res.cookie('refresh_token', jwt.refreshToken, { maxAge: 60 * 60 * 24 * 30 }); return res.status(200).json({ access_token }); } ``` 클라이언트는 받은 access_token을 redux store에 인증이 필요한 요청에 토큰을 포함시켜 전송합니다. "모두의 개발"은 `react-redux`와 `redux-saga`를 통해 구현되어 있습니다. redux에 대한 설명은 따로 대체하겠습니다. 지금은 전역으로 상태를 저장한다고만 알고 계시면 좋을 것 같습니다. ```typescript /** react-redux 활용 디스패치 */ const dispatch = useDispatch(); dispatch(/* login_action_function */); /** react-redux 활용 이후 요청에 포함시키기 */ const token = useReducer((state) => state.token) fetch(`${BASEURL}/request_path`, { headers: { Authorization: `Bearer ${token}` } }) ``` **최초 클라이언트 접속시(url로 접속)** 만약 접속 시 cookie에 refresh토큰이 있는지 여부를 검사하고 있다면 서버로 인증을 요청, access_token을 받아 redux store에 저장합니다. 그 과정은 로그인과 동일합니다. cookie의 컨트롤은 `react-cookie`모듈을 활용합니다. refresh token 로직은 모든 route에서 동작해야 하므로 앱의 최상단(index.tsx|jsx)에서 시행됩니다. ```typescript /** */ import { Cookies } from 'react-cookie'; useEffect(() => { const refresh_token = new Cookies().get('refresh_token'); if (refresh_token) dispatch(/** refresh_action_function */); }, []); ``` ## 마무리 인증, 인가, 로그인, 암호화, 세션, 쿠키, 토큰, 토큰의 저장과 관리는 웹 어플리케이션에서 가장 기본이자 시작입니다. 그만큼 다양한 이슈, 방식이 존재하므로 각각의 방법을 시도해보고 이해해보는것은 좋은 경험이 될것입니다. 다시한번 로그인을 어떻게 구현할지 고민해봅시다