Skip to content

feat(auth): Apple 로그인 verifier 구현 — JWKS 서명 검증 - #458

Merged
subin21cc merged 2 commits into
mainfrom
feat/apple-social-login
Aug 7, 2026
Merged

subin21cc merged 2 commits into
mainfrom
feat/apple-social-login

Conversation

@subin21cc

@subin21cc subin21cc commented Aug 7, 2026 •

Copy link
Copy Markdown
Collaborator

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 문서화
  • 검증 테스트 11개

기존 의존성만 씁니다(PyJWT + cryptography). 새 패키지 없음.

Notes

aud 확인이 이 구현의 핵심입니다. 서명만 보고 aud 를 확인하지 않으면 다른
앱용으로 발급된 유효한 Apple 토큰
으로도 우리 서비스에 로그인이 됩니다. iOS 는 번들
ID, 웹은 Service ID 로 서로 다른 aud 를 받으므로 목록으로 받습니다.

설정이 비어 있으면 검증을 건너뛰지 않고 거부합니다. 이 저장소의 다른 통합은 키가
없으면 폴백하지만(카카오 → 시드 데이터, 임베더 → 해시), 인증은 폴백 대상이 아닙니다.
다만 클라이언트에는 라우터가 일반화된 401 을 주므로, 운영자가 "토큰이 잘못됐나" 를
들여다보지 않도록 설정 문제임을 서버 로그에 남깁니다.

테스트는 통과 경로보다 뚫리는 경로에 무게를 뒀습니다. RSA 키쌍을 만들어 JWKS 를
대신하므로 네트워크가 필요 없습니다(CI 에 Apple 자격증명이 없고, 있더라도 실제 Apple
토큰은 재현 불가):

막아야 하는 것 테스트
다른 앱용 토큰 test_token_for_another_app_is_rejected
만료 토큰 test_expired_token_is_rejected
iss 위조 test_forged_issuer_is_rejected
남의 키로 서명 test_token_signed_by_another_key_is_rejected
alg=none 강등 test_unsigned_token_is_rejected
설정 누락 시 무검증 통과 test_missing_client_id_config_refuses_instead_of_skipping

엔드포인트가 더는 501 이 아니고, 다른 provider 와 동일한 401 로 거절하는 것도
고정했습니다 — 응답만 보고 어떤 provider 가 구현됐는지 알 수 없어야 합니다.

Apple 도입 계획은 없습니다 — 그럼에도 구현하는 이유

Apple 로그인은 유료 개발자 계정이 필요해 현재 도입 계획이 없습니다. 그럼에도
스텁을 구현으로 바꾸는 이유는 두 가지입니다.

  1. 501 스텁은 그 자체로 정보를 흘립니다. 응답만 보고 어떤 provider 가 미구현인지
    알 수 있습니다. 이제 다른 provider 와 동일한 401 로 거절합니다.
  2. 도입하지 않아도 비용이 없습니다. APPLE_CLIENT_IDS 를 비워 두면 Apple 경로는
    깨끗하게 거부되고 카카오·구글에는 아무 영향이 없습니다. 나중에 계정이 생기면
    설정 한 줄만 넣으면 됩니다.

범위 밖 — #330 은 열어 둡니다

#330 은 항목이 셋인데 이 PR 은 그중 백엔드 하나만 처리합니다. 애플을 도입하지 않더라도
나머지 둘은 그대로 남으므로 Closes 가 아니라 Part of 입니다.

  • 백엔드: Apple verifier 구현(현재 501) ← 이 PR
  • 프론트: Kakao/Google 네이티브 SDK 로 실제 토큰 획득 후 전송
  • 실패/취소 UX, 신규 사용자 온보딩 연결

프론트 연동(sign_in_page.dart:72 의 demo-<provider>-token)은 Kakao 네이티브 앱 키 ·
Google OAuth 클라이언트 ID · URL 스킴 등록 같은 플랫폼 설정과 실기기 검증이 필요해
별도로 진행합니다. 전역 USE_MOCK_API 구조상 소셜 로그인만 실서버로 켜려면 #457 이
함께 있어야 데모를 깨지 않고 시연할 수 있습니다.

테스트: 백엔드 전체 통과(신규 12개 — 검증 우회 시나리오 6종 + 이벤트 루프 차단 방지).

