인증이 필요한 API 요청엔 헤더에 토큰을 담아서 보내야 한다. 그런데 기존 로컬 스토리지에 보관했던 토큰을 서버 컴포넌트에서는 사용할 수 없는 문제가 있었다. 서버 컴포넌트에서는 로컬 스토리지, 세션 스토리지와 같은 브라우저 저장소에 접근할 수 없기 때문이다. 서버 컴포넌트에서는 `cookies` 함수를 이용하여 쿠키에 접근할 수 있었다. (단, 쿠키의 get, has와 같은 메서드는 서버 컴포넌트에서 사용할 수 있지만 set 메서드는 오직 Server Action과 Route Handler에서만 사용할 수 있다. 이는 HTTP가 스트리밍이 시작한 후에는 쿠키를 설정할 수 없게 하기 때문이다.)
서버 컴포넌트와 클라이언트 컴포넌트 모두에서 토큰을 사용하려면 서버 컴포넌트에서는 쿠키에서, 클라이언트 컴포넌트에서는 localstorage에서 토큰을 가져와야 한다. 그리고 나는 현재 custom fetch instance를 사용하고 있었기에 instance header에 자동으로 토큰을 삽입하여 매 요청마다 토큰을 삽입하는 번거로움을 없애고자 했다.
import returnFetch, { FetchArgs, ReturnFetchDefaultOptions } from 'return-fetch'
import { getToken } from '@/(auth)/_utils/getToken'
...
export const fetchExtended = returnFetchJson({
baseUrl: process.env.NEXT_PUBLIC_API_ROOT,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
interceptors: {
request: async (args) => {
return args
},
response: async (response, requestArgs) => {
if (response.status >= 400) {
console.log('Request URL:', requestArgs[0].toString())
const { message } = await response.json()
throw new Error(message);
}
return response
},
},
})
// fetch with token
export const nextFetch = async <T>(
url: FetchArgs[0],
init?: JsonRequestInit,
) => {
const token = await getToken()
return fetchExtended<T>(url, {
...init,
headers: {
...init?.headers,
Authorization: `Bearer ${token}`,
},
})
}
interceptor 안에서 직접 토큰을 넣고 싶었지만 시도했던 모든 방법이 실패했고 결국 헤더에 token을 fetch를 반환하는 fetch를 새로 만들었다. fetch 함수가 서버, 클라이언트 모든 곳에서 사용될 수 있기 때문에 getToken 함수 또한 두 곳 모두에서 사용될 수 있도록 구현해야 했다.
(getToken.ts)
import { getClientToken } from './getClientToken'
import { getServerToken } from './getSeverToken'
export const getToken = async () => {
const server = await getServerToken()
if (server) return server
return getClientToken()
}
(getServerToken.ts)
'use server'
import { cookies } from 'next/headers'
export const getServerToken = async () => {
const token = cookies().get('token')?.value || ''
return token
}
(getClientToken.ts)
import useBoundStore from '@/stores'
export const getClientToken = (): string | null => {
const { token } = useBoundStore.getState() // localstorage와 연결된 zustand store
return token
}
이렇게 함으로써 이제 요청이 어디에서 일어나든 토큰이 헤더에 자동으로 포함되어 요청되게 되었다. getToken은 fetch에서 뿐만 아니라 토큰이 필요한 다른 장소에서도 편리하게 사용하고 있다. 서버 컴포넌트와 클라이언트 컴포넌트가 모두 사용되고 있다 보니 인증 관리를 하는 데 있어서도 클라이언트 컴포넌트만 사용했을 때보다 조금 더 신경을 써야 하는 것 같다.
참고
https://nextjs.org/docs/app/api-reference/functions/cookies
잘못된 부분이 있다면 댓글로 남겨주시면 감사하겠습니다.
'Develop > NextJS' 카테고리의 다른 글
[Next.js] SEO 최적화 & 구글/네이버에 사이트 등록하기 (0) | 2024.08.10 |
---|---|
[Next.js] 스크롤 최상단 이동 버튼 구현하기 (0) | 2024.08.05 |
[Next.js] Data fetching과 Caching Mechanism (0) | 2024.06.12 |
[Next.js] Next.js 작업물 Vercel로 배포하기 (0) | 2023.07.21 |
[NextAuth] 커스텀 로그인 구현과 회원 정보 수정 (+ session update) (0) | 2023.07.14 |