웹 개발/기타 지식

[OAuth] 카카오 로그인 API를 사용하여 소셜로그인 구현하기

초보군붕이 2022. 12. 17. 16:29
반응형

토이프로젝트를 진행하면서 소셜로그인 구현을 처음 시도해봤다.

 

삽질한 경험을 블로그에 기록을 남겨 다음에도 구현할 일이 생긴다면 빠르게 구현할려고 한다.

 

● OAuth 란?

우하한테크코스 블로그에 잘 정리되어있는 글이 있어 해당 글을 보고 개념 및 동작방식을 공부했다

https://tecoble.techcourse.co.kr/post/2021-07-10-understanding-oauth/

 

OAuth 개념 및 동작 방식 이해하기

1. OAuth란? image 웹 서핑을 하다 보면 Google과 Facebook 및 Twitter…

tecoble.techcourse.co.kr

 

 

● 구현 환경

우선 언어의 경우 Typescript를 사용했으며 프론트는 React, 백엔드는 Node.js의 Express 프레임워크를 사용했다.

 

프론트/백이 분리된 환경이니 REST API를 통해서 소셜로그인을 구현했다.

 

 

 

● 구현 전 세팅해야 하는 것들

1. 카카오 개발자센터에서 로그인을 한 뒤 어플리케이션을 생성하기

카카오 개발자 센터 : https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

2. 카카로 로그인 활성화 및 Redirect URI 지정

활성화를 OFF → ON으로 변경하고 하단에 Redirect URI를 지정해준다.

 

Redirect URI는 카카오 로그인 이후 돌아올 페이지를 뜻한다.

나의 경우 회원가입 화면으로 돌아와서 기존 가입여부를 판단하고 다르게 구현할려고 회원가입 화면으로 지정했다.

카카오 로그인 화면

 

 

3. 동의항목 지정

카카오톡 로그인 이후 사용자 인증이 홈페이지에 공개할 항목들이다.

 

닉네임, 프로필사진, 이메일 등 다양한 항목이 있으며, 현재 개발중인 사이트는 이메일과 닉네임만 있으면 충분하므로 두가지만 동의항목으로 지정했다.

카카오 로그인 동의항목

 

 

 

● 카카오 로그인의 동작순서

카카오 로그인 다이어그램, 출저 : https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

내가 구현한 과정은 아래와 같다.

 

● Step 1. 인가 코드 받기 - 클라이언트에서 진행

  1. 카카오 로그인 버튼을 누르면 GET /oauth/authorize/{...some parameter} 으로 요청하여 인가코드 취득
  2. 카카오 로그인폼으로 리다이렉트
  3. 사용자 계정으로 카카오톡 로그인
  4. 수집 정보 동의화면 출력 
  5. 동의 후 카카오 서버로 재요청
  6. 전달한 리다이렉트 URI로 리다이렉트 진행

 

● Step 2. 토큰 받기 - 서버로 요청

  1. 리다이렉트된 URI에서 서버측으로 code를 넘겨준뒤 /oauth/token 으로 토큰 요청
  2. 토큰 취득

 

● Step 3. 사용자 로그인 - 클라이언트로 응답 반환

  1. 취득한 토큰으로 사용자를 인증하여 이메일, 닉네임 등을 가져온다
  2. 기존 가입된경우 로그인을 위한 accessToken을 반환하고, 새로운 회원이면 email, nickname을 반환하여 직접 패스워드를 입력하여 소셜로그인, 일반로그인을 모두 수행할 수 있도록 한다.

 

 

 

● 인가 코드 받기

1. 로그인 페이지 구현

인가 코드는 사용자가 카카오톡 로그인에 성공했을때 발급받는 코드다.

우선 로그인 페이지는 아래처럼 구현했다. 일반 회원가입도 가능하고 소셜로그인도 가능하다.

로그인 페이지

 

client/KakaoLogin.tsx

const KakaoLogin = () => {
  const KakaoLoginHandler = async () => {
    const getCodeUrl = "https://kauth.kakao.com/oauth/authorize";
    const parameter = `?client_id=${kakaoConfig.restApiKey}&redirect_uri=${kakaoConfig.redirectUri}&response_type=code`;
    window.location.href = getCodeUrl + parameter;
  };

  return (
    <StyledKakaoLogin onClick={KakaoLoginHandler}>
      <KakaoIcon />
      <ButtonText>카카오톡으로 3초만에 시작하기</ButtonText>
    </StyledKakaoLogin>
  );
};

카카오톡으로 로그인 버튼을 누르면 카카오 로그인페이지로 이동하여 계정을 입력하게 된다.

 

그 후 로그인 성공시 redirect uri로 이동하게 된다.

 

회원가입 화면으로 리다이렉트

 

위 사진의 경우 구현이 끝난 뒤 사진으로 이메일과 닉네임에 값이 자동으로 들어간다.

 

그냥 리다이렉트만 하게되는 경우 단순히 /register 페이지로 이동하게 된다.

 

client/RegisterForm.tsx

