728x90
리액트, NEXTJS로 카카오 로그인 구현을 해보았다.
진행한 카카오 로그인의 전체 흐름을 간단히 정리해보았다.
1. 맨 처음 사용자가 '카카오로그인' 눌렀을 시, 카카오 디벨로퍼 사이트에서 받은 REST API KEY 와 설정한 Redirect URI 로 카카오에 CODE 요청 → CODE 발급
2. 그 CODE 로 카카오에게 TOKEN 요청 → TOKEN 발급 (이 시점에 사용자와 앱이 연결됨!!)
3. 그 TOKEN으로 카카오에게 유저정보(ID가 담긴) 를 요청 → 유저정보(ID가 담긴) 발급
4. 그 유저정보를 백엔드에 회원가입 API로 전송 (TOKEN 값을 ID로 설정, 닉네임, 이름 등...)
5. 그 이후에 로그인 요청은 기존 TOKEN 검증처럼 똑같이 진행
리액트, NEXT.JS 로 카카오 로그인을 하는 방법이 여러가지 있는 것 같았으나
백엔드만 하다가 프론트를 처음 해봤기 때문에 최대한 간단하게 하기위해서 ,
프론트에서는 CODE, TOKEN, 유저정보만 받고 바로 백엔드로 튀었다... 다른 방법은 나중에 차차 더 공부해보기로 해야겠다. 6시간 정도만에 정말 간단하게 구현이 완성되었다.
단 3가지 파일만 필요했다.
아래는 최종 코드이다.
1. _app.tsx
아래 한 줄 추가하기.
<script src="https://developers.kakao.com/sdk/js/kakao.js"
import "@styles/globals.css";
import "@styles/assets/css/style.css";
import "tailwindcss/tailwind.css";
import Script from 'next/script';
import React from "react";
import { Windmill } from "@roketid/windmill-react-ui";
import type { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps) {
// suppress useLayoutEffect warnings when running outside a browser
if (!process.browser) React.useLayoutEffect = React.useEffect;
return (
<Windmill usePreferences={true} dark={false}>
<Script strategy='beforeInteractive' src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=mhvjuj53xv"/>
{/* 카카오 로그인 위해 추가 */}
<script src="https://developers.kakao.com/sdk/js/kakao.js"
defer
></script>
<Component {...pageProps} />
</Windmill>
);
}
export default MyApp;
2. kakaoLogin.ts
/* 카카오 로그인을 위한 API */
/** 카카오 요청 시 필요한 주소 값 등 */
// const KAKAO_SERVER = 'http://104.50.177.80:5000';
const KAKAO_SERVER = 'http://localhost:5000';
const REDIRECT_URI = KAKAO_SERVER + '/member/login/login';
const REST_API_KEY = 'd7831~~~~~~~~~~~~~5251'; //REST API KEY
const INITIAL_URL_BASE = 'https://kauth.kakao.com/oauth/authorize'; // '카카오로그인'을 클릭할 때 이동하는 URL 중 앞부분
const TOKEN_URL_BASE = 'https://kauth.kakao.com/oauth/token?grant_type=authorization_code'; // 카카오에서 CODE로 TOKEN을 받아오기 위한 BASE URL
const USER_INFO_URL = 'https://kapi.kakao.com/v2/user/me'; // 카카오에서 TOKEN으로 USER정보를 받아오기 위한 BASE URL
export const INITIAL_URL_BASE_FULL = INITIAL_URL_BASE + '?client_id=' + REST_API_KEY + '&redirect_uri=' + REDIRECT_URI + '&response_type=code'; // '카카오로그인'을 클릭할 때 이동하는 URL
/** 카카오에 getTokenFromKakao로 요청하면 받을 수 있는 응답 값*/
export interface TokenResponse {
token_type?: string;
access_token: string;
refresh_token?: string;
id_token?: string;
expires_in?: number;
refresh_token_expires_in?: string;
scope?: string;
token?: string;
id?: number;
}
/** 카카오에 getUserFromKakao로 요청하면 받을 수 있는 응답 값*/
export interface UserInfo {
id: number;
connected_at: string;
properties: {
nickname: string;
profile_image?: string; // 640x640
thumbnail_image?: string; // 110x110
};
}
/** 1. 카카오에서 CODE로 TOKEN을 받아오기 */
export default async function getTokenFromKakao(authCode: string) {
const tokenUrl = `${TOKEN_URL_BASE}&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&code=${authCode}`;
const response: TokenResponse = await fetch(tokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
}).then((res) => res.json());
if (response.access_token !== undefined) {
console.log('response (카카오에서 토큰 값 받기 성공) ....................... : ', response)
return response;
}
}
/** 2. 카카오에서 TOKEN으로 USER정보를 받아오기 */
export async function getUserFromKakao({ access_token }: TokenResponse) {
const response: UserInfo = await fetch(USER_INFO_URL, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${access_token}`,
},
}).then((res) => res.json());
if (response.id !== undefined) {
console.log('response (카카오에서 유저 정보 받기 성공) ....................... : ', response)
return response;
}
}
3. login.tsx
import React, { useState, useEffect } from "react";
import { signin, signup, LoginInfoModel } from "@apis/login";
import getTokenFromKakao, { getUserFromKakao, INITIAL_URL_BASE_FULL } from "@apis/kakaoLogin";
import { useRouter } from "next/router";
function LoginPage() {
const router = useRouter();
/**
|--------------------------------------------------
| 라우팅 함수
|--------------------------------------------------
*/
/** 맨 처음 사용자가 '카카오로그인' 버튼을 눌렀을 시 */
const handleKakaoLogin = () => {
window.location.href = INITIAL_URL_BASE_FULL
}
async function moveHome() {
window.location.href = "/member/mainPage/home"
}
/**
|--------------------------------------------------
| 카카오 로그인
|--------------------------------------------------
*/
async function kakaoLogin() {
if(router.query.code !== undefined){
console.log('router.query.code (라우터 쿼리로 코드 값 받기 성공)................: ', router.query.code);
}
/**1. 카카오 [코드]를 넣고 -> 카카오 [토큰]을 받아오기 */
const response = await getTokenFromKakao(router.query.code as string);
/**2. 카카오 [토큰]을 넣고 -> 카카오 [유저정보] (id, nickname 등.. )를 받아오기 */
const responseUser = await getUserFromKakao({ access_token: response?.access_token as unknown as string }) // id, nickname 등이 담긴 객체
const result = await signin({ token: responseUser?.id as unknown as string });
if(result.status === 200){
console.log('result (signin 호출 성공)............', result);
}
/** 3. 처음 로그인한 사람이면, 회원가입 페이지로 이동 시키고, 아니면 로그인 세션에 데이터 저장 */
if (result.status == 400) {
const request = {
"token": responseUser?.id as unknown as string,
"nick": responseUser?.properties?.nickname,
"loginType": "kakao"
};
await kakaoSignUp(request);
} else if (result.status == 200) {
// 로그인 토큰 저장
sessionStorage.setItem("parkgolfMemberToken", result.data.accessToken);
sessionStorage.setItem(
"parkgolfMemberRefreshToken",
result.data.refreshToken
);
console.log('sessionStorage...............', sessionStorage)
window.location.href = "/member/mainPage/home"; // 페이지 이동
} else {
}
}
/**
|--------------------------------------------------
| 카카오 회원가입
|--------------------------------------------------
*/
async function kakaoSignUp(request: LoginInfoModel) {
if (request.token !== undefined) {
console.log('request .....??', request)
const resultSignUp = await signup(request);
// 회원가입 성공하면 로그인 페이지로 라우팅
if (resultSignUp.status == 200) {
console.log(" #################################### 회원가입 완료 #################################### " )
console.log('resultSignUp (signup 호출 성공)............', resultSignUp);
router.push('/member/login/login')
}
}
}
/**
|--------------------------------------------------
| ⚡ 5. useEffect
|--------------------------------------------------
*/
useEffect(() => {
kakaoLogin();
}, [router.query.code]);
/*
|--------------------------------------------------
| 🎨 화면
|--------------------------------------------------
*/
return (
<div>
<body className="white-type">
<div className="header">
<div className="top-bar">
<div className="prev" >
<a className="prev-btn" onClick={moveHome}>
<i className="icon icon-prev" ></i>
로그인
</a>
</div>
</div>
</div>
<div className="section">
<div className="wrapper">
<div className="kakao-update-box">
<div className="kakao-icon"></div>
<p className="update-message">카카오톡 간편 로그인</p>
</div>
</div>
</div>
<div className="btn-wrap bottom-full-type">
<a className="btn gray-btn" onClick={moveHome}>닫기</a>
<a className="btn kakao-btn" onClick={handleKakaoLogin}>
<i className="icon ico-kakao" ></i> 카카오로그인
</a>
</div>
</body>
</div>
);
}
export default LoginPage;
참고
https://developers.kakao.com/docs/latest/ko/kakaologin/common
'Frond-end > React' 카테고리의 다른 글
[React] 네이버 지도 연동하기 (0) | 2024.03.12 |
---|---|
[React] 글과 이미지가 같이 있는 글 저장해서 보여주는 방법 (0) | 2024.03.02 |
[React] 좋아요 적용 (0) | 2024.02.27 |
[React] 리액트에서 텍스트 줄바꿈(개행)하는 방법 (whiteSpace: "pre-wrap") (0) | 2024.02.26 |
[React] datepicker 적용해보기 (0) | 2024.02.21 |