google one-tap login 을 구현하기 위해 다음과 같이 코드를 구현하였다.
https://developers.google.com/identity/one-tap/android/idtoken-auth?hl=ko
https://firebase.google.com/docs/auth/android/google-signin?hl=ko
참고로 공식문서의 코드는 요런 형식인데 언제적 onActivityResult 인지... deprecated 된지가 언젠데
빨리 코드 업데이트 좀 해줬음 좋겠다.
LoginActivity 코드의 일부분
@AndroidEntryPoint
class LoginActivity : BaseActivity() {
override val binding by lazy { ActivityLoginBinding.inflate(layoutInflater) }
private val viewModel by viewModels<LoginViewModel>()
private lateinit var oneTapClient: SignInClient
private lateinit var signInRequest: BeginSignInRequest
private lateinit var idToken: String
private val oneTapClientResult =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
try {
// 인텐트로 부터 로그인 자격 정보를 가져옴
val credential = oneTapClient.getSignInCredentialFromIntent(result.data)
// 가져온 자격 증명에서 Google ID 토큰을 추출
val googleIdToken = credential.googleIdToken
if (googleIdToken != null) {
// Google ID 토큰을 사용해 Firebase 인증 자격 증명을 생성
val firebaseCredential = GoogleAuthProvider.getCredential(googleIdToken, null)
// 생성된 Firebase 인증 자격 증명을 사용하여 Firebase 에 로그인을 시도
Firebase.auth.signInWithCredential(firebaseCredential).addOnCompleteListener { task ->
if (task.isSuccessful) {
Firebase.auth.currentUser?.getIdToken(true)?.addOnCompleteListener { idTokenTask ->
if (idTokenTask.isSuccessful) {
idTokenTask.result?.token?.let { token ->
idToken = token
viewModel.login(idToken)
} ?: Timber.e("FirebaseIdToken is null.")
}
}
} else {
Timber.e(task.exception)
}
}
} else {
Timber.e("GoogleIdToken is null.")
}
} catch (exception: ApiException) {
Timber.e(exception.localizedMessage)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initGoogleLogin()
initListener()
initObserver()
}
private fun initGoogleLogin() {
// 현쟈 액티비티(this)에 대한 Google One Tap 로그인 클라이언트를 가져옴
oneTapClient = Identity.getSignInClient(this)
signInRequest = BeginSignInRequest.builder()
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
// Google Id Token 기반 로그인을 지원하도록 설정
.setSupported(true)
// 서버의 클라이언트 ID 를 설정
.setServerClientId(BuildConfig.GOOGLE_CLIENT_ID)
// 기존에 인증된 계정만을 필터링하지 않도록 설정
.setFilterByAuthorizedAccounts(false)
.build(),
)
// 이전에 선택 했던 계정을 기억
.setAutoSelectEnabled(true)
.build()
}
private fun initListener() {
binding.cvGoogleLogin.setOnClickListener {
oneTapClient
.beginSignIn(signInRequest)
.addOnSuccessListener(this) { result ->
try {
oneTapClientResult.launch(IntentSenderRequest.Builder(result.pendingIntent.intentSender).build())
} catch (exception: IntentSender.SendIntentException) {
Timber.e("Couldn't start One Tap UI: ${exception.localizedMessage}")
}
}
.addOnFailureListener(this) { exception ->
Timber.e(exception.localizedMessage)
}
}
}
백엔드 측에 idToken을 전달해야 하는데 위의 코드를 보면 idToken으로 보이는 후보가 두가지가 있는 것을 확인할 수 있다.
val credential = oneTapClient.getSignInCredentialFromIntent(result.data)
val googleIdToken = credential.googleIdToken // <-
Firebase.auth.currentUser!!.getIdToken(true)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
idToken = task.result.token!! // <-
viewModel.getLoginBody(idToken)
} else {
Timber.e(task.exception)
}
}
처음에는 1번에서 얻은 idToken을 (변수 분리를 위해 googleIdToken이라고 선언) 백엔드 측에 보냈었는데 계속 401 Unauthorized, Firebase ID token has incorrect~ 에러가 발생하였다.
idToken을 보내라길래 난 idToken을 얻어다가 보낸건데 왜 안될까 계속 알아보던 중 2번 코드를 시도하였고, 2번에서 얻은 idToken을 통해 성공적으로 로그인을 구현 할 수 있었다.
두 idToken 은 뭐가 다른 것이며, 왜 하나는 되고 하나는 안되는 것인지 알아보도록 하겠다.
우선 1번의 idToken(googleIdToken)은 코드에서 확인할 수 있듯, firebase 와 관련되지 않은 google 에서 제공하는 token 이다. google 계정으로 로그인한 사용자를 인증하기 위한 JWT 이다.
2번의 idToken 은 코드에서 확인할 수 있듯, Firebase Authentication 에서 제공되는 idToken 이고, Firebase 로 인증된 사용자를 나타내는 JWT 이다. 백엔드 서버에서는 이 토큰을 받아 Firebase SDK 를 사용하여 토큰의 유효성을 검증하고, 사용자의 Firebase 인증 정보를 가져올 수 있다.
백엔드 측에서 Firebase SDK 를 통한 idToken 유효성 검증의 방법을 사용하고 있었기 때문에 1번 idToken 으로는 로그인을 할 수 없었던 것이었다.
따라서 백엔드와 login 관련 정책을 협의할때 어떤 방식을 사용할 것인지 확실히 정하고, 이에 맞춰 구현할 필요가 있다.
위의 문제의 원인을 일주일 넘게 찾지 못하여 시간을 낭비했었는데 나처럼 시간을 낭비하지 않길 바라며 글을 작성해보았다.
Reference)
https://developers.google.com/identity/one-tap/android/idtoken-auth?hl=ko
https://firebase.google.com/docs/auth/android/google-signin?hl=ko
https://developers.google.com/identity/sign-in/web/backend-auth?hl=ko