한 일주일을 헤매고 드디어 해낸 기록.
환경 :
next-auth : ^4.10.3 사용
next-auth의 CredentialsProvider로 로그인 시도.
/* eslint-disable */
import axios from 'axios';
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
const createOptions = (req) => ({
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
userEmail: {label: 'Email', type: 'text', placeholder: 'jsmith'},
userPw: {label: 'Password', type: 'password'},
},
async authorize(credentials, req) {
// console.log('req :: ', req)
const apiUrl = process.env.NEXTAUTH_API_URL;
const u = await axios
.post(`${apiUrl}/login-service/user/signIn`, {
userEmail: req.body.userEmail,
userPw: req.body.userPw,
})
.then((response => {
const user = response.data;
// console.log("user :: ", user)
return user;
}))
.catch((error) => {
console.log('error :: ', error);
throw new Error(JSON.stringify(error.response.data.code));
}) || null;
return u;
},
}),
],
pages: {
signIn: '/login',
error: '/loginError',
},
session: {
strategy: 'jwt',
},
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
jwt: async ({token, account, user, req}) => {
if (account) {
// 로그인 시에만 실행됩니다. 다음에 호출할 때마다 이 부분을 건너뜁니다.
token.account = {
...account,
accessToken: user.accessToken,
refreshToken: user.refreshToken
};
}
return token;
},
session: async ({session, token}) => {
// @ts-ignore
session.accessToken = token.account.accessToken;
session.refreshToken = token.account.refreshToken;
console.log('session :: ', session);
return session;
},
},
});
export default async (req, res) => {
return NextAuth(req, res, createOptions(req));
};
로그인 시 accessToken, refreshToken이 발급된다.
터미널 콘솔 :
accessToken은 5분 뒤 만료된다.
accessToken은 모든 요청의 Header에 Authorization으로 보내야 한다.
axiosConfig를 이용하여 처리할 수 있었다.
만약 5분이 지나 토큰이 만료되고,
만료된 토큰으로 요청을 보내면 ->
백엔드에서 401요청을 주면 ->
refreshToken을 요청하는 auth/refresh 를 호출하여
호출한 결과로 받은 accessToken (response.data.data값)을
session.accessToken = response.data.data으로
세션에 할당하여 다시 요청을 보내는 처리를 하였다.
axiosConfig.ts
'use client';
import axios from 'axios';
import { useSession } from 'next-auth/react';
import { useEffect } from 'react';
// axios.defaults.baseURL = process.env.NODE_ENV === 'development' ? '' : process.env.NEXT_PUBLIC_API_BASE_URL;
// axios.defaults.baseURL = process.env.API_URL;
const axiosAuth = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
});
const useRefreshToken = () => {
const { data: session } = useSession();
const refreshToken = async () => {
console.log('3리프레시 실행');
const res = await axiosAuth.post(
`/login-service/auth/refresh`, // token refresh api
{ refreshToken: session.refreshToken },
{ headers: { Authorization: `Bearer ${session.accessToken}` } }
);
// refresh 결과는 2 response 결과로 찍힌다
if (session) {
console.log('3 refresh이후 session?', session);
}
};
return refreshToken;
};
export const useAxiosAuth = () => {
const { data: session } = useSession();
const refreshToken = useRefreshToken();
useEffect(() => {
// 요청 인터셉터
const requestIntercept = axiosAuth.interceptors.request.use(
(config) => {
if (!config.headers.Authorization) {
console.log('1요청 보내는 accessToken', session.accessToken);
config.headers.Authorization = `${session.accessToken}`;
}
return config;
},
(error) => {
console.log('1 interceptor request error', error);
Promise.reject(error);
}
);
// 응답 인터셉터
const responseIntercept = axiosAuth.interceptors.response.use(
(response) => {
console.log('2 Interceptors response', response);
// 재발행 받은 토큰 덮어쓰기
if (response?.data?.massage?.includes('재발행')) {
session.accessToken = response.data.data;
}
return response;
},
async (error) => {
const prevRequest = error?.config;
if (error?.response?.status === 401 && !prevRequest?.sent) {
prevRequest.sent = true;
await refreshToken();
prevRequest.headers.Authorization = `${session?.accessToken}`;
return axiosAuth(prevRequest);
}
return Promise.reject(error);
}
);
return () => {
axiosAuth.interceptors.request.eject(requestIntercept);
axiosAuth.interceptors.response.eject(responseIntercept);
};
}, [session, refreshToken]);
return axiosAuth;
};
export default axiosAuth;
요청을 날리는 test.tsx
'use client';
import React, { useState } from 'react';
import { useAxiosAuth } from '~/services/axiosConfig';
// import axiosAuth from '~/services/axiosConfig'; //바로 axiosAuth를 불러 사용하니 응답값이 잘 안찍혔다.
import { useSession } from 'next-auth/react';
const Test = () => {
const axiosAuth = useAxiosAuth(); // 이렇게 사용하자.
const { data: session } = useSession();
const [userData, setUserData] = useState();
console.log('TESTpage session', session);
const getUserInfo = () => {
console.log('클릭');
axiosAuth({
method: 'post',
url: '/login-service/userInfo/getUserInfo',
})
.then((response) => {
console.log('test response', response);
setUserData(response.data.data);
})
.catch((err) => {
console.log('test err', err);
});
};
console.log('userData', userData);
function Comp({ item }) {
return <p>{item.userEmail}</p>;
}
return (
<>
<a onClick={getUserInfo}>getUserInfo</a>
</>
);
};
export default Test;
axios의 인터셉터를 다루며 까다로웠던 점은
interceptor가 응답을 가로채서 콘솔에 찍히는점이 좀 혼란스러웠었다.
'NextJS' 카테고리의 다른 글
nextjs [slug] 페이지에서 Link href 경로가 잘못 나올 때 (0) | 2023.03.16 |
---|