티스토리 뷰
OAuth 2.0 카카오
카카오 개발자 센터에서 엄청 자세하게 나와있어 개발을 진행할 수 있지만 빠르게 이해하고 개발을 진행하려는 분들을 위해 간단하게 개발한 과정을 설명하려고 합니다.
카카오 싱크라는 더 많은 기능을 가진 것도 있지만 기본적인 것에 초점을 맞추고 카카오 로그인을 개발해 보도록 하겠습니다.
사실 카카오에서 만들어놓은 라이브러리와 같은 것이기 때문에 OAuth 2.0 소셜 로그인 개념을 통해 과정을 알게 되면 더 쉽게 구현을 할 수 있습니다.
대표 구현
SvelteKit와 카카오 로그인 방법 (REST API)를 이용하여 크게 4가지를 구현하게 됩니다.
(해당 내용에 대해 알고 싶으면 다음 글을 읽어보는 것을 추천합니다. )
- Authorization Code
- Token (Access, Refresh)
- User Info
- Logout ( Client + 카카오 계정)
간단하게 설명을 하면 OAuth 2.0의 과정으로 카카오 로그인을 눌러 카카오 인증 서버로 가서 카카오 인증을 하고 권한(Authorization Code)을 받아옵니다. -> Authorization Code를 가지고 User Resource(정보)에 접근할 수 있는 Access Token을 받아옵니다. -> Access Token을 가지고 카카오 Resource Server에 접근하여 설정한 범위에 맞게 User Info를 가져옵니다. -> Logout을 진행합니다.
즉, 홈 페이지(http://localhost:8080) -- 로그인 click --> Kakao 인증 페이지 -> 로그인 결과 페이지(http://localhost:8080/login_result) -- 로그아웃 click --> 홈 페이지(http://localhost:8080)
다음과 같은 순서로 작동을 진행하고 있다고 생각하시면 됩니다.
즉, 해당 그림에서 1 ~ 6번 + Logout 이 끝입니다.
이젠 조금 자세하게 들어가 보겠습니다.
과정
1. 카카오 개발자 센터에서 설정하기
카카오에서 정보를 가져오고 카카오에서 만든 API를 사용하기 때문에 기본 설정해야 하는 것들이 있습니다.
그래서 대표적인 것들만 적어두었고 이 부분만 해놓으면 개발자 센터에서 기본 설정은 한 것이라고 생각됩니다.
2. 카카오 로그인 방법 선택 및 시작 (REST API)
카카오 로그인 방법을 보면 REST API, JavaScript, Android, iOS, Flutter 등 많은 방법이 있지만 다음과 같은 이유로 REST API를 사용하게 되었습니다.
- 필요한 것만 가볍게 개발 및 업데이트
SDK와 API의 차이점이라고 볼 수 있을 것 같은데 SDK는 애플리케이션과 여러 특징들을 빌드하는 소프트웨어 개발 키트이고 API는 애플리케이션 프로그래밍 인터페이스입니다.
API | SDK | |
---|---|---|
의미 | Software Development Kit | Application Programming Interface |
목적 | 소프트웨어 연결 및 통합 | 다양한 개발 도구 포함 |
특징 | 가볍고 빠르며 전문화 | 견고하고 많은 기능 포함 |
이러한 특징이 있으며 쉽게 개발을 하지만 필요한 부분만 더 정확하게 보고 개발하기 위한 것이며 SDK는 업데이트를 진행하면 변경해야 하는 부분이 있기 때문에 추 후 변경상황이 많을 것 같다고 판단이 되어 REST API로 개발하는 방법을 선택하게 되었습니다.
- 웹 / 앱에서 사용 가능
블로그를 작성하는데 단순히 JavaScript, Flutter 등 단일 부분에서만 진행되는 것을 보면 적용하기 어렵다고 판단이 되었습니다. 그래서 여러 사람들이 이 블로그를 보고 다양하게 적용해 볼 수 있으면 좋겠다는 마음에서 REST API로 개발하는 방법을 선택하게 되었습니다.
- 가벼움을 선호하는 회사
마지막으로 회사에서 라이브러리를 거의 사용하지 않으며 가벼운 개발을 지향합니다.
그래서 SDK와 같이 필요하지 않은 부분까지 받아서 사용하면 회사의 방향성과 다른 점이 있기 때문에 REST API를 사용하게 되었습니다.
3. 코드에 적용
기능적인 측면에서 코드를 보여드리기 위해 Style 태그 부분을 제거하였습니다.
필요시 Github에서 확인 부탁드립니다.
(맨 밑 부분에도 링크가 있습니다.)
- 기본 Setting
Svelte를 이용하여 개발을 진행하였으니 Svelte Setting 먼저 하는 하시면 됩니다. (이 부분은 자유롭게)
코드에 적용되는 과정을 보기 전에 저의 간단한 Setting을 .env
를 보면 아실 수 있으며 최상단에 .env
file을 만들어 다음과 같이 설정을 해주시면 됩니다.
(npm install, npm run dev는 당연히 해주실 거죠?)
[.env]
PUBLIC_KAKAO_OAUTH_AUTHORIZE_URL=https://kauth.kakao.com/oauth/authorize/ // Authorize REST API URL
PUBLIC_KAKAO_OAUTH_TOKEN_URL=https://kauth.kakao.com/oauth/token // Token REST API URL
PUBLIC_KAKAO_OAUTH_LOGOUT_URL=https://kauth.kakao.com/oauth/logout // Logout REST API URL
PUBLIC_KAKAO_USER_INFO_URL=https://kapi.kakao.com/v2/user/me // Get User Info REST API URL
PUBLIC_KAKAO_REDIRECT_URI=http://localhost:8080/kakao_login // Setting Redirect URI (인증을 한 후 이동하는 주소)
PUBLIC_LOGOUT_REDIRECT_URI=http://localhost:8080/ // Setting Logout Redirect URI (로그아웃을 한 후 이동하는 주소)
PUBLIC_REST_API_KEY=${Your REST API KEY} // Setting Your REST API KEY (나의 REST API KEY)
위에서 설명했다시피 다음과 같은 구조로 되어 있으며 시작해 보겠습니다.
홈 페이지(http://localhost:8080) -- 로그인 click --> Kakao 인증 페이지 -> 로그인 결과 페이지(http://localhost:8080/login_result) -- 로그아웃 click --> 홈 페이지(http://localhost:8080)
- 홈 페이지 (http://localhost:8080)
카카오 로그인 Button만 만들어져 있고 카카오 Button을 눌렀을 시 카카오 인증 페이지로 이동하는 부분입니다.
<style>
.div_login_container_cls {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 8px;
}
.btn_login_cls {
width: 40%;
outline: none;
border: none;
display: inline;
background-color: transparent;
cursor: pointer;
}
.img_login_cls {
object-fit: contain;
width: 100%;
}
</style>
<svelte:head>
<title>Home</title>
<meta name="description" content="Social Login" />
</svelte:head>
<div class="div_login_container_cls">
<button on:click={on_click_kakao_login} class="btn_login_cls">
<img src={kakao_login} alt="kakao-login" class="img_login_cls" />
</button>
<button on:click={on_click_naver_login} class="btn_login_cls">
<img src={naver_login} alt="naver-login" class="img_login_cls" />
</button>
</div>
<script>
import {
PUBLIC_KAKAO_OAUTH_AUTHORIZE_URL,
PUBLIC_KAKAO_CLIENT_ID,
PUBLIC_KAKAO_REDIRECT_URI,
PUBLIC_NAVER_OAUTH_AUTHORIZE_URL,
PUBLIC_NAVER_CLIENT_ID,
PUBLIC_NAVER_REDIRECT_URI
} from '$env/static/public';
import kakao_login from '$lib/images/kakao-login.png';
import naver_login from '$lib/images/naver-login.png';
import { get_authorization_page_kakao } from '../apis/kakao_login.js';
async function on_click_kakao_login() {
// const authorize_page = await get_authorization_page_kakao();
// console.log(authorize_page);
const authorize_url = `${PUBLIC_KAKAO_OAUTH_AUTHORIZE_URL}?response_type=code&client_id=${PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${PUBLIC_KAKAO_REDIRECT_URI}`;
window.location.href = authorize_url;
}
async function on_click_naver_login() {
const authorize_url = `${PUBLIC_NAVER_OAUTH_AUTHORIZE_URL}?response_type=code&client_id=${PUBLIC_NAVER_CLIENT_ID}&state=test&redirect_uri=${PUBLIC_NAVER_REDIRECT_URI}`;
window.location.href = authorize_url;
}
</script>
[ 뽀너스: Kakao, Naver CORS 문제 ]
Kakao, Naver REST API 인가 코드 받기 부분을 개발하면서 .env
로 값을 숨기는 것을 목표로 개발을 진행하였고 Kakao, Naver에서 REST API 인가 코드 받기를 postman에서 요청을 해보니 Html
형태로 받아오는 것을 확인할 수 있었습니다. 그래서 fetch
를 이용해서Html
형식으로 받아와 보여주려고 했으나 다음과 같이 CORS 문제가 있었습니다.
그래서 headers: {'cors-proxy-url': ''}
와 package.json
에 proxy
설정하는 방식으로 해결하려 했지만 Html
을 잘 받아서 실행이 되지 않은 상황에서 '이렇게 해야 하나?' 라는 의문이 들었습니다. 그래서 가장 큰 목적을 생각해 보면 값을 숨기는 것이었습니다. 즉, .env
를 통해 값을 숨기고 싶은데 POST
형식 대신 GET
형식으로 진행을 하면 어차피 Network 부분에서 값이 보이게 됩니다. 그리고 Kakao에서도 POST
없이 GET
형식으로만 지원을 하고 있어 그냥 window.location.href
를 이용하여 값을 넣는 방법으로 진행돼도 괜찮을 것 같아 fetch
를 통해 인증하는 부분으로 넘어가지 않고 window.location.href
에 값을 넣어 주소 이동을 하여 문제를 해결했으나 그 후 Kakao는 Access Token, User Info에서 CORS 문제가 발생하지 않아 Client 부분에서 Access Token, User Info를 모두 fetch
를 통해 받아올 수 있었지만, Naver의 경우 CORS를 강하게 보며 Naver Docs에서도 Server에서 문제를 해결하라고 되어 있습니다.
그래서 간단하게 우회하여 해결하기 위해 Vite
의 vite.config.js
의 proxy
설정을 통해 문제를 해결하였습니다.
( Naver의 Access Token, User Info에 접근하는 API를 사용하지 않고 자동으로 결과 값이 나오는 페이지로 이동 )
우선 이것에 대해 알기 전에 SvelteKit
에 있는 Vite
와 package.json
의 관계에 대해서 설명하겠습니다.
package.json은 JavaScript 프로젝트의 설정 및 의존성을 정의하는 파일이며 프로젝트 루트 디렉터리에 위치하며, 명령어, 패키지 등을 포함합니다. => 프로젝트의 설정과 의존성 관리
Vite는 JavaScript 프로젝트에 빠른 개발 환경을 제공하는 도구입니다. => 개발 환경을 제공
Vite
프로젝트를 생성하면 자동으로 package.json
이 설정되며 연관성이 많습니다.
SvelteKit
에서 package.json
의 proxy
설정이 동작하지 않은 이유는 다음과 같습니다.
SvelteKit은 기본적으로 Vite를 사용하여 개발 환경을 구성합니다. Vite는 개발 서버에서 HTTP요청을 proxy 하는 데 사용할 수 있는 내장된 proxy 기능을 제공하는데 SvelteKit의 경우, Vite의 기본 proxy 설정이 package.json의 proxy 설정과 일치하지 않아 package.json의 proxy설정이 동작하지 않을 수 있는 것입니다.
그래서 다음과 같이 vite.config.js
에 proxy
를 설정하여 문제를 해결할 수 도 있습니다.
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
server: {
host: '0.0.0.0',
port: 8080,
proxy: {
'/naver_login': {
target: 'https://nid.naver.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/naver_login/, ''),
secure: false,
},
},
},
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
},
});
간단히 코드를 설명하면 /naver_login 앞의 origin을 target으로 설정한 'https://nid.naver.com'으로 바꾸고 /naver_login을 ''로 변경하여 CORS 문제를 해결하는 것입니다. (더 자세한 내용)
(좋은 방법이 있으면 알려주세요!!!)
그러나 본질을 해결하는 방법은 아닙니다.
=> SvelteKit에서 Server로 API를 구현하여 문제를 해결하여 GitHub 전체 주소에 있으니 확인하면 좋을 것 같습니다.
(Kakao 위주의 글이니 Pass)
- 로그인 결과 페이지(http://localhost:8080/login_result)
로그아웃 Button과 User Info를 받아와서 JSON 형식으로 보여주고 있습니다.
<style>
.div_result_container_cls {
width: 100%;
font-size: 13px;
}
.div_logout_container_cls {
display: flex;
justify-content: center;
align-items: center;
}
.btn_kakao_logout_cls {
width: 23%;
height: 45px;
margin-bottom: 16px;
outline: none;
border: none;
border-radius: 8px;
display: inline;
background-color: #fee501;
text-align: center;
font-weight: bold;
font-size: 16px;
cursor: pointer;
}
.div_user_info_cls {
width: 100%;
height: 100%;
padding: 8px;
border: 1px solid black;
border-radius: 4px;
}
</style>
<svelte:head>
<title>Result</title>
<meta name="description" content="Social Login Result" />
</svelte:head>
<div class="div_result_container_cls">
<div class="div_logout_container_cls">
<button on:click={on_click_kakao_logout} class="btn_kakao_logout_cls"> 카카오 로그아웃 </button>
</div>
<div class="div_user_info_cls">
{user_info}
</div>
</div>
<script>
import {
PUBLIC_KAKAO_CLIENT_ID,
PUBLIC_KAKAO_REDIRECT_URI,
PUBLIC_KAKAO_OAUTH_LOGOUT_URL,
PUBLIC_LOGOUT_REDIRECT_URI
} from '$env/static/public';
import { onMount } from 'svelte';
import { get_token_data_kakao, get_user_info_kakao } from '../../apis/kakao_login.js';
let user_info = '';
onMount(async () => {
const params = new URLSearchParams(window.location.search);
const authorization_code = params.get('code');
const access_token_params = {
grant_type: 'authorization_code',
client_id: PUBLIC_KAKAO_CLIENT_ID,
redirect_uri: PUBLIC_KAKAO_REDIRECT_URI,
code: authorization_code
};
const token_data = await get_token_data_kakao(access_token_params);
const user_data = await get_user_info_kakao(token_data.access_token);
user_info = JSON.stringify(user_data, null, 4);
});
async function on_click_kakao_logout() {
const logout_url = `${PUBLIC_KAKAO_OAUTH_LOGOUT_URL}?client_id=${PUBLIC_KAKAO_CLIENT_ID}&logout_redirect_uri=${PUBLIC_LOGOUT_REDIRECT_URI}`;
window.location.href = logout_url;
}
</script>
[API (src/apis/kakao_login.js)]
import {
PUBLIC_KAKAO_OAUTH_TOKEN_URL,
PUBLIC_KAKAO_USER_INFO_URL,
PUBLIC_KAKAO_OAUTH_AUTHORIZE_URL,
PUBLIC_KAKAO_CLIENT_ID,
PUBLIC_KAKAO_REDIRECT_URI
} from '$env/static/public';
export async function get_token_data_kakao(params) {
try {
const response = await fetch(PUBLIC_KAKAO_OAUTH_TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
body: new URLSearchParams(params)
});
if (!response.ok) throw new Error(`[Error] get_access_token_kakao status: ${response.status}`);
return await response.json();
} catch (error) {
console.log(error);
}
}
export async function get_user_info_kakao(access_token) {
try {
const response = await fetch(PUBLIC_KAKAO_USER_INFO_URL, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Bearer ${access_token}`
}
});
if (!response.ok) throw new Error(`[Error] get_user_info_kakao status: ${response.status}`);
return await response.json();
} catch (error) {
console.log(error);
}
}
전체 코드 Github: https://github.com/GeonWooPaeng/social_login
마치며
개념을 이해하고 난 후 개발을 진행하니 개발을 진행하는 과정이 정해져 있는 느낌이 들었고 보다 쉽게 개발을 할 수 있었던 것 같습니다.
개발을 진행하면서 Chat GPT를 사용해서 함수의 기능 등을 확인해 보니 구글링을 할 때보다 빠르다는 것을 알게 되었습니다. 또한 설명을 하나로 묶어주다 보니 여러 정보를 한눈에 볼 수 있어 깔끔했지만 확실하지 않은 정보일 수 있다는 생각이 들어 여러 정보를 보고 싶었습니다.
OAuth를 구현하면서 과정을 알게 되면 좀 더 빠르게 이해하면서 개발을 진행하여 오류를 좀 더 잘 찾을 수 있었고 가볍게 개발을 진행해서 많은 문제들을 겪고 문제를 해결하면서 서버를 제작해보고 여러 경험을 하고 API 문서를 더 잘 볼 수 있게 되었습니다. Naver도 구현해보니 Kakao와 다른 문제를 확인할 수 있어 좋은 경험이었습니다.
긴 글 읽어 주셔서 감사합니다.
Ref
https://developers.kakao.com/
https://openai.com/blog/chatgpt
https://gwpaeng.tistory.com/454
https://stackoverflow.com/questions/74169447/javascript-is-returning-html-instead-json
https://stackoverflow.com/questions/40602751/fetch-api-returning-html-instead-of-json
https://stackoverflow.com/questions/32500073/request-header-field-access-control-allow-headers-is-not-allowed-by-itself-in-pr
'Tip and Error' 카테고리의 다른 글
Program Upgrade (w. NW.js And Electron) (0) | 2023.07.01 |
---|---|
[Error] Error: package.json not found in srcDir file glob patterns (0) | 2023.06.30 |
1. OAuth 2.0 소셜 로그인 개념 (0) | 2023.06.12 |
Touch Slider (Image) (0) | 2023.06.05 |
첫 서비스 개발을 어떻게 잘 할 수 있을까? (0) | 2023.05.06 |