Apple OAuth 구현 시 client_id 문제 해결 및 구현 방법

오현석·2025년 3월 25일
post-thumbnail

iOS 앱을 App Store에 등록하려면 반드시 Apple Login을 서비스가 지원해야 한다.

문제는 Apple Login이 다른 OAuth들과 비교했을 때 까다로운 편에 속한다는 것이다. 구현을 마친 시점에서 생각해본다면 확실히 다른 Kakao나 Google에 비해 오류를 많이 겪었던 것 같긴 하다. 아무래도 앱 서비스가 더 메인이다보니 내 입장에서는 좀 더 불편했나 싶기도 하지만, 다른 사람들이 구현한 후기들에서도 Apple 욕이 많았던 것을 보면 일반적인 편이 아니었나 생각이 든다.

어찌 됐든 서비스에 애플 로그인을 구현하게 되었고, 이 과정에서 겪었던 시행착오들과 고민들을 기록하고자 한다.

글을 쓰는 시점 이후로도 차근차근 되짚어가며 문제 경우를 추가할 예정이다.

1. 로그인 시 클라이언트는 서버에게 어떤 값을 넘겨줘야 할까?

Apple에서 제공하는 문서에서는 다음과 같은 Flow를 보여준다.

클라이언트에서는 사용자가 로그인(FaceID 혹은 Email 등)을 하고, 그 값으로 애플 서버에 요청을 보내 토큰을 받아온다.

여기서 클라이언트의 판단이 두 가지가 존재한다.

AuthenticationServices를 사용하여 ASAuthorizationAppleIDCredential 객체에서

1. identityToken을 백엔드 서버로 전송하기

2. authorizationCode를 백엔드 서버로 전송하기

결론을 미리 말하자면, 지금 상태에서 우리 서비스는 로그인 시에는 1번, 회원 탈퇴 시에는 2번을 사용한다.

identityToken 사용하기

identityToken을 사용해서 얻을 수 있는 장점은 추가적인 토큰 교환 과정 없이 사용자의 정보를 받을 수 있다는 것이다. 클라이언트는 서버로 이 토큰을 보내기만 하면 서버에서 이 토큰을 바로 검증해서 유효함을 확인하고, 토큰을 디코딩하여 사용자의 정보(이메일 등)을 빼낼 수 있다.

authorizationCode 사용하기

authorizationCode를 사용해서 얻을 수 있는 장점은 Apple 서버와 통신할 때 필요한 추가적인 토큰들을 얻을 수 있다는 것이다. accessToken이나 refreshToken 등을 받아 서버가 Apple과 추가적인 통신을 이어갈 수 있게 해주고, 특히 refreshToken을 알아낼 수 있기 때문에 장기적인 세션 관리에 유리하다.


내 개인적인 생각으로는 어차피 사용자의 정보만 가지고 바로 우리 서버 전용 JWT 토큰을 생성해주고 앞으로의 서버 통신은 이 JWT 토큰들을 사용할텐데, 로그인 시에는 identityToken을 사용하는 것이 속도를 높일 수 있다고 생각한다. 추가적인 이유는 밑에서 더 해보자.


대신 identityToken을 로그인 시에 사용하면 고민해봐야 할 것이 생긴다.

1.5 Apple 서버가 준 refreshToken은 처음 한번만 받을 수 있다고? 그러면 탈퇴 처리는 어떻게 하는 게 좋을까?

처음에는 서버에서만 사용자 데이터를 지우는 식으로 구현했다가, 앱스토어 심사 시 문제가 생길 것 같아 Apple 서버에서도 토큰을 Revoke 시키도록 했다.

아까 봤던 이미지 중에 두 번째 이미지를 다시 확인해보자.

여기서 authorizationCode를 사용하면 Apple 서버 측에서는 accessToken, refreshToken을 반환해준다.
이때, accessToken의 유효시간은 3600초지만, refreshToken의 유효시간은 없다...!

refreshToken의 유효시간이 존재하지 않는다는 것은 즉, 이 토큰은 계속해서 유효하다는 것이다. 이런 이유로 Apple 서버는 authorization code로 Apple의 access, refreshToken을 조회하려고 할 때, 처음 1회시에만 refreshToken을 제공하고 그 이후로는 refreshToken을 반환해주지 않는다.

