본문 바로가기
React

리액트로 OAuth 소셜로그인 구현하기(구글,네이버,카카오)

by 왜안돼요 2024. 6. 10.
728x90

이전 프로젝트에서는 무작정 카카오소셜로그인 하나만 구현했엇는데 이번 기회에는 OAuth가 뭔지부터 공부하고자 작성해본다.

 

❓OAuth가 뭐야

OAuth란 Open Authorization의 약자로, 인터넷 사용자들이 자신의 자격 증명(사용자 이름과 비밀번호)를 공유하지 않고도 웹 사이트나 애플리케이션이 사용자 정보를 접근할 수 있도록 하는 인증 프로토콜이다.

제3자 애플리케이션 접근 허용

사용자들이 자신의 소셜 미디어 계정(구글,페이스북 등)을 통해 다른 웹사이트나 애플리케이션에 로그인 할 때, 이를 가능하게 하는 것이 OAuth이다. 사용자는 자신의 자격증명을 공유하지 않고도 제3자 애플리케이션이 필요한 정보에 접근 할 수 있도록 승인한다.

 

API  접근 권한 부여

개발자들이 OAuth를 사용하여 다른 서비스의 API에 접근할 수 있다. 예를 들어 트위터 API를 사용하여 트윗을 게시하거나 읽을 때 OAuth를 사용하여 인증한다.

 

구동방식

세개의 소셜로그인을 구현해본 결과 코드들이 다 비슷하고 크게 다른점이 없는것 같아 대표적으로 카카오로그인 과정을 가져왔다.

기본적인 흐름은 다음과 같다.

  • 사용자 인증 : 사용자가 로그인하고, 서비스 제공자는 사용자에게 제3자 애플리케이션이 자신의 정보에 접근하는 것을 허용할 것인지 묻는다.
  • 사용자 승인 : 사용자가 접근을 허용하면, 서비스 제공자는 제3자 애플리케이션에 접근 토큰을 발급한다.
  • 토큰 사용 : 제3자 애플리케이션은 이 접근 토큰을 사용하여 사용자의 정보에 접근하거나 특정 작업을 수행한다.

 

나는 백엔드분들과 같이 소셜로그인을 진행하여 따로 kakao developers에 앱을 등록하지 않고 필요한 키들은 백엔드분께 받아서 .env에 저장후 사용했다.

 

구글을 제외한 네이버,카카오는 앱 등록이 비슷하여 구글링해서 등록해보면 좋을듯하다.

 

 

사용하기

 

카카오로 부터 accessToken을 받기 위한 리다이렉트 페이지가 필요하다.

 

const commonRoutes = [
  <Route path="/" element={<Layout />}>
    <Route index element={<Main />} />
    <Route path="/sign-in" element={<SignIn />} />
    <Route path="/sign-up" element={<SignUp />} />
    <Route path="/search" element={<Search />} />
    <Route path="/all" element={<TotalProducts />} />
    <Route path="/product/:productId" element={<ProductDetailModal />} />
    <Route path="/oauth2/naver/redirect" element={<LoginHandler />} />
    <Route path="/oauth2/kakao/redirect" element={<LoginHandler />} />
    <Route path="/oauth2/google/redirect" element={<LoginHandler />} />
  </Route>,
];

나는 카카오,네이버,구글이라 url만 다르게하고 똑같은 페이지로 해줬다. 하나면 되지않을까?라는 생각이 들었는데

앱에 등록하는 리다이렉트 url에 때문에 이렇게 똑같은걸 세개를 만들었다...
이건 백엔드와 진행할경우엔 합의를 보면 좋지않을까 싶음.

백엔드에선 /auth/login/callback/${provider} 이런식으로 api를 던져줬고 나는 저 provider에 알맞게 구글인지 네이버인지 카카오인지 넣어주면 됨

 

(가끔 공부 제대로 안한 백엔드가 get 만들어서 주는 경우가 있는데. 필요없다고 해주십시오.. 로그인할때마다 백엔드에서 처리하는거기때문에 프론트는 Post만 해주면 됩니다요.
그리고 무조건 앱에 등록하는 url는 프론트 경로와동일해야합니다.!!!)

 

