5. useMutation 을 위한 세팅
route handler 를 향해 fetch 를 날릴건데,
이것을 useMutation 으로 처리하고자합니다.
먼저 mutationFn 에 들어갈 함수를 만듭니다.
export async function postNaverLogIn(): Promise<Buddy | null> {
if (!window.location.hash) return null;
const hash = window.location.hash.substring(1);
const params = new URLSearchParams(hash);
const accessToken = params.get('access_token');
const url = '/api/auth/callback/naver';
try {
const data = await fetchWrapper<Buddy>(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ accessToken }),
});
return data;
} catch (error: any) {
throw error;
}
}
그냥 이 함수 내부에서 해시값을 추출한 뒤 body에 담아서
api/auth/callback/naver 를 향해서 fetch POST 를 날립니다.
useMutation 도 그냥 훅으로 만들었습니다.
export function useNaverLogInMutation() {
const queryClient = useQueryClient();
return useMutation<Buddy | null, Error, void>({
mutationFn: () => postNaverLogIn(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY_BUDDY] });
},
});
}
6. auth.context.tsx 의 naverLogIn 함수
우리는 auth 관련한 로직들을 auth.context.tsx 에서 contextAPI로 관리합니다.
여기에 쓰기편하게 naverLogIn 을 만들어서 내보냅니다.
export const AuthContext = createContext<AuthContextValue>(initialValue);
export function AuthProvider({ children }: PropsWithChildren) {
// ...중략...
const { mutateAsync: naverLogInMutation, isPending: isNaverLogInPending } =
useNaverLogInMutation();
// ...중략...
const naverLogIn: AuthContextValue['naverLogIn'] = useCallback(async () => {
try {
const buddy = await naverLogInMutation();
if (!buddy)
return showAlert('caution', '알 수 없는 오류가 발생했어요');
showAlert('success', `${buddy.buddy_email}님 환영합니다!`, {
onConfirm: () => router.replace('/'),
});
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error';
return showAlert('error', errorMessage, {
onConfirm: () => router.refresh(),
});
}
}, [naverLogInMutation, router]);
// ...중략...
const value: AuthContextValue = {
// 기타등등
naverLogIn,
};
return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
}
7. /api/callback/naver/route.ts 라우트핸들러
이제 라우트 핸들러를 만들어줍니다.
const FIXED_PASSWORD = process.env.NAVER_PROVIDER_LOGIN_SECRET;
export async function POST(request: Request) {
const { accessToken } = await request.json();
if (!FIXED_PASSWORD) {
return NextResponse.json(
{ error: 'NAVER_PROVIDER_LOGIN_SECRET is not set' },
{ status: 400 },
);
}
if (!accessToken) {
return NextResponse.json(
{ error: 'Access token not found' },
{ status: 400 },
);
}
try {
// 네이버 API를 사용하여 사용자 정보 가져오기
const response = await fetch('https://openapi.naver.com/v1/nid/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
return NextResponse.json(
{ error: 'Failed to fetch user info from Naver' },
{ status: 400 },
);
}
const userData = await response.json();
const userEmail = userData.response.email;
const supabase = createClient();
// signInWithPassword로 로그인 시도
const { data: signInData, error: signInError } =
await supabase.auth.signInWithPassword({
email: userEmail,
password: FIXED_PASSWORD,
});
if (signInError) {
if (signInError.message.includes('Invalid login credentials')) {
// 사용자 존재하지 않음, 새로 가입 시도
const { data: signUpData, error: signUpError } =
await supabase.auth.signUp({
email: userEmail,
password: FIXED_PASSWORD,
options: {
data: {
avatar_url: userData.response.profile_image,
},
},
});
if (signUpError) {
throw signUpError;
}
if (!signUpData.user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 401 },
);
}
const buddy = await getBuddy(supabase, signUpData.user.id);
return NextResponse.json(buddy, { status: 200 });
} else {
throw signInError;
}
} else {
const buddy = await getBuddy(supabase, signInData.user.id);
// 로그인 성공
return NextResponse.json(buddy, { status: 200 });
}
} catch (error) {
console.error('Error during Naver login callback:', error);
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 },
);
}
}
여기서 좀 걸리는 부분은 supabase 가 naver를 지원하지 않아서
어쩔 수 없이 우회하느라 supabase email 기반 계정을 하나 생성해버리고,
이때 비밀번호를 그냥 다 고정값으로 했다는 점 입니다.
그런데 뭐 딱히 방법이 없기도 하고 저희가 필요한 것은 buddies 테이블의 buddy 이지
supabase auth 스키마의 user 가 아니기 때문에 그냥 적용했습니다!
대강 로직은 https://openapi.naver.com/v1/nid/me
여기로 받은 토큰을 같이 보내면
네이버에 로그인된 네이버 아이디 정보를 리턴해주는데
여기에서 이메일과 프로필사진 두 가지만 씁니다.
이 두가지를 사용해서 supabase auth 에 가입시켜버리는데,
먼저 로그인 시도를 해보고 없으면 가입시킵니다.
supabase 로그인이 만약 된다면 이미 한번 네이버로
로그인 한 적이 있다는 뜻이니 그냥 로그인이 진행 될 것입니다.
아니라면 신규 가입이 되겠습니다!
남은 문제들...
결국 이 방법은 그냥 편법일 뿐인 것 같습니다.
어떤 사람이 네이버에서 인증된 메일을 변경하고
저희 사이트에서 다시 네이버로그인을 시도하게되면
저희 테이블에 기록된 이메일은 이전의 이메일이기 때문에
새로 가입이 될 것이고, 그러면 서비스 이용에 장애가 생깁니다.
극복하는 방법은 네이버에서 리턴해주는 고유 id 를
컬럼하나 만들어서 기록해놓으면 될 것 같긴 합니다!
고민해 봐야겠습니다!
'supabase' 카테고리의 다른 글
[240730 TIL] supabase upsert (0) | 2024.07.30 |
---|---|
[240729 TIL] 수파베이스 리얼타임(테이블저장) (0) | 2024.07.29 |
[240727 TIL] naver 로그인 편법 구현 with Supabase 1편 (0) | 2024.07.28 |
[240723 TIL] SQL 팔로잉 수 측정 (0) | 2024.07.24 |
[240722 TIL] supabase auth.users 정보로 유저 테이블 자동입력 (0) | 2024.07.21 |