카카오·네이버 소셜 로그인 연동 실전 가이드
소셜 로그인은 사용자가 별도의 회원가입 절차 없이 카카오·네이버 등 기존 계정으로 서비스에 로그인할 수 있게 해주는 기능입니다. OAuth 2.0 프로토콜을 기반으로 동작하며, 국내 서비스에서는 카카오 로그인과 네이버 로그인이 가장 널리 쓰입니다.
두 플랫폼을 동시에 지원하면 국내 사용자 대부분을 커버할 수 있어 가입 전환율 향상에 큰 도움이 됩니다. 이 글에서는 FastAPI 백엔드 + SvelteKit 프론트엔드 기준으로 실전 구현 방법을 단계별로 안내합니다.
OAuth 2.0 인증 코드 흐름
카카오와 네이버 모두 Authorization Code Grant 방식을 사용합니다. 전체 흐름은 아래와 같습니다.
- 사용자가 소셜 로그인 버튼 클릭
- 서비스 서버가 카카오/네이버 인증 서버로 리다이렉트
- 사용자가 동의하면 인가 코드(code) 발급
- 서비스 서버가 code + client_secret으로 Access Token 요청
- Access Token으로 사용자 정보 조회
- 사용자 정보 기반 회원가입/로그인 처리 후 자체 JWT 발급
카카오 vs 네이버 주요 비교
| 항목 | 카카오 로그인 | 네이버 로그인 |
|---|---|---|
| 개발자 센터 | developers.kakao.com | developers.naver.com |
| REST API 키 이름 | REST API 키 | Client ID |
| 인증 URL | kauth.kakao.com/oauth/authorize | nid.naver.com/oauth2.0/authorize |
| 토큰 발급 URL | kauth.kakao.com/oauth/token | nid.naver.com/oauth2.0/token |
| 사용자 정보 URL | kapi.kakao.com/v2/user/me | openapi.naver.com/v1/nid/me |
| 이메일 제공 | 선택 동의 (비즈앱 필요) | 기본 제공 |
| state 파라미터 | 권장 | 필수 |
개발자 센터 앱 등록
카카오
- developers.kakao.com 접속 후 앱 생성
- 플랫폼 > 웹 사이트 도메인 등록
- 카카오 로그인 > 활성화 ON
- Redirect URI 등록 (예:
https://api.yourdomain.com/auth/kakao/callback) - 동의항목에서 필요한 사용자 정보 설정
- 요약 정보에서 REST API 키 확인
네이버
- developers.naver.com 접속 후 애플리케이션 등록
- 사용 API에서 네이버 로그인 선택
- 서비스 URL 및 Callback URL 등록
- Client ID와 Client Secret 확인
환경 변수 설정
# .env
KAKAO_CLIENT_ID=your_kakao_rest_api_key
KAKAO_CLIENT_SECRET=your_kakao_client_secret
KAKAO_REDIRECT_URI=https://api.yourdomain.com/auth/kakao/callback
NAVER_CLIENT_ID=your_naver_client_id
NAVER_CLIENT_SECRET=your_naver_client_secret
NAVER_REDIRECT_URI=https://api.yourdomain.com/auth/naver/callback
JWT_SECRET=your_jwt_secret_key
FastAPI 백엔드 구현
카카오 로그인
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
import httpx, secrets, os
from datetime import datetime, timedelta
import jwt
app = FastAPI()
KAKAO_CLIENT_ID = os.getenv("KAKAO_CLIENT_ID")
KAKAO_CLIENT_SECRET = os.getenv("KAKAO_CLIENT_SECRET")
KAKAO_REDIRECT_URI = os.getenv("KAKAO_REDIRECT_URI")
JWT_SECRET = os.getenv("JWT_SECRET")
@app.get("/auth/kakao/login")
async def kakao_login():
state = secrets.token_urlsafe(16)
url = (
"https://kauth.kakao.com/oauth/authorize"
f"?client_id={KAKAO_CLIENT_ID}"
f"&redirect_uri={KAKAO_REDIRECT_URI}"
f"&response_type=code"
f"&state={state}"
)
return RedirectResponse(url)
@app.get("/auth/kakao/callback")
async def kakao_callback(code: str, state: str):
async with httpx.AsyncClient() as client:
token_res = await client.post(
"https://kauth.kakao.com/oauth/token",
data={
"grant_type": "authorization_code",
"client_id": KAKAO_CLIENT_ID,
"client_secret": KAKAO_CLIENT_SECRET,
"redirect_uri": KAKAO_REDIRECT_URI,
"code": code,
}
)
token_data = token_res.json()
access_token = token_data.get("access_token")
async with httpx.AsyncClient() as client:
profile_res = await client.get(
"https://kapi.kakao.com/v2/user/me",
headers={"Authorization": f"Bearer {access_token}"}
)
profile = profile_res.json()
kakao_id = str(profile.get("id"))
nickname = profile.get("properties", {}).get("nickname", "")
jwt_token = create_jwt(kakao_id)
return {"access_token": jwt_token, "nickname": nickname}
def create_jwt(user_id: str) -> str:
payload = {
"sub": user_id,
"exp": datetime.utcnow() + timedelta(hours=24)
}
return jwt.encode(payload, JWT_SECRET, algorithm="HS256")
네이버 로그인
NAVER_CLIENT_ID = os.getenv("NAVER_CLIENT_ID")
NAVER_CLIENT_SECRET = os.getenv("NAVER_CLIENT_SECRET")
NAVER_REDIRECT_URI = os.getenv("NAVER_REDIRECT_URI")
@app.get("/auth/naver/login")
async def naver_login():
state = secrets.token_urlsafe(16)
url = (
"https://nid.naver.com/oauth2.0/authorize"
f"?client_id={NAVER_CLIENT_ID}"
f"&redirect_uri={NAVER_REDIRECT_URI}"
f"&response_type=code"
f"&state={state}"
)
return RedirectResponse(url)
@app.get("/auth/naver/callback")
async def naver_callback(code: str, state: str):
async with httpx.AsyncClient() as client:
token_res = await client.post(
"https://nid.naver.com/oauth2.0/token",
data={
"grant_type": "authorization_code",
"client_id": NAVER_CLIENT_ID,
"client_secret": NAVER_CLIENT_SECRET,
"redirect_uri": NAVER_REDIRECT_URI,
"code": code,
"state": state,
}
)
token_data = token_res.json()
access_token = token_data.get("access_token")
async with httpx.AsyncClient() as client:
profile_res = await client.get(
"https://openapi.naver.com/v1/nid/me",
headers={"Authorization": f"Bearer {access_token}"}
)
profile_data = profile_res.json()
user_info = profile_data.get("response", {})
naver_id = user_info.get("id")
email = user_info.get("email", "")
name = user_info.get("name", "")
jwt_token = create_jwt(naver_id)
return {"access_token": jwt_token, "email": email, "name": name}
SvelteKit 프론트엔드 연동
로그인 버튼 컴포넌트를 만들어 백엔드 엔드포인트로 리다이렉트합니다.
<script lang="ts">
const API_BASE = "https://api.yourdomain.com";
function kakaoLogin() {
window.location.href = `${API_BASE}/auth/kakao/login`;
}
function naverLogin() {
window.location.href = `${API_BASE}/auth/naver/login`;
}
</script>
<div class="social-login-buttons">
<button class="kakao-btn" on:click={kakaoLogin}>
카카오로 시작하기
</button>
<button class="naver-btn" on:click={naverLogin}>
네이버로 시작하기
</button>
</div>
<style>
.kakao-btn {
background-color: #FEE500;
color: #000000;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 700;
cursor: pointer;
width: 100%;
}
.naver-btn {
background-color: #03C75A;
color: #ffffff;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 700;
cursor: pointer;
width: 100%;
margin-top: 12px;
}
</style>
로그인 성공 후 콜백 페이지(src/routes/auth/callback/+page.svelte)에서 토큰을 저장합니다.
<script lang="ts">
import { onMount } from "svelte";
import { page } from "$app/stores";
import { goto } from "$app/navigation";
onMount(async () => {
const token = $page.url.searchParams.get("access_token");
if (token) {
localStorage.setItem("access_token", token);
goto("/dashboard");
} else {
goto("/login?error=auth_failed");
}
});
</script>
<p>로그인 처리 중입니다...</p>
보안 체크리스트
소셜 로그인 구현 시 반드시 확인해야 할 보안 항목입니다.
- state 파라미터 검증: CSRF 공격 방지를 위해 랜덤 값을 생성하고 세션에 저장 후 콜백 시 반드시 검증
- HTTPS 강제: Redirect URI 및 모든 API 통신에 HTTPS 적용 필수
- Client Secret 서버 보관: 절대 클라이언트 사이드에 노출하지 말 것
- Access Token 만료 처리: Refresh Token을 활용한 갱신 로직 구현
- Redirect URI 화이트리스트: 개발자 센터에 등록된 URI만 허용
자주 발생하는 오류와 해결법
# 오류: redirect_uri_mismatch
# 원인: 개발자 센터에 등록한 URI와 코드의 URI가 다름
# 해결: 슬래시 유무, http vs https, 포트 번호까지 완전히 일치시킬 것
# 오류: invalid_client
# 원인: Client ID 또는 Client Secret 오류
# 해결: 환경 변수 로드 여부 확인, 공백 포함 여부 점검
# 오류: KOE101 (카카오 한정)
# 원인: 카카오 로그인 미활성화
# 해결: 개발자 센터 > 카카오 로그인 > 활성화 ON
# 오류: state 검증 실패
# 원인: CSRF 방지용 state 불일치
# 해결: 로그인 요청 시 state를 서버 세션에 저장하고 콜백 시 비교
마무리
카카오와 네이버 소셜 로그인을 도입하면 가입 허들이 낮아져 전환율이 눈에 띄게 올라갑니다. 두 채널을 함께 지원하면 국내 사용자 대부분을 커버할 수 있습니다. state 파라미터 검증, HTTPS 적용, Client Secret 서버 보관이라는 3가지 보안 원칙만 지키면 안전하고 빠른 소셜 로그인을 구축할 수 있습니다.
코드벤터는 소셜 로그인 연동을 포함한 웹 서비스 풀스택 개발을 전문으로 합니다. 인증 시스템 구축부터 AWS 배포까지 빠르고 안전하게 함께 만들어가겠습니다. 🐾