Skip to content

feat(oauth): Apple 로그인 구현#285

Merged
Mindev27 merged 2 commits intodevelopfrom
feat/apple-oauth
Feb 10, 2026
Merged

feat(oauth): Apple 로그인 구현#285
Mindev27 merged 2 commits intodevelopfrom
feat/apple-oauth

Conversation

@Mindev27
Copy link
Contributor

@Mindev27 Mindev27 commented Feb 10, 2026

  • Apple OAuth 로그인 구현
  • 기존 Google/Kakao/Naver OAuth 패턴과 동일한 구조

Summary by CodeRabbit

  • New Features
    • Added Apple Sign In support — users can now authenticate using their Apple account alongside existing OAuth providers.

@Mindev27 Mindev27 requested a review from huhdy32 February 10, 2026 06:50
@Mindev27 Mindev27 self-assigned this Feb 10, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Adds Apple OAuth support: new configuration binding, Apple OAuth client with ES256-signed client secret (uses nimbus-jose-jwt), token exchange and revocation, token/id_token models, bean registration, and an APPLE enum value.

Changes

Cohort / File(s) Summary
Build Configuration
domain/mathrank-auth-domain/build.gradle
Added dependency implementation 'com.nimbusds:nimbus-jose-jwt:10.7'.
Apple OAuth Configuration
domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleConfiguration.java
New @ConfigurationProperties class binding oauth.apple (clientId, teamId, keyId, privateKey) with conditional creation.
Response Models
domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleTokenResponse.java, domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleMemberInfoResponse.java
New records for token response and member info; AppleMemberInfoResponse implements MemberInfoResponse and maps id_token claims to MemberInfo.
OAuth Client Implementation
domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleOAuthClient.java
New AppleOAuthClient implementing OAuthClientHandler: exchanges tokens (auth code -> token), parses id_token, generates ES256 client_secret from PEM EC private key, and supports token revocation.
Framework Integration
domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/OAuthConfiguration.java, domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/entity/OAuthProvider.java
Registers AppleOAuthClient bean (conditional on AppleConfiguration) and adds APPLE enum constant to OAuthProvider.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AppleOAuthClient as Apple<br/>OAuth Client
    participant AppleAuthServer as Apple<br/>Auth Server
    participant JWTProcessor as JWT<br/>Signer/Parser

    Client->>AppleOAuthClient: getMemberInfo(code)
    AppleOAuthClient->>JWTProcessor: generateClientSecret() (ES256 sign using EC private key)
    JWTProcessor-->>AppleOAuthClient: client_secret (JWT)
    AppleOAuthClient->>AppleAuthServer: POST /auth/token (code, client_id, client_secret, grant_type)
    AppleAuthServer-->>AppleOAuthClient: AppleTokenResponse (id_token, access_token, refresh_token)
    AppleOAuthClient->>JWTProcessor: parseIdToken(id_token)
    JWTProcessor-->>AppleOAuthClient: claims (sub, email)
    AppleOAuthClient-->>Client: AppleMemberInfoResponse (sub, email)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 In moonlit code I hop and cheer,
I sign JWTs with whisker-dear,
Apple tokens safely brought near,
Nimbus hums — the path is clear! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: Apple OAuth login implementation. It directly corresponds to the substantial new code adding Apple OAuth support (AppleOAuthClient, AppleConfiguration, and supporting classes).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/apple-oauth

No actionable comments were generated in the recent review. 🎉


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 and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Mindev27, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 애플 OAuth 로그인 기능을 시스템에 통합하여 사용자 인증을 위한 새로운 옵션을 제공합니다. 기존에 구현된 Google, Kakao, Naver와 같은 다른 OAuth 제공자들의 패턴을 따르면서, 사용자 경험을 확장하고 더 많은 로그인 방법을 지원하기 위함입니다.