나는 저번에 줬으니 그 값 계속 써라 이런 뉘앙스 같아서 좀 마음에 안 든다

문제는 저 refreshToken은 회원 탈퇴 시에 필요한 값일 수도 있다는 것이다.

여기 보면 token 값에 accessToken 혹은 refreshToken을 사용하라고 나와있다.

만약에 로그인 시에 authorization code를 사용하지 않고 identityToken을 사용했다면 서버 입장에서는 Apple 서버의 accessToken이나 refreshToken을 모르니 다시 로그인을 진행해야 한다. (authorization code는 유효기간이 5분이라 로그인 시점과 탈퇴 시점의 시간 차이을 견디지 못한다.)

혹은 authorization code를 사용하여 로그인을 구현했다고 해보자. 이렇게 한다고 해도 refreshToken을 따로 저장하지 않았다면 accessToken도 만료되었고, refreshToken은 모르는 상태일테니 다시 로그인을 진행해야 한다.

사람마다의 구현 차이는 존재하겠지만, 어떤 사람들은 authorization code를 로그인 시에 사용하여 DB에 Apple refreshToken을 저장해두고, 탈퇴 시에는 재로그인 없이 바로 이 토큰을 사용해서 회원 탈퇴를 진행한다.

그러나, 내 생각으로는 탈퇴라는 기능은 서비스를 제공하는 사람 입장에서는 굉장히 사용자에게 조심스럽게 제공해야 하는 부분이라고 생각된다. 잘못 눌렀을 수도 있고, 내가 아닌 다른 사람이 몰래 탈퇴 버튼을 눌렀을 때 내 의도와는 상관없이 모든 데이터가 사라지면 안되니까 말이다.

그래서 탈퇴를 원하는 사용자가 있다고 하면 인증 과정을 거치게 하는 것이 맞다고 생각한다. 이 인증 방식으로는 아무래도 FaceID와 같은 인증 방식이 적합하다고 생각했고, 이 FaceID 인증을 제공하는 방식으로 재로그인을 진행하는 것이 좋다고 결론을 내렸다.

결론적으로는, 클라이언트가 탈퇴를 원하는 사용자가 있을 때 로그인을 다시 진행하여 authorization code를 백엔드 서버로 넘겨주고 이 code를 Apple 서버에 전송하여 토큰으로 바꾼 후, Apple 서버에서 탈퇴되도록 했다. 물론 내가 내린 결정으로 도달한 결론이기 때문에 이것이 정답이 아닐 수 있고, 잘못된 방법일 수도 있다.

2. 백엔드 서버 개발자가 Apple Login 테스트하기

이제 설계를 마쳤으니 코드를 짜고, 서버에서 구현한 로그인, 탈퇴 코드가 잘 돌아가나 확인하기 위해서는 Apple에서 테스트를 위한 토큰들을 가져왔어야 했다.

문서를 확인해보면

거의 모든 요청에 client_id가 필요하다. 이 client_id에는 Apple Developers에서 받아온 App ID나 Services ID를 입력하라고 한다.

로그인 구현 시에는 제대로 테스트를 못하고 올렸어서 App ID를 사용했었는데, 클라이언트 측과 문제없이 소통이 되었어서 탈퇴 구현 후 테스트 시에도 App ID를 사용했고, 토큰을 받아 테스트 할 때 Postman에서 이 App ID를 client_id에 넣어 토큰을 받으려고 했다.

그런데,

Client를 인식하지 못한다고 하는 것이었다....

이유를 찾아보니 앱에서는 App ID로 요청을 보내야 하고, 웹에서 요청을 보낼 때는 App ID로 보내면 안된다고 한다.

App에서 보내는 요청 = App ID 사용
Web에서 보내는 요청 = Services ID 사용

그래서 Services ID로 요청을 보내보니

정상적으로 서비스에 접근할 수 있었고, 토큰을 받아올 수 있었다!

토큰으로 코드가 잘 돌아가는 것을 확인한 후에는 다시 서버 설정에서 Services ID를 App ID로 교체해주었고, 이후 클라이언트와 잘 통신되었다!

profile
Backend Engineer(FastAPI, Spring, ...) / Prompt & Context Engineer / DevOps

0개의 댓글