Flutter 기반 iOS 앱에 카카오 로그인 설정하기
개요
이 문서는 개발을 끝낸 후 몇 달이 지나 기억과 ai를 이용해서 작성했다. 최근 개발하는 시점에서는 이 문서와 일치하지 않을 수 있다.
Flutter를 이용해 앱을 개발할 때 카카오 로그인 기능을 구현해야 할 때가 있다. 이 경우 kakao_flutter_sdk 같은 카카오 네이티브 SDK를 쓰지 않는다. 대신 REST API 키로 카카오 authorize 페이지를 직접 여는 웹 OAuth 방식을 사용한다. 흔한 “카카오 SDK 연동” 가이드와 달리 iOS 네이티브 앱 키 등록이나 번들 ID 등록이 필요 없다 — Redirect URI 등록과 REST API 키만 있으면 된다.
전체 OAuth 설계(2단계 인증 흐름, provider별 응답 형식 차이 등)는 OAuth2 소셜 로그인 문서에 정리되어 있다. 이 문서는 그중 Flutter + iOS(시뮬레이터/실제 기기) + 로컬 백엔드 조합에서 실제로 돌려보는 방법에 집중한다.
이 문서가 다루는 범위는 다음과 같다.
- Kakao Developers에서 로컬 백엔드용 Redirect URI 등록
- iOS 프로젝트의 URL Scheme(딥링크) 확인
.env환경변수 설정- 로컬 Django 서버 + iOS 시뮬레이터 동시 실행
- 실제 기기(iPhone)에서 테스트할 때 추가로 필요한 설정 (개발서버 외부 접근 허용)
- 로그인 전체 시퀀스 이해 및 트러블슈팅
시뮬레이터 vs 실제 기기: 시뮬레이터는 Mac과 네트워크 네임스페이스를 공유하므로 http://localhost:8000으로 바로 접근된다. 실제 기기는 별도의 네트워크 장치라 localhost가 iPhone 자신을 가리켜 접속이 안 된다 — Mac IP로 개발서버를 열어야 하며, 필요한 추가 설정은 6절에 정리했다.
1. Kakao Developers — Redirect URI 등록
Kakao Developers → 애플리케이션 추가.
앱 > 일반 > 앱 기본 정보:
- 앱 아이콘 등록
- 카카오 회원 인증 또는 사업자 번호 입력 → 비즈앱 등록 (이메일 scope 필요 시)
카카오 로그인 > 일반:
- 사용 설정: On
- OpenID Connect: On
카카오 로그인 > 개인정보:
- 닉네임
- 프로필 사진
- 카카오 계정(이메일)
카카오 로그인 > Redirect URI:
http://localhost:8000/api/auth/kakao/callback/
앱이 직접 리다이렉트를 받지 않는다. iOS 앱은 https:// 퍼블릭 URL을 가질 수 없으므로, Kakao의 Redirect URI는 항상 로컬 백엔드(localhost:8000)를 가리킨다. 백엔드가 이 요청을 받아서 다시 앱의 커스텀 URL 스킴으로 302 리다이렉트하는 구조다 — 자세한 흐름은 7절 참고.
앱 > 앱 키 > REST API 키 복사 → 뒤에서 KAKAO_CLIENT_ID로 사용.
네이티브 앱 키 / iOS 번들 ID 등록은 건너뛴다. 카카오 로그인 > 플랫폼 > iOS 플랫폼 등록 화면에서 번들 ID를 요구하는 경우가 많은데, 이는 kakao_flutter_sdk(네이티브 SDK) 사용 시에만 필요하다. 이 프로젝트는 REST API 키로 kauth.kakao.com/oauth/authorize를 웹뷰로 여는 방식이라 Redirect URI 등록 하나로 충분하다.
2. iOS 프로젝트 — URL Scheme 확인
flutter_web_auth_2는 OAuth 콜백을 가로채기 위해 iOS CFBundleURLTypes에 등록된 커스텀 스킴이 필요하다. ios/Runner/Info.plist에 이미 등록되어 있다.
ios/Runner/Info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.myapp.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>이 스킴 문자열은 코드에서 다음 상수와 반드시 일치해야 한다.
lib/config/api_config.dart
class ApiConfig {
// ...
static const String deepLinkScheme = 'myapp';
}새 프로젝트에 이 구조를 옮겨 쓸 경우, 이 두 곳(Info.plist + 상수)의 스킴 문자열을 같이 바꿔야 한다. 하나만 바꾸면 콜백이 영원히 캡처되지 않고 ASWebAuthenticationSession이 멈춘 것처럼 보인다.
3. .env 설정 (시뮬레이터용)
cp .env.sample .env.env에서 카카오 관련 값만 채운다.
.env
KAKAO_CLIENT_ID=<REST API 키>
# API_BASE_URL은 주석 처리된 상태로 둔다 — 코드 기본값이 이미 localhost:8000
#API_BASE_URL=http://localhost:8000lib/config/api_config.dart
static const String _defaultDebugBaseUrl = 'http://localhost:8000';
static const bool _isRelease = bool.fromEnvironment('dart.vm.product');
static String get baseUrl => _envBaseUrl.isNotEmpty
? _envBaseUrl
: (_isRelease ? _defaultReleaseBaseUrl : _defaultDebugBaseUrl);dart.vm.product가 false인 debug 빌드(시뮬레이터 실행은 항상 debug)에서는 API_BASE_URL을 비워두면 자동으로 http://localhost:8000을 쓴다.
4. 로컬 Django 서버 실행 (시뮬레이터)
백엔드 저장소에서:
python manage.py runserver시뮬레이터는 Mac과 네트워크를 공유하므로 0.0.0.0 바인딩이나 ALLOWED_HOSTS 추가 없이 기본 127.0.0.1:8000이면 충분하다.
실제 기기(iPhone)를 USB로 연결해서 테스트할 경우 이 명령으로는 안 된다 — 6절의 추가 설정이 필요하다.
5. iOS 시뮬레이터 실행
# 시뮬레이터 앱 열기 (선택)
make sim
# 기본 기기(iPhone 16e)로 실행
make iphone16
# 다른 기기로 실행
make iphone16 IPHONE16="iPhone 16 Pro"make iphone16이 내부적으로 하는 일:
Makefile
define set_env
@BUNDLE_ID=$$(grep '^BUNDLE_ID=' $(1) | cut -d'=' -f2); \
TEAM=$$(grep '^DEVELOPMENT_TEAM=' $(1) | cut -d'=' -f2); \
echo "APP_BUNDLE_ID=$$BUNDLE_ID" > ios/Flutter/Env.xcconfig; \
echo "DEVELOPMENT_TEAM=$$TEAM" >> ios/Flutter/Env.xcconfig
endef
iphone16:
$(call set_env,.env)
flutter run -d "$(IPHONE16)" --dart-define-from-file=.env.env의BUNDLE_ID/DEVELOPMENT_TEAM을 XcodeEnv.xcconfig에 주입 (코드 서명용).env전체를--dart-define-from-file로 Dart 컴파일 타임 상수에 주입 →ApiConfig.kakaoClientId등이 채워짐
6. 실제 기기(iPhone)에서 테스트하기
실제 기기는 시뮬레이터와 달리 Mac과 별개의 네트워크 장치다. localhost:8000은 iPhone 자신을 가리키므로, 개발서버를 Mac의 로컬 IP로 외부에 열어야 한다.
6.1 Mac IP 확인
ipconfig getifaddr en0와이파이를 바꾸거나 재접속하면 IP가 달라질 수 있다(DHCP). 아래 2~4단계는 IP가 바뀔 때마다 다시 맞춰야 한다. 매번 다시 맞추는 게 번거로우면 Mac IP를 고정해두는 방법도 있다.
6.2 Django 서버를 외부에 개방
python manage.py runserver 0.0.0.0:80000.0.0.0으로 바인딩해야 Mac의 외부 인터페이스(Wi-Fi)로 들어오는 요청을 받는다. 기본값인 127.0.0.1은 Mac 자기 자신만 접근 가능하다.
ALLOWED_HOSTS에 Mac IP를 추가해야 Django가 요청을 거부하지 않는다.
settings.py
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'YOUR_MAC_IP']또는 개발서버에서만 모두 허용하기 위해 다음과 같이 해도 된다.
settings/local.py
ALLOWED_HOSTS = ['*']처음 0.0.0.0으로 띄우면 macOS가 “수신 네트워크 연결을 허용하시겠습니까?” 방화벽 팝업을 띄울 수 있다. 허용을 눌러야 실제 기기에서 접속된다. 시스템 설정 → 네트워크 → 방화벽에서 나중에 다시 확인/허용할 수도 있다.
6.3 Kakao Developers에 Mac IP용 Redirect URI 추가
기존 localhost 항목은 시뮬레이터용으로 남겨두고, Mac IP 항목을 추가로 등록한다 (카카오 로그인 > Redirect URI).
http://localhost:8000/api/auth/kakao/callback/
http://<MAC_IP>:8000/api/auth/kakao/callback/
6.4 .env.device 설정
cp .env.sample .env.device.env.device
KAKAO_CLIENT_ID=<REST API 키> # 시뮬레이터용 .env와 동일한 값
API_BASE_URL=http://<MAC_IP>:8000 # 실제 기기 전용 — 반드시 채워야 함.env(시뮬레이터용)와 달리 API_BASE_URL을 비워두면 안 된다. 비어 있으면 ApiConfig.baseUrl이 debug 기본값인 http://localhost:8000으로 떨어지는데, 이는 iPhone 입장에서 자기 자신이라 절대 개발서버에 닿지 못한다 (A-8 참고).
6.5 실제 기기 연결 후 실행
Mac에 iPhone을 USB로 연결한 뒤:
make iphoneMakefile
iphone:
$(call set_env,.env.device)
flutter run -d $(shell flutter devices 2>/dev/null | grep '• ios' | grep -v simulator | head -1 | sed 's/.*• \([^ ]*\) *• ios.*/\1/') --dart-define-from-file=.env.device.env 대신 .env.device를 읽어 Env.xcconfig와 Dart 컴파일 타임 상수를 채운다 — API_BASE_URL이 Mac IP로 주입되는 지점이 여기다.
최초 설치 시: 설정 → 일반 → VPN 및 기기 관리 → 개발자 앱에서 신뢰 필요. 무료 Apple 개발자 계정으로 서명한 경우 7일마다 재설치해야 한다.
7. 로그인 시퀀스
핵심 포인트: Kakao의 Redirect URI는 로컬 백엔드를 가리키고, 백엔드가 받은 code를 즉시 소비하지 않고 앱의 myapp:// 스킴으로 한 번 더 리다이렉트한다. flutter_web_auth_2는 이 두 번째 리다이렉트(커스텀 스킴)만 가로챈다. 실제 카카오 토큰 교환은 앱이 code를 다시 POST로 보낸 시점에 백엔드에서 일어난다.
실제 기기 테스트 시: 위 다이어그램의 localhost:8000을 <MAC_IP>:8000으로 바꿔서 읽으면 된다 — 로직은 동일하고, redirect_uri/API_BASE_URL이 가리키는 호스트만 다르다.
부록 A: 트러블슈팅
공식 문서: Kakao Developers — 카카오 로그인 트러블슈팅
A-4. 로그인 페이지는 뜨는데 로그인 후 앱으로 안 돌아옴 (세션 멈춤)
원인: CFBundleURLSchemes(Info.plist)와 ApiConfig.deepLinkScheme 문자열 불일치, 또는 Info.plist 수정 후 clean build 안 함.
해결:
grep -A3 CFBundleURLSchemes ios/Runner/Info.plist
grep deepLinkScheme lib/config/api_config.dart
flutter clean && make iphone16A-5. Connection refused / 네트워크 에러 (코드 POST 단계)
원인: 로컬 Django 서버가 안 떠 있거나 다른 포트에서 실행 중.
해결: python manage.py runserver가 8000 포트로 떠 있는지, .env의 API_BASE_URL이 비어있어 기본값(localhost:8000)을 쓰고 있는지 확인.
A-6. 매번 다른 카카오 계정으로 테스트하고 싶은데 자동 로그인됨
원인: 시뮬레이터 내 웹뷰(Safari 세션)에 카카오 로그인이 유지됨.
해결: 시뮬레이터에서 카카오 계정 로그아웃(브라우저로 accounts.kakao.com 접속 후 로그아웃) 하거나, 새 시뮬레이터 기기를 만들어 테스트.
A-7. Hot Restart 직후 콜백이 중복 처리되며 에러
원인: Authorization Code는 1회용. Flutter Hot Restart(R)로 상태가 재구성되며 이전 콜백이 다시 처리되는 경우가 있다 (Apple 로그인 문서의 “dev 모드 이중 실행”과 같은 부류의 문제).
해결: 로그인 플로우 테스트 중에는 Hot Restart 대신 앱을 완전히 재시작(q → make iphone16)해서 확인.
A-8. (실제 기기) 카카오 로그인 후 앱으로 안 돌아오거나 흰 화면 / 응답 없음
원인: .env.device의 API_BASE_URL을 비워둠 → ApiConfig.baseUrl이 debug 기본값인 http://localhost:8000으로 빠짐. iPhone 입장에서 localhost는 iPhone 자기 자신이라 Mac의 Django 서버에 닿을 수 없다.
해결: .env.device에 API_BASE_URL=http://<MAC_IP>:8000을 반드시 채운다 (6.4절 참고). 시뮬레이터용 .env처럼 주석 처리하면 안 된다.
A-10. (실제 기기) Django DisallowedHost 에러
원인: ALLOWED_HOSTS에 Mac IP가 없는 상태로 0.0.0.0:8000을 띄움.
해결: settings.py의 ALLOWED_HOSTS에 Mac IP 추가 후 서버 재시작.
A-11. (실제 기기) 서버는 떠 있는데 iPhone에서 아예 연결이 안 됨 (타임아웃)
원인: macOS 방화벽이 Python(Django)의 수신 연결을 차단. 또는 Mac과 iPhone이 서로 다른 Wi-Fi(예: iPhone이 개인 핫스팟/게스트망)에 붙어 있음.
해결: - 시스템 설정 → 네트워크 → 방화벽에서 Python/터미널 수신 연결 허용 - Mac과 iPhone이 같은 Wi-Fi에 연결되어 있는지 확인
참고 링크
- OAuth2 소셜 로그인 — Google/Naver/Kakao 공통 백엔드 설계
- Kakao Developers — REST API 카카오 로그인