Highlights

  • Apple OAuth 로그인 구현: 새로운 Apple OAuth 로그인 기능이 시스템에 추가되었습니다.
  • 기존 OAuth 패턴 유지: Google, Kakao, Naver와 같은 기존 OAuth 제공자들의 구현 패턴과 동일한 구조를 사용하여 일관성을 유지했습니다.
  • 새로운 라이브러리 의존성 추가: Apple OAuth 연동을 위해 nimbus-jose-jwt 라이브러리가 의존성에 추가되었습니다.
  • Apple OAuth 관련 클래스 추가: Apple OAuth 클라이언트 설정, 멤버 정보 응답, 토큰 응답을 위한 전용 클래스들이 도입되었습니다.
  • OAuthProvider 확장: OAuthProvider enum에 APPLE이 추가되어 Apple 로그인 제공자를 식별할 수 있게 되었습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • domain/mathrank-auth-domain/build.gradle
    • com.nimbusds:nimbus-jose-jwt 의존성이 추가되었습니다.
  • domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleConfiguration.java
    • Apple OAuth 설정을 위한 AppleConfiguration 클래스가 추가되었습니다.
  • domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleMemberInfoResponse.java
    • Apple OAuth로부터 받은 멤버 정보를 처리하는 AppleMemberInfoResponse 레코드가 추가되었습니다.
  • domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleOAuthClient.java
    • Apple OAuth 인증 및 토큰 관리를 담당하는 AppleOAuthClient 클래스가 구현되었습니다.
  • domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleTokenResponse.java
    • Apple OAuth 토큰 응답을 위한 AppleTokenResponse 레코드가 추가되었습니다.
  • domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/OAuthConfiguration.java
    • AppleOAuthClient 빈을 등록하는 로직이 추가되었습니다.
  • domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/entity/OAuthProvider.java
    • OAuthProvider enum에 APPLE이 추가되었습니다.
