feat(auth): Apple 로그인 verifier 구현 — JWKS 서명 검증 - #458
Merged
Merged
Conversation
`/auth/social/apple` 이 501 을 반환하고 있었다. (#330) Apple 은 카카오/구글과 달리 토큰을 확인해 주는 조회 엔드포인트가 없다. 클라이언트가 받은 identity_token 자체가 JWT 이고 서버가 직접 검증해야 해서, 이 verifier 만 구조가 다르다. PyJWKClient 로 Apple 공개키를 캐싱하며 가져와(키 회전 자동 추적) 서명·iss· aud·exp 를 확인한다. **aud 확인이 핵심이다.** 이걸 빼면 다른 앱용으로 발급된 유효한 Apple 토큰으로도 로그인이 뚫린다. 허용 목록은 APPLE_CLIENT_IDS 로 받으며, iOS 는 번들 ID·웹은 Service ID 로 서로 다른 aud 를 받으므로 복수를 허용한다. 설정이 비어 있으면 검증을 **건너뛰지 않고 거부**한다. 다른 provider 는 키가 없으면 폴백하지만(카카오→시드) 인증은 폴백 대상이 아니다. 다만 클라이언트에는 라우터가 일반화된 401 을 주므로, 운영자가 원인을 알 수 있도록 설정 문제임을 로그로 남긴다. 테스트는 RSA 키쌍을 만들어 JWKS 를 대신해 네트워크 없이 돈다. 통과 경로뿐 아니라 **뚫리는 경로가 실제로 막히는지**를 본다: 만료·aud 불일치·iss 위조·남의 키 서명· alg=none 강등·sub 누락. 엔드포인트가 더는 501 이 아니고 다른 provider 와 동일한 401 로 거절하는 것(응답으로 구현 여부가 드러나지 않도록)도 고정했다.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughApple 소셜 로그인 스텁을 실제 JWT 검증 흐름으로 교체했습니다. 허용 client ID 설정, Apple JWKS 키 조회와 캐싱, 필수 클레임 검증, 오류 응답 및 테스트를 추가했습니다. ChangesApple 소셜 로그인
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AppleEndpoint as Apple 인증 엔드포인트
participant AppleVerifier
participant AppleJWKSClient
participant SocialIdentity
AppleEndpoint->>AppleVerifier: Apple JWT 전달
AppleVerifier->>AppleJWKSClient: kid 기반 공개키 조회
AppleJWKSClient-->>AppleVerifier: 공개키 반환
AppleVerifier->>AppleVerifier: JWT 검증
AppleVerifier->>SocialIdentity: 사용자 ID와 이메일 전달
SocialIdentity-->>AppleEndpoint: 인증 결과 반환
Possibly related issues
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/social/apple.py`:
- Around line 79-81: Update the Apple token verification flow around
get_signing_key_from_jwt and verify so the blocking JWKS lookup runs via
asyncio.to_thread or the project’s thread-pool utility, and await its result
from the async path. Preserve the existing SocialAuthError wrapping for lookup
failures.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 2051ae28-1053-4871-b3f7-c3f305640c85
📒 Files selected for processing (4)
backend/.env.examplebackend/app/core/config.pybackend/app/services/social/apple.pybackend/tests/test_social_apple.py
`PyJWKClient` 는 urllib 기반이라 동기 블로킹인데, async 핸들러에서 그대로 호출하고 있었다. 캐시가 비었거나 Apple 이 키를 회전한 직후에는 여기서 실제 HTTP 요청이 나가고, 그동안 **이벤트 루프 전체가 멈춰** 소셜 로그인과 무관한 요청까지 함께 지연된다. 다른 provider 는 httpx.AsyncClient 라 이 문제가 없었고 이 verifier 만 예외였다. asyncio.to_thread 로 넘겨 루프를 놓아 준다. JWKS 조회 타임아웃도 5초로 낮췄다. PyJWKClient 기본값은 30초인데, 스레드로 넘겨 루프는 안 막히더라도 그 스레드가 30초씩 잡혀 있을 이유가 없다(다른 provider 의 httpx timeout=5.0 과 맞췄다). 조회가 루프 스레드가 아닌 곳에서 실행되는지 테스트로 고정했다. to_thread 를 되돌리면 실제로 실패하는 것을 확인했다.
3 tasks
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #330
Summary
POST /auth/social/apple이 501(미지원)을 반환하고 있었습니다. Apple identity_token 을Apple 공개키로 직접 검증하도록 구현해 #330 의 백엔드 항목을 채웁니다.
Apple 은 카카오/구글과 달리 토큰을 확인해 주는 조회 엔드포인트가 없습니다.
클라이언트가 받은 identity_token 자체가 JWT 이고 서버가 서명을 직접 검증해야 해서,
이 verifier 만 다른 provider 와 구조가 다릅니다.
Changes
AppleVerifier구현 —PyJWKClient로 Apple 공개키를 캐싱하며 가져오고(키 회전자동 추적) 서명 ·
iss·aud·exp를 검증APPLE_CLIENT_IDS설정 추가(콤마 구분) +.env.example문서화기존 의존성만 씁니다(PyJWT + cryptography). 새 패키지 없음.
Notes
aud확인이 이 구현의 핵심입니다. 서명만 보고aud를 확인하지 않으면 다른앱용으로 발급된 유효한 Apple 토큰으로도 우리 서비스에 로그인이 됩니다. iOS 는 번들
ID, 웹은 Service ID 로 서로 다른
aud를 받으므로 목록으로 받습니다.설정이 비어 있으면 검증을 건너뛰지 않고 거부합니다. 이 저장소의 다른 통합은 키가
없으면 폴백하지만(카카오 → 시드 데이터, 임베더 → 해시), 인증은 폴백 대상이 아닙니다.
다만 클라이언트에는 라우터가 일반화된 401 을 주므로, 운영자가 "토큰이 잘못됐나" 를
들여다보지 않도록 설정 문제임을 서버 로그에 남깁니다.
테스트는 통과 경로보다 뚫리는 경로에 무게를 뒀습니다. RSA 키쌍을 만들어 JWKS 를
대신하므로 네트워크가 필요 없습니다(CI 에 Apple 자격증명이 없고, 있더라도 실제 Apple
토큰은 재현 불가):
test_token_for_another_app_is_rejectedtest_expired_token_is_rejectedtest_forged_issuer_is_rejectedtest_token_signed_by_another_key_is_rejectedalg=none강등test_unsigned_token_is_rejectedtest_missing_client_id_config_refuses_instead_of_skipping엔드포인트가 더는 501 이 아니고, 다른 provider 와 동일한 401 로 거절하는 것도
고정했습니다 — 응답만 보고 어떤 provider 가 구현됐는지 알 수 없어야 합니다.
Apple 도입 계획은 없습니다 — 그럼에도 구현하는 이유
Apple 로그인은 유료 개발자 계정이 필요해 현재 도입 계획이 없습니다. 그럼에도
스텁을 구현으로 바꾸는 이유는 두 가지입니다.
알 수 있습니다. 이제 다른 provider 와 동일한 401 로 거절합니다.
APPLE_CLIENT_IDS를 비워 두면 Apple 경로는깨끗하게 거부되고 카카오·구글에는 아무 영향이 없습니다. 나중에 계정이 생기면
설정 한 줄만 넣으면 됩니다.
범위 밖 — #330 은 열어 둡니다
#330 은 항목이 셋인데 이 PR 은 그중 백엔드 하나만 처리합니다. 애플을 도입하지 않더라도
나머지 둘은 그대로 남으므로
Closes가 아니라Part of입니다.프론트 연동(
sign_in_page.dart:72의demo-<provider>-token)은 Kakao 네이티브 앱 키 ·Google OAuth 클라이언트 ID · URL 스킴 등록 같은 플랫폼 설정과 실기기 검증이 필요해
별도로 진행합니다. 전역
USE_MOCK_API구조상 소셜 로그인만 실서버로 켜려면 #457 이함께 있어야 데모를 깨지 않고 시연할 수 있습니다.
테스트: 백엔드 전체 통과(신규 12개 — 검증 우회 시나리오 6종 + 이벤트 루프 차단 방지).
Summary by CodeRabbit