기존 iOS 네이티브 앱에서 애플 로그인을 웹뷰에서 구현했었는데, 이번에 공식 라이브러리를 사용해 다시 구현하기로 했다.
이를 위해 앱 측에서 애플 인가 서버로부터 발급받은 사용자 정보와 authorization code
등을 서버(스프링 API 서버)에 전달했다. 이를 받은 서버에서는 기존 방식과 마찬가지로 code
를 사용해 토큰을 요청했지만 Invalid grant
에러가 발생했다.
iOS 앱 측에서 발급받은 authorization code를 사용해 애플 서버의 /auth/token에서 토큰을 발급받으려고 했으나 계속 Invalid_grant 에러가 발생했다.
{
"error": "invalid_grant",
"error_description": "client_id mismatch. The code was not issued to com.example.bundle."
}
공식 문서에 따르면 위 에러는 다음과 같은 이유로 발생할 수 있다고 한다.
authorization code
를 사용해 토큰 발급을 요청할 경우code
를 발급받은 client_id
가 일치하지 않는 경우code
가 만료되거나 이미 사용된 경우refresh token
을 사용해 토큰 발급을 요청할 경우refresh token
을 발급받은 client와 client_id
가 일치하지 않는 경우refresh token
이 유효하지 않거나 만료된 경우우리는 서비스에서 refresh token
이 아니라 code
를 사용해서 토큰을 발급받으려고 한다. 그리고 이 code
는 실시간으로 앱에서 받아 요청했으므로 만료되거나 이미 사용한 경우도 아니었기 때문에 client_id
와 문제가 있을 것으로 추측된다.
결론부터 말하자면, 토큰 발급 시 App ID가 아닌 Service ID를 사용했기 때문에 발생한 문제였다.
client_id
는 애플 개발자 계정에서 Certificates, Identifiers & Profiles
- Identifiers
에서 설정한 id이다. 그리고 우리는 App IDs
와 Service IDs
두 개를 설정했었다. 당시에는 그저 블로그에 나온 예제를 따라했었으나 여기가 문제였다.
https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
토큰을 발급받으려면 위와 같은 파라미터와 함께 요청하는데, 애플 로그인 기능 구현 시 client_id는 Service IDs
에서 생성한 id로 설정했었다. 당시에는 App IDs
와 Service IDs
의 차이에 대해 별 생각이 없기 때문에 그랬는데 이것이 문제였다.
App IDs
Bundle Identifier
와 연결되며, 배포 시 중요한 역할을 한다고 한다.Sign in with Apple
구현 시 웹뷰 방식이 아닌 공식 라이브러리를 사용할 경우 자동으로 Xcode 등에서 설정한 이 ID를 사용하는 것으로 보인다.Service IDs
기존 방식처럼 모바일에서 웹뷰를 사용해 로그인을 할 때에는 서버 측에서 직접 code
, id token
등을 발급받았기 때문에 문제가 없었다. 그러나 이번에 iOS 앱에서 직접 이를 발급받으면서, 앱에서 사용한 client_id
와 서버에서 사용하는 client_id
가 서로 달라 Invalid grant
에러가 발생한 것이었다.
iOS 앱에서 요청을 했을 때에는 App ID
를, 그 외의 서비스(Android, Web)에서 요청했을 때에는 Service ID
를 client_id
로 사용하도록 구현했다.
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_secret", createClientSecretKey());
params.add("code", code);
params.add("redirect_uri", appleProperties.getRedirectUrl());
// 애플 로그인 시 사용하는 clinet id는 app id(iOS 네이티브 앱 버전)와 service id(웹 서비스 버전)을 각각 사용함
if (isiOSApp) {
params.add("client_id", appleProperties.getIOSClientId());
} else {
params.add("client_id", appleProperties.getClientId());
}
사실 굉장히 간단한 문제였음에도, 문서나 글을 대충 읽고 넘어가 발생한 문제였다. 나는 iOS나 Mac OS 등의 사용 경험과 배경 지식이 거의 없다는 핑계를 댈 수도 있겠지만 회사였다면 의미 없는 행위였을 것이다... 앞으로는 이런 점들에 대해 주의 깊게 짚고 넘어가도록 하자