REturn 0;

[React] 카카오 소셜 로그인 구현 본문

개발

[React] 카카오 소셜 로그인 구현

zza.___.lng 2023. 10. 10. 01:09

대한민국의 98%는 카카오톡을 사용하고 있다.

그래서인지 현재 대부분의 서비스에서 소셜로그인, 그 중에서도 카카오 로그인을 이용하여 서비스를 사용 할 수 있도록 하고있다.

내 기억에도 서비스에서 로그인이 필요할 때, 카카오 로그인을 이용해서 로그인 한 경험이 많았었다.

그래서~ 이번 프로젝트에서 소셜 로그인을 맡아서 진행하기로 했다.

카카오 소셜 로그인 서비스

이번 프로젝트는 프론트와 백엔드가 협업하여 프론트에서 로그인을 요청하여 인가코드를 받아와서 백엔드로 넘겨주면 인가코드를 이용해 카카오 서버에서 토큰을 받아와 얻은 정보를 이용해 자체 jwt 토큰을 제작해서 프론트에서 사용자 정보를 받아올 것이기 때문에 REST API를 사용했다.

 

Kakao Developers

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

developers.kakao.com

카카오에 설명하는 카카오 로그인이란?

카카오 로그인은 OAuth 2.0 기반의 소셜 로그인 서비스입니다. 카카오 로그인을 사용하면 사용자가 카카오톡 또는 카카오계정으로 손쉽게 서비스에 로그인이 가능하다. 서비스는 서비스 ID 및 비밀번호를 입력받고 검증하는 과정을 직접 구현하지 않고도 사용자에 대한 인증과 인가를 간편하고 안전하게 처리할 수 있습니다.

카카오 로그인 과정

위 과정을 통해 우리 서비스에서의 로그인 과정을 간단하게 설명하면

  1. 사용자가 카카오 로그인 요청
  2. 카카오 서버에 인가 코드 요청
  3. 카카오 인증 서버에서 사용자에게 인증 및 동의 요청(카카오 로그인 페이지로 이동시켜 진행)
  4. 사용자의 로그인 및 동의 요청
  5. 카카오 서버에서 인가코드 발급(미리 설정해둔 Redirect URI에서 인가코드 발급)
  6. 백엔드로 인가 코드를 전송해서 백엔드가 카카오 서버로 토큰 발급 요청
  7. 백엔드에서 토큰으로 사용자 정보(닉네임, 이름, 이메일, 성별 등) 요청
  8. 사용자 정보를 DB에 저장하고, 이에 접근할 수 있는 자체 jwt 토큰 생성
  9. 프론트로 자체 jwt 토큰 전송
  10. 프론트에서 jwt토큰을 이용해 사용자 정보 불러오기

요렇게만 적어놓으면 이해하기가 어렵기 때문에 하나씩 코드와 사진을 이용해서 설명

1번부터 설명하기 전에 kakao developers에서 미리 설정해야 할 것

Kakao Developre에 프로젝트 등록(🚨KEY는 유출되면 안된다!)

플랫폼 카테고리를 선택해서 아래 Web을 활성화 시키고 사이트 도메인 입력(현재는 로컬에서만 진행)

카카오 로그인 카테고리를 선택해서 활성화 On으로 변경

카카오 로그인 카테고리를 선택하고 하단에 Redirect URI 설정(REST API 방식이기 때문에 필수로 설정)

위 4가지를 설정해주고 코드를 작성하면된다!


이제 1번부터 10번까지 차근차근 설명해보겠다.

 

1. 사용자가 카카오 로그인 요청

말 그대로 사용자가 카카오 로그인 버튼을 누르면 로그인을 요청하게 되는 것이다.

사용자가 로그인이 필요한 페이지에 들어갔을 때, 로그인이 되지 않은 상태라면 아래 컴포넌트를 출력한다.

아래 카카오로 시작하기 버튼을 누르면 카카오 서버에 인가 코드를 요청하는 페이지로 이동한다.

2. 카카오 서버에 인가 코드 요청

뒤에 설명할 3, 4, 5번 과정을 통해 카카오를 이용해 우리 서비스에 로그인하면 제공되는 인가코드를 받아온다.

 

 

3. 카카오 인증 서버에서 사용자에게 인증 요청(카카오 로그인 페이지로 이동시켜 진행)

