# 과거 "모두의 개발"의 로그인과 인증 방식(비밀번호, 토큰)
로그인과 인증은 웹 어플리케이션을 구현하는 시작점입니다.(물론 요구하지 않는 사이트도 많습니다) 어떤 로그인 방식을 구현할지 고민하고 구현하는것은 많은 예제가 있고 그 중 하나의 방식을 이해하고 적용하는 것은 정말 중요한 일이라 생각합니다.
이 글은 "모두의 개발" 사이트가 어떤방식의 로그인을 가지고 있었고 어떻게 변경했으며 어떤 문제가 발생했고 어떻게 구현되어 있는지를 이야기해보는 글입니다.
## 기본적인 구조
과거에도 지금도 모두의 개발의 클라이언트 - 서버 구조는 동일하며 동일한 서버 아키텍쳐를 가지고 있습니다.

<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에 저장하였습니다. 과정을 표현하면 아래와 같습니다.

암호화는 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이 페이지 단에서만 유지되므로 토큰이 탈취되더라도 안전하다는 장점이 있습니다.

클라이언트는 로그인 시 아이디, 비밀번호를 입력하여 서버로 요청하면 서버는 토큰을 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 */);
}, []);
```
## 마무리
인증, 인가, 로그인, 암호화, 세션, 쿠키, 토큰, 토큰의 저장과 관리는 웹 어플리케이션에서 가장 기본이자 시작입니다. 그만큼 다양한 이슈, 방식이 존재하므로 각각의 방법을 시도해보고 이해해보는것은 좋은 경험이 될것입니다. 다시한번 로그인을 어떻게 구현할지 고민해봅시다