프로젝트에서 제일 중요한 지점중 하나는 auth
관리라고 요번에 많이 느꼈다.
전반적인 기능개발(조건부 처리) , 페이지 단위에서 데이터 통신등 여러 조건들을 신경 쓰게 되는데,
요번 프로젝트에서
위 2가지를 고민하고 생각해봤다.
서버와 통신 할 때 axios
와 react-query
를 사용한다.
각각의 page단에서 axios를 통해 request-header
에 토큰을 넣으면 되겠지만,
하나의 instance를 만들면 그 안에서 url
과 method(get,post,put...)를 바꿔주면 되므로, 모듈화를 만들어야 겠다 판단했다.
// server_api.ts
import { I_AuthStore } from '@/types/auth/auth';
import axios, { InternalAxiosRequestConfig } from 'axios';
const baseURL = process.env.NEXT_PUBLIC_SERVER_BASE_URL;
const SESSION_ID = process.env.NEXT_PUBLIC_SESSION_PATH;
/**
* @explain 서버와 요청할 때 토큰이 필요한 함수를 위한 util함수
* @returns
*/
const getAuthorizationToken = () => {
// zustand action으로 호출해줄걸...
const AUTH_SESSION = sessionStorage.getItem(SESSION_ID as string);
if (AUTH_SESSION) {
const {
state: { accessToken, refreshToken },
}: { state: I_AuthStore } = JSON.parse(AUTH_SESSION);
return {
accessToken,
refreshToken,
};
}
throw Error('토큰이 없습니다. 로그인 해주시고 이용해주세요');
};
/**
* @explain 서버에 요청을 보내기전에 세션 토큰을 헤더에 집어넣는 instance 입니다.
*/
export const axiosValidInstance = axios
.create({
baseURL,
})
.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const { accessToken, refreshToken } = getAuthorizationToken();
if (!accessToken || !refreshToken) return config;
config.headers.Authorization = `Bearer ${accessToken}`;
config.headers.refreshToken = refreshToken;
return config;
},
error => {
return Promise.reject(error);
},
);
실제 프로젝트에서 내가 작성한 파일을 가져왔다.
지금 보니 줄일 수 있는 것도 많은 것같네....
axios에서 제공하는 interceptor
를 이용하여 session에 토큰 값이 있는지를 판해서 프론트엔드 개발자들이 axios instance를 import
하여 사용 할 수 있게 만들었다.
내가 직접적으로 client단에서 request header에 부착한 토큰으로 통신을 하는 경우는 없었다.
그러나, 로그인 여부를 각 페이지단에서 어떻게 처리를 할지도 맡게 되었다.
Next.js에서 제공하는 나중가서는 next-auth library
, middleware
를 물론 사용하여 로그인 여부에 따른 페이지 처리를 하였지만, 라이브러리와 미들웨어를 처리하기 이전에 고민 했던 생각을 작성하고 그에 따른 문제점이 무엇인지 생각하였다.
내 경우는 이 때까지, cookie 관련 지식자체가 없었기에... sessionStorage와 zustand의 전역 상태관리를 사용 할 때, 무엇을 사용하는 것이 좋을지 생각해봤다.
공통 레이아웃안에 router.pathname
에따라 조건부 라우팅 해주기
onst Layout = ({ children }: { children: ReactNode }) => {
const router = useRouter();
const isLogin = useAuthStore((state) => state.isLogin);
// 아니면 getValue를 통해 session을 구독하게 하여 isLogin값 가져오기
const isLoginValue = getAuthSessionValue('isLogin')
useEffect(() => {
if (
(!isLogin && router.pathname === Pages.home) ||
router.pathname === Pages.zod
) {
router.push("/auth-zustand");
}
}, [router.pathname, isLogin]);
음...
장점보단, router의 페이지마다 조건부를 추가해줘야하고, 로직이 너무 많아진다.
계속해서 호출과 조건에 따른 라우팅을 해야 하므로...
이 다음 useEffect와 별반 다를게 없어보인다.
페이지 컴포넌트에서 직접 로그인 여부 확인 방법 중 하나로 useEffect
hook을 사용하여 컴포넌트 마운트 시 zustand의 store에 accessToken
혹은 isLogin
값을 가지고 redirect 처리하는 방법을 생각했다.
// SamplePage.tsx
const SamplePage = ()=> {
const router = useRouter()
useEffect(()=> {
const isLogin = useAuthStore(state=>state.isLogin);
if(!isLogin)router.push('/login')
},[])
return <Component/>
}
장점으로는
단점
_app.tsx
에서 useEffect를 사용하여 먼저 getAuth같은 session값을 가져오는 것으로 처리 하면 되지만, 전역상태관리를 페이지단과 _app.tsx
두 군데를 관리해야한다.// _app.tsx
export default function App() {
const isLogin = useAuthStore((state) => state.isLogin); // 매번 호출됨
useEffect(() => {
const result = getAllAuthValue(); // 매번 호출됨
if (result.isLogin && !isLogin) setAuthInSession(result);
// 아이디 바꿔서 로그인 하면 그에따른 예외 처리 추가해야함
}, []);
예전 프로젝트에서 react-error-boundary
를 사용 했는데, 그때 HOC
pattern을 사용하여 routing되는 컴포넌트를 감싸줬었다.
const withAuth = (WrappedComponent: React.ComponentType) => {
const Component = () => {
const isLogin = useAuthStore((state) => state.isLogin);
const router = useRouter();
useEffect(() => {
if (!isLogin) router.push("/login");
if (isLogin) router.push("/");
}, [isLogin]);
return <WrappedComponent />; // Component가 return하는 컴포넌트
};
return Component; // Component를 return
};
export default withAuth;
// page.tsx
export default withAuth(about);
export default withAuth(Home);
//...wrapped pages of other needed
장점
단점
useEffect
하는 것과 사실 별반 차이가 없어보인다.코드들이 직관적이고, 각 페이지를 개발 하는 사람들이 작성해주면 되지만,
useEffect
에 따른 처리(렌더링 되기전에 되었으면...)위의 5가지만 봐도, 불편한 점이 많다.
새로고침시에도, 먼저 로그인 관련 여부를 확인 했으면 좋겠고
페이지를 추가 할 때마다 작성해야할 코드들을 단축 시켰으면 좋겠다.
auth는 중앙(진입점)에서 처리하고, 그 하위 또는 파생으로 페이지마다 조건부 렌더링이 되기를 바란다.
axios module화를 통해 하나의 instance를 가지고 각 페이지마다 처리하는 것과 맞췄으면 하는 바램으로 인한 것이기에
이때 나온 Next.js의 middleware.ts 파일인데, middleware를 사용하기 위해 먼저
Next.js와 호완성을 이루는 Next-Auth 라이브러리를 적용하게 된 계기와 라이브러리 사용법, 등을 포스팅 해보자