.env

VITE_REST_API_KEY="발급받은 키 넣으세용"
VITE_KAKAO_REDIRECT_URI="앱에 설정한 리다이렉트 url 넣으세요 (프론트 경로와 동일해야합니다)"
VITE_GOOGLE_REDIRECT_URI="앱에 설정한 리다이렉트 url 넣으세요 (프론트 경로와 동일해야합니다)"
VITE_NAVER_REDIRECT_URI="앱에 설정한 리다이렉트 url 넣으세요 (프론트 경로와 동일해야합니다)"

VITE_GOOGLE_AUTH_CLIENT_ID="발급받은 아이디 넣으세용"
VITE_GOOGLE_SCOPE="https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
VITE_GOOGLE_SECRET="발급받은 키 넣으세용"

VITE_NAVER_CLIENT_ID="발급받은 아이디 넣으세용"
VITE_NAVER_CLIENT_SECRET="발급받은 아이디 넣으세용"
VITE_NAVER_STATE="필요없을지도.."

 

로그인 페이지

const {
  VITE_REST_API_KEY,
  VITE_KAKAO_REDIRECT_URI,
  VITE_GOOGLE_REDIRECT_URI,
  VITE_NAVER_REDIRECT_URI,
  VITE_GOOGLE_AUTH_CLIENT_ID,
  VITE_GOOGLE_SCOPE,
  VITE_NAVER_CLIENT_ID,
  VITE_NAVER_STATE,
} = import.meta.env;

const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${VITE_REST_API_KEY}&redirect_uri=${VITE_KAKAO_REDIRECT_URI}&prompt=login`;

const GOOGLE_AUTH_URL = `https://accounts.google.com/o/oauth2/auth?client_id=${VITE_GOOGLE_AUTH_CLIENT_ID}&redirect_uri=${VITE_GOOGLE_REDIRECT_URI}&response_type=code&scope=${VITE_GOOGLE_SCOPE}`;

