1. 개요
앱을 만들다보면 서버를 이용해서 로그인하는 기능이 필요로할 때가 있다. 이 때, 자체적인 회원가입을 통해 로그인을 하게 할 수도 있지만 대게 다른 소셜 계정을 통해서 로그인하는 방법이 편리할 때가 있다.
FirebaseAuth에서는 다양한 소셜 계정으로 서버에 로그인하는 방법을 지원하는데, 여기서 Google 계정을 이용해서 로그인하는 방법을 적용해보고자 한다.
또한 앱 종료 후 다시 실행시켰을 때 이전 로그인한 기록을 가지고 자동로그인하는 방법도 구현하였다.
2. Firebase와 Xcode 연결하기
Firebase 프로젝트와 Xcode 프로젝트 생성은 이전 포스팅과 같다.
이미 방법을 알고 있다면 중간에 패키지 추가사항만 확인해두면 되겠다.
먼저, Xcode에서 프로젝트를 생성한다.
프로젝트 생성시 정해지는 Bundle Identifier 정보는 Firebase 프로젝트 생성할 때 필요한 정보이다.
물론 프로젝트 생성 후 변경할 수도 있다.
다음은, Firebase 프로젝트를 생성해보겠다.
프로젝트 이름은 임의대로 지정해주고,
Google 애널리틱스 설정은 권장 설정대로 적용하고 다음으로 진행한다.
Google 애널리틱스 구성 설정 창도 Default Account for Firebase를 선택하고 프로젝트를 생성해주자.
이제 프로젝트 개요 메인 화면에서 가운데 iOS+ 을 눌러 앱을 추가하여 시작한다.
앱 정보 입력
아까 얘기해둔 XCode 프로젝트 Bundle Identifier를 Apple 번들 ID에 입력한다. (필수)
앱 닉네임, App Store ID는 입력 해도 되고 안해도 되지만 되도록 입력 할 수 있는 정보는 모두 입력하자.
Google Info plist 추가
다음은 GoogleService-Info plist를 추가하는 것인데, 다운로드 한 뒤에 파일을 Xcode 프로젝트에 드래그 앤 드롭으로 넣어두자.
GoogleService-Info plist에는 Firebase와 프로젝트간 연결할 수 있는 정보들이 담겨있다.
다음은 Firebase SDK를 추가한다.
Xcode 상단 메뉴 바에서 File > Add Packages로 이동
https://github.com/firebase/firebase-ios-sdk
위 링크를 Search or Enter Package URL에 입력해준다.
Firebase 패키지 추가
Firebase SDK Product 선택 설치
firebase-ios-sdk 를 선택하고 Add Package를 클릭하고 이후에 패키지를 선택 추가 창이 나오면
FirebaseAuth 패키지를 선택하여 추가한다.
또한 Google 로그인에 필요한 패키지를 하나 더 추가한다.
Xcode 상단 메뉴 바에서 File > Add Packages로 이동
https://github.com/google/GoogleSignIn-iOS
위 링크를 Search or Enter Package URL에 입력해주고 GoogleSignIn-iOS를 추가한다.
패키지까지 추가하고 Firebase에서 다음을 누르면,
초기화 코드 프로젝트 추가
위 처럼 초기화 코드를 입력하라고 하는데,
프로젝트이름App.swift 파일로 들어가 아래처럼 작성해준다.
Firebase 프로젝트 메인에 좌측 빌드 메뉴를 클릭하면 Firebase에서 사용할 수 있는 여러가지 빌드 목록들이 나오는데, 우리는 계정과 관련된 기능을 사용할 것이므로, Authentication을 클릭한다.
Authentication을 빌드목록에서 선택하면 위와같은 메인화면이 나오는데, 시작하기를 눌러준다.
Firebase에서 기본으로 제공하는 로그인 수단들이 나열되어있다. 다양한 수단으로 할 수 있으며, 우리는 여기서 Google 계정을 이용해서 로그인을 하려고한다.
추가 제공업체에 있는 Google을 선택한다.
Google을 선택하면 위 사진처럼 Google 사용 설정을 허용으로 토글 버튼을 눌러주고, 프로젝트 지원 이메일에 팀에서 정한 이메일 혹은 본인 이메일을 선택하고 저장한다.
저장하고 로딩이 끝나면, 위 사진처럼 Google 제공업체 상태가 '사용 설정됨'으로 되어있어야한다.
3. Google 계정을 이용한 로그인
구글 계정을 로그인하기 위해서 프로젝트에서 한가지 설정을 더 해줘야한다.
프로젝트 파일에서 TARGETS 선택 -> Info -> 하단 URL Types에서 +로 항목을 추가하고 URL Schemes 부분에
GoogleService-Info.plist에 있는 REVERSED_CLIENT_ID 값을 복사 붙여넣기 해준다.
com.googleusercontent.apps.로 시작하는 문자열들이다.
LoginView 파일을 생성하고 아래와 같은 코드를 작성한다.
import SwiftUI
struct LoginView: View {
var body: some View {
VStack {
Button {
// Google 로그인 액션부분
} label: {
Text("Google Account Login")
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
먼저, 로그인 버튼 디자인은 신경쓰지 않고 일단 로그인 로직만 동작하게 만들 것이다.
아직 ViewModel을 정의하지 않았으므로, 로그인 화면은 위와같이 간단하게만 만들어둘 것이다.
그리고 이제 로그인이 정상적으로 수행되면 다음으로 넘어갈 뷰를 만들어보자.
MainView 파일을 생성하고 아래와 같이 코드를 작성한다.
import SwiftUI
struct MainView: View {
var body: some View {
Text("로그인 완료!")
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
이제 로그인에 필요한 ViewModel을 작성해보려고한다.
AuthenticationViewModel.swift 파일을 추가하고 다음 코드들을 작성해본다.
import Firebase
import GoogleSignIn
class AuthenticationViewModel: ObservableObject {
@Published var signState: signState = .signOut
enum signState {
case signIn
case signOut
}
// google 로그인 절차
func signIn() {
// 1
if GIDSignIn.sharedInstance.hasPreviousSignIn() {
GIDSignIn.sharedInstance.restorePreviousSignIn { [unowned self] user, error in
authenticateUser(for: user, with: error)
}
} else {
// 2
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// 3
let configuration = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = configuration
// 4
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
guard let rootViewController = windowScene.windows.first?.rootViewController else { return }
// 5
GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) {[unowned self] result, error in
guard let result = result else { return }
authenticateUser(for: result.user, with: error)
}
}
}
// firebase 로그인 절차
private func authenticateUser(for user: GIDGoogleUser?, with error: Error?) {
// 1
if let error = error {
print(error.localizedDescription)
return
}
// 2
guard let accessToken = user?.accessToken.tokenString, let idToken = user?.idToken?.tokenString else { return }
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
// 3
Auth.auth().signIn(with: credential) { (_, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.signState = .signIn
}
}
}
// 로그아웃 절차
func signOut() {
// 1
GIDSignIn.sharedInstance.signOut()
do {
// 2
try Auth.auth().signOut()
self.signState = .signOut
} catch {
print(error.localizedDescription)
}
}
}
Firebase를 이용한 Google 계정 로그인의 절차는 다음 순서로 이루어진다.
먼저, Firebase로부터 clientID를 발급받아 이를 이용하여 Google 서버로 보내면, 어떤 플랫폼으로 Google 로그인을 하는 것인지 Google 서버에 전송하고, 사용자의 Google 계정의 유효성을 인증받는다.
이후, 정상적으로 유효성을 인증받게되면, credential 정보를 받게 되고, 이를 Firebase Auth로 전송한다.
전송이 정상적으로 이루어지고 credential 정보도 유효하다면, Firebase Auth 로그인이 완료된다.
// google 로그인 절차
func signIn() {
// 1
if GIDSignIn.sharedInstance.hasPreviousSignIn() {
GIDSignIn.sharedInstance.restorePreviousSignIn { [unowned self] user, error in
authenticateUser(for: user, with: error)
}
} else {
// 2
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// 3
let configuration = GIDConfiguration(clientID: clientID)
// 4
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
guard let rootViewController = windowScene.windows.first?.rootViewController else { return }
// 5
GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) {[unowned self] result, error in
guard let result = result else { return }
authenticateUser(for: result.user, with: error)
}
}
}
그럼 Firebase Auth의 Google 로그인 절차를 설명했으니 이제 단계적으로 코드를 분석해본다.
위 코드에서 1번은 이전에 Google 계정으로 로그인한 이력이 있는지 확인하는 단계이다. 일정 시간이 지나거나, Firebase Auth에서 로그아웃해서 Firebase와 Google 계정간 인증 토큰의 유효성이 사라졌다면, false의 결과를 반환할 것이고 로그인했던 정보가 있다면 블록 안 내용을 실행할 것이다.
true이면, GIDSign.In.sharedInstance.restorePreviousSignIn { ... } 을 통해서 이전 로그인했던 사용자의 계정 정보를 호출하게 되고, authenticateUser 함수를 실행한다. 여기서 authenticateUser 함수는 Firebase Auth에 로그인하는 절차이고, 코드의 반복성을 피하기 위해서 사용자가 작성한 함수이다.
2번은 타 플랫폼에서 Google 계정을 이용해서 로그인하기 위해서는 clientID가 필요하다. 어떤 플랫폼에서 Google 서버에 접근하는지 해당 플랫폼의 임시 ID를 생성해서 접근한다고 생각하면 편하다.
3번 구문은 clientID를 이용해서 Google configuration 객체를 생성했다.
4번 구문은 최상위 뷰 컨트롤러로 Google 로그인 모달 뷰를 넘겨준다.
5번 구문은 Google 계정으로 로그인하는 함수이다.
참고
5번 구문 클로저 내에 있는 result 변수를 활용하여 다양한 정보를 확인할 수 있다.
result.user.userID: Google 계정의 UUID 값을 가져온다.
result.user.profile.email: Google 계정의 email 값을 가져온다.
result.user.profile.familyName: Google 계정의 사용자 성함 중 '성'을 가져온다.
result.user.profile.givenName: Google 계정의 사용자 성함 중 '이름'을 가져온다.
result.user.profile.name: Google 계정의 사용자 성함을 가져온다.
// firebase 로그인 절차
private func authenticateUser(for user: GIDGoogleUser?, with error: Error?) {
// 1
if let error = error {
print(error.localizedDescription)
return
}
// 2
guard let accessToken = user?.accessToken.tokenString, let idToken = user?.idToken?.tokenString else { return }
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
// 3
Auth.auth().signIn(with: credential) { (_, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.signState = .signIn
}
}
}
위 코드는 Google 계정으로부터 계정 유효성을 점검받은 후, Firebase로 로그인하는 절차를 나타낸다.
Google 로그인이 성공한 함수 이후에 authenticateUser 함수가 호출되는 것을 볼 수 있는데, Google 로그인이 성공하고 바로 Firebase Auth로 연결짓기 위해서이다.
1번 구문은 에러처리 구문이다.
2번 구문은 Google 계정으로 로그인한 후, 생성된 사용자의 Google User 객체를 매게변수로 받아서 accessToken, idToken 정보를 가져온다. 이후 credential 변수로 Google 계정의 인증 정보를 할당하는 과정이다.
이렇게 Google 계정의 인증 정보를 가지고 Firebase Auth에 접근하여 로그인 절차를 마무리하는데, 관련된 함수가 3번 구문이다.
Auth.auth().signIn(with: credential) { ... } 함수를 통해 Firebase Auth에 로그인 과정을 설명한다.
또한 현재 로그인 된 상태라는 것을 알리기 위해 self.signState = .signIn 구문을 작성한다.
// 로그아웃 절차
func signOut() {
// 1
GIDSignIn.sharedInstance.signOut()
do {
// 2
try Auth.auth().signOut()
self.signState = .signOut
} catch {
print(error.localizedDescription)
}
}
로그아웃은 로그인에 비해 간단하다.
1번: Firebase Auth에 전달된 Google 계정의 credential 정보를 삭제한다.
2번: Firebase Auth에 사용자 상태를 로그아웃 상태로 변경하고, 현재 로그아웃 상태라는 것을 저장하기 위한 self.signState = .signOut 구문을 작성한다.
이제 View 파일들을 수정해보자.
App.swift
import SwiftUI
import FirebaseCore
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
@main
struct GoogleSignInProjectApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@StateObject var authenticationViewModel: AuthenticationViewModel = AuthenticationViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(authenticationViewModel)
}
}
}
App 파일에서 authenticationViewModel ViewModel을 초기화한다.
ContentView.swift
import SwiftUI
import FirebaseAuth
struct ContentView: View {
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
VStack {
if authenticationViewModel.signState == .signIn {
MainView()
} else {
LoginView()
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(AuthenticationViewModel())
}
}
ContentView 파일을 살펴보면, authenticationViewModel에 있는 signState의 상태에 따라 다른 뷰를 띄워주는 것을 볼 수 있다.
Google 로그인이 안되어있는 상태라면, LoginView()를 보여줄 것이고, 로그인이 완료되면 MainView로 전환될 것이다.
LoginView.swift
import SwiftUI
struct LoginView: View {
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
VStack {
Button {
authenticationViewModel.signIn()
} label: {
Text("Google Account Login")
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
.environmentObject(AuthenticationViewModel())
}
}
MainView.Swift
import SwiftUI
struct MainView: View {
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
Text("로그인 완료!")
Button {
authenticationViewModel.signOut()
} label: {
Text("로그아웃")
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
.environmentObject(AuthenticationViewModel())
}
}
로그아웃을 하면 ViewModel에 있는 signState 변수의 값이 .signOut 상태로 바뀌어 다시 LoginView를 보여주게 된다.
4. 자동로그인
여기서 최근 앱 종료 전 로그인이 되어있던 상태라면 앱을 다시 실행했을 때 로그인이 되어있는 상태 그대로 보여주고 싶다면 ContentView에 다음처럼 설정해보자.
struct ContentView: View {
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
var body: some View {
VStack {
if authenticationViewModel.signState == .signIn {
MainView()
} else {
LoginView()
}
}
.onAppear {
if Auth.auth().currentUser != nil {
authenticationViewModel.signState = .signIn
}
}
.padding()
}
}
VStack에 수정자 .onAppear를 추가했다.
Auth.auth().currentUser 객체 안에 로그인 제공업체로부터 연계된 사용자의 이름, 이메일 정보 등을 가져올 수 있다.
View의 라이프 사이클에 따라서 View가 나타나면 Firebase Auth로 접근하여 아직 사용자가 로그인되어있는 상태인지 확인하고, ViewModel의 signState 값을 .signIn으로 변경하여 앱을 실행했을 때 자동으로 MainView로 이동하게 된다.
'SwiftUI' 카테고리의 다른 글
SwiftUI Firebase와 KakaoTalk 로그인 연결하기 (0) | 2023.01.25 |
---|---|
SwiftUI Firebase Email Auth Login 구현하기 (0) | 2022.12.23 |
SwiftUI Firebase Realtime Database CRUD 제대로 사용하기 (12) | 2022.12.08 |
ATS Policy에 의한 HTTP 통신 제약 (0) | 2022.11.28 |
SwiftUI 문서 구조화 해보기 - 초급 (0) | 2022.11.07 |