export const Rest_API_key = '@!#!@$!@$!@#$!@#$!@#$@!';
export const Redirect_uri = 'http://localhost:3000/kakaologin';
export const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${Rest_API_key}&redirect_uri=${Redirect_uri}&response_type=code`;

REST API key는 Kakao Developers에서 복사해서 붙여넣은 본인 프로젝트의 key이다.

Redirect_uri에는 Kakao Developers 로그인 카테고리에서 본인이 작성한 주소를 입력한다.

kakaoURL은 로그인 버튼을 눌렀을 때 이동되는 카카오에서 제공하는 로그인 페이지이다.

이렇게 파일을 나누어 정보들을 변수에 저장하고,

 

// login.tsx
import { kakaoURL } from './loginInfo';

const handleLogin = () => {
	window.location.href = kakaoURL;
};

<img src={'/img/kakao.png'} css={kakao} onClick={handleLogin} />

 

위와 같이 로그인 페이지로 이동하는 카카오 로그인 버튼을 만들어서 함수를 연결해주면 된다.

현재 우리 프로젝트에서 login.tsx 컴포넌트를 렌더링하면 아래와 같은 페이지를 볼 수 있다.

위 버튼을 클릭하여 로그인을 시도하면 카카오 인증서버에서 사용자에게 인증을 요청하는 로그인 페이지로 이동된다.

4. 사용자의 로그인 및 동의 요청

위 로그인 버튼을 클릭하면

우리가 서비스를 사용하면서 카카오톡으로 로그인하기를 누르면 볼 수 있었던 페이지에 들어가게 된다.

KakaoUrl 변수에 넣은 REST_API_key가 우리 프로젝트를 위한 고유 key 이기 때문에 해당 주소에서 로그인하면 우리 프로젝트로 로그인을 할 수 있는 원리이다. 로그인 요청이 완료되면 동의 요청 페이지로 넘어간다.

위와 같이 사용자 정보에 대한 제공 동의 요청 페이지로 이동되고, 동의까지 완료해야 로그인이 완료되는 시스템이다.

 

5. 카카오 서버에서 인가코드 발급(미리 설정해둔 Redirect URI에서 인가코드 발급)

그럼 KakaoUrl에 넣은 Redirect_URI는 어디서 사용할까?

카카오 계정으로 로그인이 완료되고 나면 다음 페이지가 나온다.

현재 주소는 http://localhost:3000/kakaologin?code=#!@$@!$!@#~#~!#~!#~!#~!#!~이다.

 

주소에 적혀있듯이 로그인 페이지로 이동할 때 입력했던 redirect uri로 이동된 것을 확인할 수 있다. 로그인이 완료되고나면 돌아갈 페이지를 설정해주는 것이다. 하지만 이때 redirect uri는 kakao developers에 등록한 redirect uri로만 이동이 가능하다.

 

등록한 주소는 /kakaologin인데 뒤에 붙은 code=%!@#%@!는 무엇일까? 이것이 바로 카카오 서버에서 발급해준 "인가코드" 이다. 이 인가코드에는 사용자가 동의한 내역이 담겨있고, 이 인가코드를 이용해 사용자가 제공에 동의한 정보 데이터로 접근할 수 있는 "토큰"을 발급받을 수 있다.

 

클라이언트는 서버에 이 인가코드를 전송하여 서버가 사용자 정보에 접근할 수 있도록 토큰을 발급받도록 해야한다.

 

6. 백엔드로 인가 코드를 전송해서 백엔드가 카카오 서버로 토큰 발급 요청

이제 Redirect uri로 발급받은 인가코드를 서버에 전송해보자

일단 주소에 있는 인가코드를 변수에 저장한다.

 

const code: string | null = new URL(window.location.href).searchParams.get('code');

window.location.href 는 현재 페이지에 주소를 가져온다.

이 주소를 new URL을 이용해 URL 객체로 만들어주고 쿼리 매개변수를 가져올 수 있는 searchParams 메서드를 이용해 'code' 라는 쿼리 매개변수 값을 .get을 이용해 저장한다.

이렇게 하면 위 주소에 있던 code = #@!#!@#에 있던  #@!#!@# 값이 string 형태로 code라는 변수에 저장된다. 

 

이제 이 인가코드를 서버에 전송해야된다. api 명세서를 한 번 확인해보자

예시처럼 달라고 한다. 저렇게 주자.

interface postCodeBody {
    authCode: string | null;
}

const body: postCodeBody = {
    authCode: code,
};

아까 저장해둔 code를 post요청 시 전송할 body에 서버측에서 작성한 예시대로 작성해서 저장해주었다.

이제 인가코드를 서버해 전송해보자

export const postCode = async (body: postCodeBody) => {
	try {
		const response = await axios.post(`${baseURL}`, body);
		console.log('인가코드 전송 및 전용 토큰 응답');
		const accessToken = response.data.accessToken;
		const refreshToken = response.data.refreshToken;

		localStorage.setItem('access', accessToken);
		localStorage.setItem('refresh', refreshToken);
	} catch (error) {
		console.error('🚨🚨에러 발생 에러 발생 🚨🚨', error);
		throw error;
	}
};

api url에 맞는 주소를 입력하고, body안에 인가코드를 넣고 서버 측으로 post 요청을 한다. 이 때, 전송한 인가코드로 서버는 카카오 서버에 해당 사용자 정보에 접근할 수 있는 "토큰"을 발급받는다. 토큰은 access토큰과 refresh토큰 2가지로 나눠서 발급된다. 

 

access 토큰: 사용자 인증, 사용자 정보 접근 권한 부여 // 만료기한 6시간

refresh 토큰: 액세스 토큰 재발급에 사용, 리프레시 토큰이 유효하면 재로그인 없이도 액세스 토큰 재발급 // 만료기한 2달

 

post요청에 응답으로 전송받은 값을 response에 저장한다.

이때 response안에 토큰이 있고, 이 토큰을 클라이언트 측에서 받아서 저장한다.

 

클라이언트 측으로 전송한 토큰은 서버가 카카오 서버에서 발급받은 토큰이 아니다. 이 설명은 9번에서 하도록 하겠다.

 

위에서 작성한 postCode는 어디서 호출해야될까? 인가코드를 저장할 수 있는 redirect url가 렌더링되면 바로 실행되도록 kakaologin.tsx 컴포넌트에서 호출했다.

//kakaologin.tsx
useEffect(() => {
		const login = async () => {
			await postCode(body);
			setIsLogin(true);
			navigate('/');
		};
		login();
	}, []);

로그인이 완료되고 redirect url이 렌더링되면 바로 서버측으로 인가코드를 보내고 navigate("/")를 이용해 홈으로 이동했다.

 

7. 백엔드에서 토큰으로 사용자 정보(닉네임, 이름, 이메일, 성별 등) 요청

서버 측에서 카카오 서버에서 발급받은 토큰을 이용해 사용자 정보를 받아온다. 카카오 서버는 받은 토큰이 유효한지 요청 검증 및 처리 후, 사용자 정보를 전송해준다.

8. 사용자 정보를 DB에 저장하고, 이에 접근할 수 있는 자체 jwt 토큰 생성

서버는 카카오 서버로부터 받아온 사용자 정보를 클라이언트가 접근할 수 있는 DB Table에 사용자 정보를 저장한다.

저장과 동시에 저장한 사용자 정보에 접근할 수 있는 JWT 토큰을 생성한다.

 

9. 프론트로 자체 jwt 토큰 전송

const response = await axios.post(`${baseURL}`, body);
console.log('인가코드 전송 및 전용 토큰 응답');
const accessToken = response.data.accessToken;
const refreshToken = response.data.refreshToken;

localStorage.setItem('access', accessToken);
localStorage.setItem('refresh', refreshToken);

위에서 POST 요청을 보내면 서버는 응답으로 8번에서 생성한 JWT 토큰을 보내준다.

클라이언트는 받아온 응답 내에 accessToken과 refreshToken을 로컬 스토리지에 저장한다.

 

 

10. 프론트에서 jwt토큰을 이용해 사용자 정보 불러오기

이제 저장한 토큰으로 서버측 DB에 저장된 사용자 정보를 받아와보겠다.

일단 API 명세서를 확인하자

이렇게 달라고한다. 이렇게 주자.

 

export const getUserInfo = async () => {
	const accessToken = localStorage.getItem('access');

	const response = await axios.get(
		`${baseURL}`,
		{
			headers: {
				Authorization: `Bearer ${accessToken}`,
			},
		},
	);
	console.log('사용자 정보 저장 완료');

	return response.data;
};

header 예시에 맞게 accessToken을 가져와서 헤더에 담아서 get 요청을 보낸다.

요청하면 응답으로 사용자 정보를 받아올 수 있도록 api 호출 코드를 작성했다. 

 

이 getUserInfo는 어디서 호출해야할까? redirect url에서 인가코드를 전송하고, 토큰을 발급받는 api를 호출하면 토큰이 로컬스토리지에 저장된 채로 "/" 홈으로 이동되도록 코드를 작성했기 때문에, 사용자 정보는 홈 컴포넌트가 렌더링되면 실행되도록 하였다(서비스 페이지에 처음 들어가면 렌더링 되는 컴포넌트가 홈 컴포넌트이기 때문에 조건을 붙여서 함수를 호출할 수 있도록 코드를 작성했다)

 

useEffect(() => {
    if (localStorage.getItem('access') && userName === '') {
        const getUserProfile = async () => {
            const data = await getUserInfo();
            console.log(data);
            setUserEmail(data.email);
            setUserName(data.userName);
        };
        getUserProfile();
    }
}, [userName]);

userName은 사용자의 이름을 담는 recoil 값이다. 액세스 토큰이 존재하는데, 사용자 이름을 불러와서 저장하지 않았을 경우 이 함수를 실행하도록 if문을 작성했다.

 

조건을 만족하면 위에서 작성한 api를 호출하는 코드를 실행하고, 받아온 데이터에서 필요한 정보를 recoil 값에 저장해서 전역에서 사용할 수 있도록 하였다.

 

이제 로그인이 완료되고 홈으로 이동되면 사용자 정보까지 자동으로 저장이 완료된다.

아래는 recoil에 저장된 사용자 정보를 필요한 컴포넌트에서 불러서 사용한 모습이다!

로그인 완료, 사용자 정보 저장 후 마이페이지

 

 

 

🚨🚨겸상한 오류들🚨🚨

 

Internal Server Error 500

진짜 서버와 통신하면서 가장 많이 만났던 에러..................

postman을 이용해 api를 사용했을땐 잘 됐는데, 통신하니까 500에러가 발생했다. uri에서 받은 인가코드를 복사해서 postman으로 붙여넣기를 하면 토큰이 잘 받아와지는걸 보고 통신하는 과정에서 에러가 발생한다는 것을 알 수 있었다.

 

새벽까지 백엔드 개발자와 함께 있으면서 이것저것 다 시도해봤는데 안되서 그냥 집에 갔는데, 도저히 잠이 안와서 찾아보다가 백엔드에서 구현하는 코드와 프론트에서 구현하는 코드 모두 Redirect uri를 설정해주어야 하는데, 백엔드에서 api를 만들고 자체 테스트를 해보느라 localhost:8080 이나 배포된 사이트 주소를 redirect uri로 설정해놔서 그런거였다...... 프론트 선배포를 해뒀기 때문에 배포 주소를 redirect uri로 설정할 수 있었고, 나는 내 로컬에서 코드를 구현중이어서 localhost:3000으로 설정했기 때문에 오류가 발생했던 것이다. kakao developers에서 추가해둔 redirect uri로 다시 변경했더니 바로 됐다. 기뻤다.

 

+++

 

백엔드 api 주소가 http로 설정했는데, 배포된 도메인은 https라서 500에러가 발생했었다.

채널톡, 구글 태그 매니저 등을 설치하느라 https 도메인이 필수적이었기 때문에 백엔드에서 https로 변경해주는 방법밖에는 없었다. 프론트 입장에서 고칠 수 있는 방법이 있는지 관리해주시는 멘토님께 여쭤봤더니

고쳐달라고 했다. 고쳐졌다.

 

403 Forbidden

오류의 뜻은 서버에서 설정해 둔 권한과 맞지 않는 접속 요청이 들어오면 접근을 거부한다는 뜻이다.

 

우리는 백엔드에서 스프링 시큐리티... 인가... 어쩌구 작업을 하다가 토큰 형식을 Bearer로 전송하면 프리패스하는 식으로 만들었는데, 그 소통이 잘못되서 그냥 string 형식으로 전송해서 만난 오류였다.

 

{
    headers: {
        Authorization: `Bearer ${accessToken}`,
    },
},

이렇게 토큰 앞에 Bearer을 붙이면 해결되는 문제이다!

 

 

 

 

⭐⭐느낀점⭐⭐

프론트 입장에서 코드 구현 자체가 어려운 부분은 없었다. 동기/비동기 이해를 잘못하고 설계했다가 잘못된 적도 있지만 금방 고칠 수 있었고, 컴포넌트나 토큰 및 사용자 정보 저장하는 부분도 크게 어려운 부분이 없었다. 하지만 소셜 로그인의 원리와 흐름을 이론적으로 이해하는데 오래걸렸다,,, 두번째 프로젝트였는데, 앞으로 이 정도로 백엔드와 많이 소통 할 프로젝트가 있을까? 싶을 정도로 정말 많은 오류를 마주하고 고치려고 많은 얘기를 나눴다. 프론트 입장에서 복잡한 코드가 아니기 때문에 매번 백엔드 측에서 고쳐줘야될 것들이 대부분이었는데, 화 한 번 내지않고 묵묵히 디버깅하고 수정해준 백엔드 개발자에게 고마웠다. 

 

+++

레퍼런스들이 너무 어렵게 설명한 글이 많았고, 스스로 공부한게 아까워서 이렇게 쉽게 누구나 구현할 수 있도록 정리를 해보았다. 우여곡절이 많았던 소셜 로그인이지만 해내서 기분이 좋다.👏🏻

'개발' 카테고리의 다른 글

특별 세션 회고  (2) 2023.11.17
Slack과 Github 연동  (0) 2023.09.08