소셜로그인을 사용하는 이유는 사용자와 개발자 모두에게 편리함과 보안을 제공합니다.
간편한 로그인: 사용자가 별도로 계정을 생성할 필요 없이, 이미 사용하는 소셜 미디어 계정을 통해 간편하게 로그인할 수 있습니다. 이를 통해 회원가입 과정을 단순화하고, 사용자 입장에서는 여러 사이트에 별도의 계정 정보를 기억하지 않아도 됩니다.
빠른 로그인 과정: 이메일 주소 확인, 비밀번호 설정, 인증 등의 과정을 생략할 수 있어 로그인 시간이 단축됩니다.
위와 같은 이유로 해커톤 프로젝트인 필쏘굿
에 소셜로그인은 빠르고 간단하게 로그인과 회원가입 기능을 구현할 수 있기에 적합하다고 생각하여 구현하기로 결정했습니다.
const KakaoLoginButton = () => {
const link = `https://kauth.kakao.com/oauth/authorize?client_id=${import.meta.env.VITE_REST_API_KEY}&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code`;
const loginHandler = () => {
window.location.href = link;
};
return (
<button onClick={loginHandler} className="w-[27.8rem]">
<img src={kakao} alt="kakao" />
</button>
);
};
link
의 필수 쿼리 파라미터로는 client_id
, redirect_uri
, response_type
이 있습니다.
client_id
와 redirect_uri
는 백엔드 개발자가 생성한 카카오 Developers에서 알 수 있습니다.
response_type
은 code로 고정합니다.
자세한 내용은 공식 문서를 참고해주세요.
이렇게 설정한 link
로 이동하여 로그인 동의를 하게 되면, 설정한 redirect_uri
로 리다이렉트 됩니다. 이 때 인가코드도 쿼리 파라미터값으로 받아집니다.
리다이렉트된 주소에 해당하는 페이지를 작성합니다.
useEffect
훅을 사용하여 페이지가 마운트될 때 로직을 실행하고 실행하는 동안 로딩 스피너를 보여줍니다.
const KakaoRedirectPage = () => {
// 리다이렉트된 페이지에서 쿼리 파라미터로 전달된 인가코드 값을 받습니다.
const [searchParams] = useSearchParams();
const code = searchParams.get("code");
useEffect(() => {
getToken(code).then(({ token, user }) => {
// GET요청으로 받아온 토큰값을 브라우저 쿠키에 저장합니다.
document.cookie = `accessToken=${token}; Path=/`;
if (user.hardware_no) {
window.location.href = "/";
} else {
window.location.replace("/serial");
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <Spinner />;
};
카카오 서버에서 전송된 인가코드를 받아서 백엔드 서버에 전달합니다.
export const getToken = async (code: string | null) => {
const res = await fetch(
`https://api.example.com/login/oauth2/code/kakao?code=${code}&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}`
);
const data = await res.json();
const userResponse = await fetch("https://api.example.com/users/login", {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
});
// 백엔드 서버에서 headers로 보내준 토큰을 받습니다.
const token = userResponse.headers.get("Authorization");
if (!token) throw new Error('토큰을 받지 못했습니다.')
const accessToken = token.split(" ")[1];
const userData = await userResponse.json();
return { token: accessToken, user: userData };
};
브라우저 쿠키에 저장된 토큰값을 받아 유저 데이터를 받는 함수를 작성합니다.
export const getUser = async (): Promise<AuthUser | null> => {
if (!accessToken) return null;
const res = await fetch("https://api.example.com/users/me", {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
const user = await res.json();
return user;
};
Context API
를 사용하여 전역적으로 세션값을 관리하는 SessionProvider
를 작성합니다.
import { getUser } from "@/service/auth";
import { AuthUser } from "@/types/user";
import { createContext, useContext, useEffect, useState } from "react";
const SessionContext = createContext<{ loginUser: AuthUser | null }>({
loginUser: null,
});
const SessionProvider = ({ children }: { children: React.ReactNode }) => {
const [loginUser, setLoginUser] = useState<AuthUser | null>(null);
useEffect(() => {
getUser().then((user) =>
user?.email ? setLoginUser(user) : setLoginUser(null)
);
}, []);
const ctx = { loginUser };
return (
<SessionContext.Provider value={ctx}>{children}</SessionContext.Provider>
);
};
export const useSession = () => useContext(SessionContext);
export default SessionProvider;
이제 SessionProvider
로 감싼 모든 컴포넌트에서 useSession
훅을 통해 세션을 관리할 수 있습니다.