우리 조는 JWT를 사용하여 개발을 하기로 결정했다.
많은 이유가 있었지만 이를 선택한 이유는
1 . stateless
2. 간편한 클라이언트 인증
3. 수업에서 배운 점을 적용
다른 팀원 분들은 아마 또 JWT를 선택한 이유가 더 있을 수 있다.
선택 후 따로 이야기를 나눈적은 없었기 때문이다. 필자는 위 3가지의 이유 때문에 동의하였다.
자, 그럼 프론트엔드에서는 어떤 걸 해주면 될까 고민했다.
먼저 백엔드 개발자님 께서 "Authorization"에 명시해서 jwt를 보내달라고 하셨었다.
얼떨결에 넵!이라고 했지만 그게 무슨 뜻인지 사실 잘 알지 못했다. (일단 개발하면 알게되겠지? 라는 마인드 ㅋㅋ)
그렇게 일단 개발에 들어갔다.
가장 먼저 로그인을 구현하기로 했기 때문에 가장 첫 기능 구현인 만큼 잘하고자 노력했다.
백엔드 개발자님의 실력이 매우 좋아 API는 정말 빠르고 성능좋게 나왔다.
필자는 먼저 페칭함수를 만들었다.
const login = async (data: User) => {
const response = await authInstance.post("/users/log-in", data);
const { message } = response.data;
const jwt = response.headers["authorization"];
return { message: message, jwt: jwt };
};
BE 개발자님은 header에 jwt를 보내주셨다. (강의에서는 body에 보냈었다. 알아보니 header에 보내는게 일반적이고 사실 body에 보내도 크게 다른 점은 없다고 한다.)
위 처럼 데이터를 받아오려 했는데 jwt값이 계속 비어서 들어오는 문제를 발견했다.
withcredential로 true로 설정했고 origin도 잘 맞게 열려있는거 같은데..
그래서 구글링을 하다 보니 아래의 게시글을 보게 되었다.
cors에 의해 발생하는 이슈였고 header에 "Authorization"이라는
key값의 value를 노출할 것인지에 대한 옵션을 주어야 프론트에서 접근이 가능한 문제였다.
이렇게 message와 jwt를 받아
//Login.tsx
const onSubmit: SubmitHandler<User> = async (data) => {
try {
const response = await login(data);
const { message, jwt } = response;
storeLogin(jwt);
alert(message);
goHome();
} catch (error) {
//error 핸들링
alert("일치하는 회원 정보가 없습니다.");
}
};
위처럼 페칭하여 해당 jwt를 클라이언트에서도 접근할 수 있게 되었다.
자 그럼 jwt를 어떻게 보내줄까?
매번 페칭 함수에 옵션으로 jwt를 넣어 보내주기엔 너무 귀찮다. 그렇게 해도 되지만 더 좋은 방법은
axios intance를 통해 authInstance를 만들어서 jwt가 필요한 handleFetching함수는
해당 instance를 통해 호출을 하는 것이다.
(ajax통신을 하는건데 그 라이브러리인 axios를 사용하는건데 해당 axios의 기능인 instance를 통해 jwt까지 집어 넣어서 통신하는거)
export const BASE_URL = "http://localhost:3001/api";
const DEFAULT_TIMEOUT = 20000;
const createDefaultInstance = (config?: AxiosRequestConfig) => {
const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: { "Content-Type": "application/json" },
withCredentials: true,
...config,
});
return axiosInstance;
};
필자는 먼저 default의 instance를 생성하였다. timeout은 최대 기다려주는 시간
만약 이 시간을 넘는다면 오류로 간주하고 오류 처리를 해줄 수 있다.
다시 본론으로 돌아와서 headers에는 Content-type으로 application/json이라고 적어놨는데
이게 무엇일까!
일단 HTTP의 request는 다음과 같은 4개의 파트로 나눌 수 있다.
Message Body에 들어가는 타입을 HTTP header에 명시해줄 수 있는데 이때 명시해줄 수 있도록 해주는 필드가 바로 Content Type이다!
즉, body에 뭘 주고 받을꺼냐? 물었는데 나는 json 형식의 데이터를 주고받겠다! 라고 말해준 것이다.
그리고 이제 중요한건 auth를 적용할 수 있는 instance를 오버라이딩하는 것이다.
const authInstance = createDefaultInstance({
headers: {
Authorization: getToken() ? getToken() : "",
},
});
이처럼 만들었던 default Instance를 가져와 headers에 Authorization에 담아 보내주었다.
getToken은 Localstorage에 담겨있던 jwt를 가져오는 함수이다.
이 기능을 알게된 건 같은 팀의 FE분이 사용하시는걸 보고 알게 되었다. 이렇게도 할 수 있구나!
axios는 interceptors 라는 기능을 제공한다.
데이터를 보내거나 받기 전에 즉, 백엔드로 데이터를 보내기전/클라이언트로 데이터를 받기전 무언가 처리를 해줄 수 있는 기능을 제공한다. 미들웨어이다.
그래서 필자는 interceptors.request를 통해 다음과 같은 오류를 개선하였다.
authInstance.interceptors.request.use(
(config) => {
const token = getToken();
config.headers.Authorization = token;
return config;
},
(error) => {
return Promise.reject(error);
}
);
이게 무엇이냐면 사실 위 코드
const authInstance = createDefaultInstance({
headers: {
Authorization: getToken() ? getToken() : "",
},
});
이렇게만 요청을 보낸다면 getToken은 최초에 받아온 토큰만을 기억하게 된다.
그러면 로그아웃을 하더라도? 새로고침을 하지 않는 이상 계속 로그인이 유지되는 현상이 있었다.
(그런데 사실 다른 FE분께서 이 점을 미리 아시고 전역상태관리라이브러리를 사용해주셔서 이러한 오류현상이 없어졌지만 그 전에 구현했던 부분이라 작성해보겠다!)
그럼 이럴때 interceptors를 통해 getToken을 새롭게 받아 보내줄 수 있는 것이다.
그런데 interceptors.response를 통해서 error 핸들링도 가능하다 ㄷㄷ!
authInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response) {
switch (error.response.status) {
case 401:
removeToken();
window.location.href = "/login";
break;
case 404:
return Promise.resolve({ data: [] });
case 409:
return alert(error.response.data.message);
case 304:
return alert(`이전과 동일한 스케줄입니다.`);
default:
break;
}
} else {
console.error("No response received from server");
}
return Promise.reject(error);
}
);
이렇게 error객체를 받아와서 해당 status에 따라 에러 처리를 해줄 수 있다.
여기서는 304에러가 전체 웹 서비스에서 특정 경우로 한정되어 이렇게 포괄되게 사용할 수 있었는데
만약 아주 큰 서비스라면 이렇게 interceptors 를 통해 전체적인 handling하는 것보다는 또 다른 처리를 해주는 것이 더 좋을 거 같다. (직접적으로 Alert를 날리기 보다는 throw error하거나?)
이렇게 로그인은 마무리 하였다.