const RegisterForm = () => {
  const { search } = useLocation();
  const { code } = useMemo(() => queryString.parse(search), [search]);

  /** 카카오 로그인으로 넘어온 경우(쿼리스트링에 code 존재할경우) */
  useEffect(() => {
   /** Some Logic... */
  }, [code]);

카카오톡 로그인을 진행하고 나면 http://localhost:3000/register?code=ue5mc.... 형식으로 리다이렉트 되는데 컴포넌트에서 쿼리스트링을 파싱해서 만약 code가 존재한다면 서버측에 요청하는 방식으로 구현했다.

 

 

● 토큰 받기

1. 서버로 엑세스토큰 발급 요청

client/Register.tsx

const RegisterForm = () => {
  const { search } = useLocation();
  const { code } = useMemo(() => queryString.parse(search), [search]);
  const setLoginUser = useSetRecoilState(loginUserState);

  /** 카카오 로그인으로 넘어온 경우(쿼리스트링에 code 존재할경우) */
  useEffect(() => {
    const kakaoAuth = async () => {
      try {
        const res = await axios.post("http://localhost:5000/auth/kakao-login", { code });
      } catch (error: any) {
        throw error;
      }
    };

    if (code) {
      kakaoAuth();
    }
  }, [code]);

받은 code를 서버를 통해서 accessToken을 발급하는 형태이다.

 

2. 클라이언트에서 받은 code로 엑세스토큰 발급 요청

auth.router.ts

authRouter.post("/kakao-login", AuthController.kakaoLogin);

 

auth.controller.ts

static kakaoLogin = async (req: Request, res: Response, next: NextFunction) => {
    const { code } = req.body;

    try {
      const data = await AuthService.kakaoLogin(code);
      res.status(200).json(data);
    } catch (error: any) {
      res.status(error.status || 500).json({ message: error.message || "에러발생" });
    }
  };

 

auth.service.ts

/** 추후 아래에 작성 */

 

auth.model.ts

static getUserByEmail = async (email: string) => {
    const query = "SELECT * FROM users WHERE email=?";
    const values = [email];
    try {
      const [rows, fields]: [GetUserByEmailReturn[], FieldPacket[]] = await connectionPool.execute(query, values);
      return rows;
    } catch (error: any) {
      throw {
        status: 500,
        message: error.message,
      };
    }
  };

 

router → controller 그 후 auth.service.ts에서 엑세스토큰 발급요청 로직을 처리하게 된다. ( 3 layer architecture )

 

공식문서에 기재된 토큰발급 요청 Sample

 

아래 코드를 통해 accessToken 발급을 요청했다.

/** 카카오 OAuth를 통해 사용자의 accessToken 가져오기 */
      const token = await axios.post(
        "https://kauth.kakao.com/oauth/token",
        {
          grant_type: "authorization_code",
          client_id: kakaoConfig.restApikey,
          redirect_uri: kakaoConfig.redirectUri,
          code: code,
        },
        {
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
          },
        }
      );

      /** accessToken이 없는경우 */
      if (!token.data) {
        throw {
          status: 500,
          message: "OAuth error",
        };
      }

 

3. accessToken으로 사용자정보 조회하기

공식문서에 기재된 사용자정보 발급 Sample

 

accessToken의 경우 Header에 넣어서 get요청을 하면 된다.

RFC6750 표준에 따라 토큰의 타입은 Bearer타입을 사용한다.

/** accessToken으로 사용자 정보 가져오기 */
      const kakaoUser = await axios.get("https://kapi.kakao.com/v2/user/me", {
        headers: {
          Authorization: `Bearer ${token.data.access_token}`,
        },
      });

      /** 사용자 정보 가져오기 실패 */
      if (!kakaoUser) {
        throw {
          status: 500,
          message: "Get User Info Failed",
        };
      }

 

4. 기존 가입되어있는 유저인지 판별하여 클라이언트로 응답 반환

      const email = kakaoUser.data.kakao_account.email;
      const nickname = kakaoUser.data.properties.nickname;

      /** 사용자가 가입되어있는 유저인지 체크 */
      const userByEmail = await AuthModel.getUserByEmail(email);

      /** 신규 유저인 경우 */
      if (userByEmail.length === 0) {
        return {
          existUser: false,
          data: {
            email,
            nickname,
          },
        };
      }

      /** 기존 가입된 유저면 accessToken 발행 */
      const accessToken = Jwt.createToken(email, nickname);

      return {
        existUser: true,
        data: {
          accessToken: accessToken,
          email: email,
          nickname: nickname,
        },
      };
    } catch (error: any) {
      throw {
        status: 500,
        message: error.message,
      };
    }
  };

신규 유저일 경우에는 email, nickname을 반환하여 회원가입이 가능하도록 자동완성하고, 기존 가입된 유저는 기존 로그인 형식과 동일하게 accessToken을 발급해서 클라이언트로 반환해준다.

 

 

 

● 구현 결과

1.  카카오 계정으로 신규 회원가입

신규가입

 

2.  기존 유저 카카오 계정으로 로그인

 

기존유저 로그인

 

3.  카카오계정 유저가 일반계정으로 로그인

일반계정 로그인

 

일반계정 로그인은 이전 카카오 서버장애를 생각하면서 구현해봤다.

 

● Github

전체 소스코드는 아래 깃허브에서 확인 가능합니다.

https://github.com/imkdw/used-marketplace

 

GitHub - imkdw/used-marketplace: 중고 물품을 거래할 수 있는 웹사이트 입니다.

중고 물품을 거래할 수 있는 웹사이트 입니다. Contribute to imkdw/used-marketplace development by creating an account on GitHub.

github.com

 

반응형