const NAVER_AUTH_URL = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${VITE_NAVER_CLIENT_ID}&state=${VITE_NAVER_STATE}&redirect_uri=${VITE_NAVER_REDIRECT_URI}`;

// 소셜미디어 로그인 버튼
const socialMedia = [
  { name: 'naver', src: '/images/naver.png', url: NAVER_AUTH_URL },
  { name: 'kakao', src: '/images/kakao.png', url: KAKAO_AUTH_URL },
  { name: 'google', src: '/images/google.svg', url: GOOGLE_AUTH_URL },
];

const SignIn = () => {

  
  return (
    <FormProvider {...form}>
      <div className="w-full  bg-mainBlack flex justify-center items-center flex-col">
        <div className="w-[460px] flex justify-center flex-col mt-40">
          <p className="font-didot text-3xl text-center mb-7">Sign In</p>
          <form onSubmit={handleClickSignIn} autoComplete="off">
            <AuthInput {...register('email')} type="email" placeholder="이메일">
              Email
            </AuthInput>
            {errors.email && <p className=" text-sm text-red-500 mt-1">{errors.email.message}</p>}
            <AuthInput {...register('password')} type="password" placeholder="비밀번호">
              password
            </AuthInput>
            <CommonButton className="w-full rounded-lg bg-mainWhite px-3.5 py-2.5 text-lg font-semibold font-didot text-mainBlack shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 mt-6">
              Sign In
            </CommonButton>
          </form>
        </div>
        <div className="mt-6">
          아직 회원이 아니신가요?
          <Link to="/sign-up">
            <button type="button" className="font-semibold ml-2">
              Sign in
            </button>
          </Link>
        </div>
        <div className="flex justify-center items-center mt-10 w-[460px]">
          <span className=" w-full h-[1px] bg-mainWhite mr-2"></span>
          <p>or</p>
          <span className=" w-full h-[1px] bg-mainWhite ml-2"></span>
        </div>
        <div className="flex flex-row mt-5">
          {socialMedia.map(({ name, src, url }) => (
            <a key={name} href={url} className="w-[39px] h-[39px] mr-3 cursor-pointer overflow-hidden rounded-full">
              <img src={src} alt={name} className="w-full h-full object-cover" />
            </a>
          ))}
        </div>
      </div>
    </FormProvider>
  );
};

export default SignIn;

 

카카오 아이콘을 누르게 되면 카카오 로그인 창이 뜨게 되고 로그인에 성공하면 우리가 설정한 리다이렉트 URI로 이동하게 된다. 

 

리다이렉트 페이지

import instance from '@/api/instance';
import Loading from '@/components/Loading';
import axios from 'axios';
import { useEffect } from 'react';
import { Cookies } from 'react-cookie';
const { VITE_BASE_REQUEST_URL, VITE_GOOGLE_AUTH_CLIENT_ID, VITE_GOOGLE_SCOPE, VITE_GOOGLE_SECRET, VITE_NAVER_STATE } = import.meta.env;

const LoginHandler = () => {
  const url = new URL(window.location.href)
  const code = url.searchParams.get("code");
  const cookies = new Cookies();


  const kakaoLogin = async () => {
    try {
      const response = await instance.post(`${VITE_BASE_REQUEST_URL}/users/login/social/kakao/`, 
        {
          code: code
        }
      );
      cookies.set("accessToken", response.data.access, { path: '/', secure: true, })
      cookies.set("refreshToken", response.data.refresh, { path: '/', secure: true, })
      window.location.href = "/"
    } catch (error) {
      console.error("카카오 로그인 실패", error);
    }
  }

  const naverLogin = async () => {
    try {
      const response = await instance.post(`${VITE_BASE_REQUEST_URL}/users/login/social/naver/`, 
        {
          code: code,
          state: VITE_NAVER_STATE
        }
      );
      cookies.set("accessToken", response.data.access, { path: '/', secure: true, })
      cookies.set("refreshToken", response.data.refresh, { path: '/', secure: true, })
      window.location.href = "/"
    } catch (error) {
      console.error("네이버 로그인 실패", error);
    }
  }

  const googleLogin = async () => {
    try {
      const response = await instance.post(`${VITE_BASE_REQUEST_URL}/users/login/social/google/`, 
        {
          code: code,
        }
      );
      if (response.headers['set-cookie']){
        console.log(response.headers['set-cookie'])
      }
      cookies.set("accessToken", response.data.access, { path: '/', secure: true, })
      cookies.set("refreshToken", response.data.refresh, { path: '/', secure: true, })
      window.location.href = "/"
    } catch (error) {
      console.error("구글 로그인 실패", error);
    }
  }
  useEffect(() => {
    if (url.href.includes("kakao")) {
      kakaoLogin()
    } else if (url.href.includes("naver")) {
      naverLogin()
    } else if (url.href.includes("google")) {
      googleLogin()
    }
  }, [])

  return (
    <div><Loading /></div>
  )
}

export default LoginHandler

 

리다이렉트가 되면 화면에 카카오로부터 받은 accessToken을 부여받을 수 있다. 이는 URL을 통해 제공이 된다. 

URL 객체에서 searchParams method를 통해 받아올 수 있다.

 

백엔드가 있다면 프론트에서 처리해줄건 더 이상 없다.

구글, 카카오, 네이버 이 세개가 다 동일하다,,, 백엔드에서 앱 만들고 발급받은 키 env에 저장해서 리다이렉트 되는곳으로 이동만 되게 하면 나머지는 백엔드 영역이라.. 그렇게 어렵진 않았다. 

 

네이버 리다이렉트 url에서 state를 요구하는데 이게 csrf 토큰을 넣어줘야하는건지 정확히 파악을 못하겠다. 좀 더 알아봐야 할 것같은데 일단 임시로 아무값이나 넣어줘도 로그인이 되긴 한다. 

 

백엔드 없이 프론트혼자서 진행해보려면

 

React를 활용한 OAuth 2.0 카카오 로그인 연동

이분 블로그를 참고해도 좋을것 같다.

 

 

참고

카카오 로그인 이해하기

최근댓글

최근글

skin by © 2024 ttuttak