Summary by CodeRabbit

  • 새 기능
    • Apple 소셜 로그인의 JWT 토큰 검증을 지원합니다.
    • iOS 번들 ID와 웹 Service ID 등 여러 Apple 클라이언트 ID를 허용 목록으로 설정할 수 있습니다.
    • 서명, 발급자, 대상, 만료 기간 및 사용자 식별자를 확인해 안전한 로그인을 제공합니다.
  • 버그 수정
    • 유효하지 않거나 만료된 Apple 토큰은 일관된 인증 오류로 처리됩니다.
    • Apple 로그인 미설정 및 사용자 정보 누락 상황을 적절히 거부합니다.

`/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 로 거절하는 것(응답으로 구현 여부가 드러나지 않도록)도 고정했다.
@subin21cc subin21cc added enhancement New feature or request test Test additions or quality gates labels Aug 7, 2026
@subin21cc subin21cc self-assigned this Aug 7, 2026
@vercel

vercel Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sudo-capstone-project Ready Ready Preview Aug 7, 2026 5:05pm

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Apple 소셜 로그인 스텁을 실제 JWT 검증 흐름으로 교체했습니다. 허용 client ID 설정, Apple JWKS 키 조회와 캐싱, 필수 클레임 검증, 오류 응답 및 테스트를 추가했습니다.

Changes

Apple 소셜 로그인

Layer / File(s) Summary
Apple client ID 설정
backend/.env.example, backend/app/core/config.py
APPLE_CLIENT_IDS 환경 변수와 apple_client_ids 설정을 추가했습니다. 여러 Apple aud 값을 콤마로 지정하며, 값이 비어 있으면 검증을 거부합니다.
Apple JWT 검증 구현
backend/app/services/social/apple.py
Apple JWKS 공개키를 kid 기준으로 조회하고 캐시합니다. 서명, RS256, aud, iss, exp, sub를 검증한 뒤 SocialIdentity를 생성합니다.
검증 및 오류 응답 테스트
backend/tests/test_social_apple.py
허용 audience, 만료, issuer, 서명, alg=none, sub 누락, 설정 누락을 검증합니다. 잘못된 Apple 토큰이 401과 공통 오류 상세를 반환하는지도 확인합니다.

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: 인증 결과 반환
Loading

Possibly related issues

  • #330: 미구현 상태였던 AppleVerifier에 실제 Apple JWT 검증 로직을 추가했습니다.

Suggested reviewers: ajisua

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 제목이 Apple 로그인 verifier 구현과 JWKS 서명 검증이라는 핵심 변경을 정확하고 간결하게 설명합니다.
Description check ✅ Passed 설명에 구현 목적, 주요 변경, 보안 근거, 테스트, 범위 제외 사항이 포함되어 있어 요구사항을 대부분 충족합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/apple-social-login

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ceb18c9 and 43e6373.

📒 Files selected for processing (4)
  • backend/.env.example
  • backend/app/core/config.py
  • backend/app/services/social/apple.py
  • backend/tests/test_social_apple.py

Comment thread backend/app/services/social/apple.py Outdated
`PyJWKClient` 는 urllib 기반이라 동기 블로킹인데, async 핸들러에서 그대로 호출하고
있었다. 캐시가 비었거나 Apple 이 키를 회전한 직후에는 여기서 실제 HTTP 요청이 나가고,
그동안 **이벤트 루프 전체가 멈춰** 소셜 로그인과 무관한 요청까지 함께 지연된다.
다른 provider 는 httpx.AsyncClient 라 이 문제가 없었고 이 verifier 만 예외였다.

asyncio.to_thread 로 넘겨 루프를 놓아 준다.

JWKS 조회 타임아웃도 5초로 낮췄다. PyJWKClient 기본값은 30초인데, 스레드로 넘겨
루프는 안 막히더라도 그 스레드가 30초씩 잡혀 있을 이유가 없다(다른 provider 의
httpx timeout=5.0 과 맞췄다).

조회가 루프 스레드가 아닌 곳에서 실행되는지 테스트로 고정했다. to_thread 를 되돌리면
실제로 실패하는 것을 확인했다.
@subin21cc
subin21cc merged commit 0353692 into main Aug 7, 2026
5 checks passed
@subin21cc
subin21cc deleted the feat/apple-social-login branch August 7, 2026 17:36

This branch was successfully deployed

1 active deployment
Preview — b12aadf8 Deployed Aug 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request test Test additions or quality gates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant