Firebase Auth, Google 로그인, Combine을 활용해 회원가입을 할 수 있도록 하는 Firebase Auth에 대해 기록하려 한다.
Firebase를 처음 사용해본다면 다음 포스팅을 참고해서 프로젝트를 생성한 다음 실행하면 된다.
Firebase 프로젝트 생성하는 방법
Firebase Auth를 추가하고자 하는 콘솔창으로 들어간 다음 좌측 메뉴에 '빌드' 하위 항목인 Authentication에 들어간 후 시작하기를 눌러준다.
여기서 어떤 방법을 이용해 로그인 할 수 있는지 체크해줄 수 있는데, 이번엔 Google 로그인에 대해 알아보는 것이기 때문에 Google 버튼을 눌러주도록 한다.
그런 다음 사용 설정을 토글해주고, 프로젝트 지원 이메일을 입력해주면 저장하기 버튼이 활성화되고, 저장해주면 된다. 여기까지만 해주면 우선 Firebase 콘솔에서 설정하는 부분은 끝이다. 이제 Xcode로 넘어가서 Google 로그인을 구현해보도록 하자. 아래에는 Firebase에서 제공하는 공식 문서인데 여기 들어가보면 Google 로그인을 하는 방법에 대해 자세히 나와있으니 아래 문서를 참고해봐도 좋다.
애플 플랫폼에서 Google 로그인 하기 관련 문서 링크
Firebase Auth를 통해 Google 로그인을 할 때는 아래와 같은 흐름으로 로그인이 진행된다.
1. Firebase의 클라이언트 ID로 Google configuration object 생성
2. 해당 object를 활용해 Google 로그인 요청
3. Google 로그인 완료 시 Access Token, ID Token이 발행이 되고, 해당 정보를 토대로 credential(사용자 인증 정보) 생성
4. credentiall로 Firebase 로그인
가장 먼저 Google 로그인을 하기 위해서는 패키지를 추가해줘야 한다. Pakage Manager를 통해 https://github.com/firebase/firebase-ios-sdk 해당 SDK를 추가해주도록 하자.
우선 App Target에 들어간 후 Info 탭에서 URL Types를 추가해줘야 한다. +버튼을 눌러 추가한 후 아래 박스에 URL Schemes를 넣어주면 되는데 해당 값은 앞서 Firebase 에서 다운 받았던 GoogleService-info.plist 파일의 REVERSED_CLIENT_ID의 값을 복사해서 넣어주면 된다.
그런 후 AppDelegate에 아래의 메서드를 정의해준다. 이 때 Firebase Core, Firebase Auth, GoogleSignIn을 Import 해줘야 한다.
import FirebaseAuth
import FirebaseCore
import GoogleSignIn
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance.handle(url)
}
}
그 다음 회원에 대한 정보를 담아서 사용하기 위해 필요한 정보들을 가지고 있는 User를 정의하면 된다.
struct User: Identifiable {
var id: String
var name: String
}
우선 간단하게 식별을 위한 id와 이름만 정의를 했다. 여기서 추가적으로 필요한 정보가 있다면 더 추가해주면 된다.
다음으로 Google 로그인에 대한 메서드를 정의해주면 되는데 Google 로그인은 Combine을 제공하지 않는다. 그렇기 때문에 Completion Handler를 만들어 처리하고 완료 되었을 때 Future를 활용해 Publisher를 생성해줘야 이 후 Combine을 이용해 처리해줄 수 있다.
가장 먼저 흐름의 1번과 2번을 진행해서 Access Token과 ID Token을 발행하는 메서드부터 아래 주석을 참고해 만들어주도록 하자.
private func signInWithGoogle(completion: @escaping (Result<User, Error>) -> Void) {
// Firebase clientID 받아오기
guard let clientID = FirebaseApp.app()?.options.clientID else {
completion(.failure(AuthenticationError.clientIDError))
return
}
// clientID를 바탕으로 Google Configuration object 생성
let config = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = config
// Configuration object로 Google 로그인 요청
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first,
let rootViewController = window.rootViewController else { return }
GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) { [weak self] result, error in
if let error {
completion(.failure(error))
return
}
// 로그인 성공 시 result에서 user와 ID Token 추출
guard let user = result?.user, let idToken = user.idToken?.tokenString else {
completion(.failure(AuthenticationError.tokenError))
return
}
// user에서 Access Token 추출
let accessToken = user.accessToken.tokenString
// Token을 토대로 Credential(사용자 인증 정보) 생성
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
// 생성한 Credential을 Firebase Auth 로그인 메서드에 전달
// 해당 메서드는 해당 포스팅 아래에 정의되어 있음
self?.authenticateUserWithFirebase(credential: credential, completion: completion)
}
}
다음으로는 Credential을 받아서 Firebase Auth에 로그인 하는 메서드를 정의한 후 위 메서드의 가장 아래줄에 넣어주면 된다.
private func authenticateUserWithFirebase(credential: AuthCredential, completion: @escaping (Result<User, Error>) -> Void) {
// 전달받은 Credential로 Firebase Auth에 로그인
Auth.auth().signIn(with: credential) { result, error in
if let error {
completion(.failure(error))
return
}
guard let result else {
completion(.failure(AuthenticationError.invalidated))
return
}
// 로그인에 성공 시 result에서 user를 받을 수 있는데
// 해당 정보를 토대로 위에서 정의한 User Model 생성
let firebaseUser = result.user
let user: User = .init(id: firebaseUser.uid,
name: firebaseUser.displayName ?? "")
completion(.success(user))
}
}
마지막으로 로그인 ViewModel에서 가져다 쓸 로그인 메서드를 만들어주면 되는데 Future를 활용해 Publisher를 생성해주면 된다.
func signInWithGoogle() -> AnyPublisher<User, ServiceError> {
Future { [weak self] promiss in
// Service 내부에서 사용하는 Completion Handler 메서드 활용
self?.signInWithGoogle { result in
switch result {
case let .success(user):
promiss(.success(user))
case let .failure(error):
promiss(.failure(.error(error)))
}
}
}.eraseToAnyPublisher()
}
여기까지 완료한다면 Google Login 연동은 완료된다.
전체 코드는 Messenger Demo App 해당 프로젝트의 AuthenticationService에서 확인할 수 있다.