임통 아이콘

임통 블로그

REST API 카카오 소셜 로그인

기술

2024년 08월 12일

REST API 카카오 소셜 로그인의 썸네일

소셜로그인?

소셜로그인은 사용자와 개발자 모두에게 편리함과 보안을 제공합니다.

1. 사용자 경험 개선

  • 간편한 로그인: 사용자가 별도로 계정을 생성할 필요 없이, 이미 사용하는 소셜 미디어 계정을 통해 간편하게 로그인할 수 있습니다. 이를 통해 회원가입 과정을 단순화하고, 사용자 입장에서는 여러 사이트에 별도의 계정 정보를 기억하지 않아도 됩니다.

  • 빠른 로그인 과정: 이메일 주소 확인, 비밀번호 설정, 인증 등의 과정을 생략할 수 있어 로그인 시간이 단축됩니다.

2. 보안강화

  • 안전한 인증: 대부분의 소셜 로그인 제공자는 강력한 보안 시스템을 적용하고 있습니다.
    예를 들어, Google이나 Facebook같은 플랫폼은 2단계 인증(2FA), 비밀번호 복구 등 강력한 보안 기능을 제공합니다. 이를 통해 보안 유지에 대한 부담을 사이트 운영자 대신 소셜 로그인 제공자가 담당합니다.

위와 같은 이유로 해커톤 프로젝트인 필쏘굿에 소셜로그인은 빠르고 간단하게 로그인과 회원가입 기능을 구현할 수 있기에 적합하다고 생각하여 구현하기로 결정했습니다.

구현 방법

1. 로그인 / 동의 확인

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_idredirect_uri는 백엔드 개발자가 생성한 카카오 Developers에서 알 수 있습니다.
response_type은 code로 고정합니다.
자세한 내용은 공식 문서를 참고해주세요.

이렇게 설정한 link로 이동하여 로그인 동의를 하게 되면, 설정한 redirect_uri로 리다이렉트 됩니다. 이 때 인가코드도 쿼리 파라미터값으로 받아집니다.

2. 인가코드 전달

리다이렉트된 주소에 해당하는 페이지를 작성합니다.
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 };
};

3. 세션 관리

브라우저 쿠키에 저장된 토큰값을 받아 유저 데이터를 받는 함수를 작성합니다.

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 훅을 통해 세션을 관리할 수 있습니다.

해당 글은 목차가 없습니다.