Activity
  • 현재까지 이 PR에 대한 활동은 없습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Apple OAuth login functionality, adhering to existing patterns for other OAuth providers. However, critical security concerns were identified in the AppleOAuthClient implementation. The code lacks proper JWT signature and claim verification for the Apple id_token, and sensitive credentials (client secrets and tokens) are transmitted as query parameters in POST requests. Addressing these issues is essential to align with OpenID Connect security best practices and prevent potential credential leakage or account impersonation. Additionally, consider replacing magic numbers and strings with constants and improving exception handling for enhanced code clarity and robustness.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@domain/mathrank-auth-domain/build.gradle`:
- Line 7: The dependency declaration for com.nimbusds:nimbus-jose-jwt in
build.gradle is pinned to 9.47 which is vulnerable to CVE-2025-53864; update the
implementation coordinate 'com.nimbusds:nimbus-jose-jwt' in the build.gradle
(where implementation 'com.nimbusds:nimbus-jose-jwt:9.47' appears) to at least
10.0.2 (recommended 10.7+) and re-run dependency resolution/gradle build to
ensure the newer artifact is used and tests pass.

In
`@domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleOAuthClient.java`:
- Around line 134-149: The revoke(String) method currently uses retrieve() which
throws on 4xx/5xx so it never returns false; change revoke(String) to either (A)
perform a non-throwing exchange (use WebClient.exchangeToBodilessEntity or
exchangeToMono and inspect the ClientResponse status) and return
response.getStatusCode().is2xxSuccessful(), or (B) change the method signature
to void and let exceptions propagate; apply the chosen approach to the
revoke(...) method in AppleOAuthClient (reference: revoke(String) and
revokeClient.post()), and for getAccessToken(...) catch
RestClientResponseException thrown by WebClient.retrieve() and map it to your
InvalidOAuthLoginException (include response body/status in the exception) so
Apple errors produce the intended domain exception instead of raw
RestClientResponseException.
- Around line 67-80: The current parseIdToken method deserializes the JWT with
SignedJWT.parse but does not verify its signature or validate claims; update
parseIdToken to: parse the token (SignedJWT.parse), fetch Apple's JWKS from
https://appleid.apple.com/auth/keys, select the JWK by the token header's kid,
construct the RSA public key (or use RSAKey.toRSAPublicKey), create an
RSASSAVerifier and call signedJWT.verify(verifier) and throw
InvalidOAuthLoginException if verification fails; after successful verification,
validate claims.getIssuer() equals "https://appleid.apple.com",
claims.getAudience() contains your client_id, claims.getExpirationTime() is in
the future, and (if you use nonce) validate claims.getStringClaim("nonce"); only
then build and return the AppleMemberInfoResponse using claims.getSubject() and
claims.getStringClaim("email").
🧹 Nitpick comments (3)
domain/mathrank-auth-domain/src/main/java/kr/co/mathrank/domain/auth/client/AppleOAuthClient.java (3)

36-42: RestClient instances are inline-initialized final fields alongside a Lombok-injected final field.

This works because Lombok's @RequiredArgsConstructor only includes uninitialized final fields. However, the two RestClient fields being both final and inline-initialized alongside the constructor-injected appleConfiguration could confuse future maintainers. Consider removing final from the RestClient fields or adding a brief comment clarifying the initialization semantics.


84-108: Consider caching the loaded private key rather than re-parsing PEM on every request.

generateClientSecret() is called on every getMemberInfo and revoke invocation, and each call re-parses the PEM string and re-creates an ECDSASigner. The private key is immutable configuration — parse it once (e.g., in a @PostConstruct or lazy-init field) and reuse it.

♻️ Sketch: cache the ECPrivateKey
+import jakarta.annotation.PostConstruct;
+
 `@RequiredArgsConstructor`
 class AppleOAuthClient implements OAuthClientHandler {
 	private final AppleConfiguration appleConfiguration;
+	private ECPrivateKey cachedPrivateKey;
+
+	`@PostConstruct`
+	void init() {
+		this.cachedPrivateKey = loadPrivateKey(appleConfiguration.getPrivateKey());
+	}

 	private String generateClientSecret() {
 		try {
-			final ECPrivateKey ecPrivateKey = loadPrivateKey(appleConfiguration.getPrivateKey());
 			final Instant now = Instant.now();
             // ... unchanged ...
-			signedJWT.sign(new ECDSASigner(ecPrivateKey));
+			signedJWT.sign(new ECDSASigner(cachedPrivateKey));

110-125: Broad catch (Exception e) swallows all exceptions indiscriminately.

loadPrivateKey catches Exception, which could mask unexpected runtime errors (e.g., NullPointerException from a null config value). Narrow the catch to the expected checked exceptions (GeneralSecurityException, IllegalArgumentException for Base64 decode failures).

♻️ Proposed narrower catch
-		} catch (Exception e) {
+		} catch (java.security.GeneralSecurityException | IllegalArgumentException e) {
 			throw new InvalidOAuthLoginException("애플 개인 키 로딩에 실패했습니다.");
 		}

@Mindev27 Mindev27 merged commit 57a9ace into develop Feb 10, 2026
2 checks passed
@Mindev27 Mindev27 deleted the feat/apple-oauth branch February 10, 2026 07:57
Copy link
Collaborator

@huhdy32 huhdy32 left a comment

Choose a reason for hiding this comment

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

이렇게 빨리 업데이트 해주실 줄 몰랐네요! 이렇게 빨리 코드 베아스를 파악하시다니 ㄷㄷ

감사합니다.

리뷰에서 예외와 별도로 로그를 추가해달라 부탁드렸는데요, 이유는,

예외에 담기는 메시지는 api 사용자에게 전달됨으로 보안상 상세한 정보를 담지 않는 반면, 로그는 개발자의 디버깅 과정을 위해 예외의 상세한 콘텍스트를 담아야할 필요가 있기 때문입니다. (물론 credential 한 정보는 빼고요 )

따라서, 예외 처리에 담기는 내용과 별도로 자세한 콘텍스트를 더 담아주시면, 만일에 발생할 예외 상황에 대해 더 자세히 파악할 수 있을것 같습니다.

귀찮게 하는 것 같아 죄송하네요 ㅎㅎ
감사합니다.

Comment on lines +82 to +83
// Apple OAuth의 client_secret은 ES256으로 서명된 JWT이다.
// https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
Copy link
Collaborator

Choose a reason for hiding this comment

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

관련 링크 감사합니다

Comment on lines +77 to +79
} catch (ParseException e) {
throw new InvalidOAuthLoginException("애플 서버로부터 사용할 수 없는 메시지를 받았습니다.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

포맷에 맞게 로그만 남겨주시면 감사하겠습니다!!

Comment on lines +105 to +107
} catch (JOSEException e) {
throw new InvalidOAuthLoginException("애플 client_secret 생성에 실패했습니다.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

포맷에 맞게 로그만 남겨주시면 감사하겠습니다!!

Comment on lines +122 to +124
} catch (Exception e) {
throw new InvalidOAuthLoginException("애플 개인 키 로딩에 실패했습니다.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

포맷에 맞게 로그만 남겨주시면 감사하겠습니다!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants