技術筆記-iOS實戰004-整合Firebase,使用它的Google SignIn做身份認證

更新 發佈閱讀 18 分鐘

需求情境:

為了讓多人使用 App,必須有驗證程序,以識別特定使用者,存取各自擁有的資源。

解決方案:

引用 google 所提供的雲端服務平台 Firebase,其中有多種驗證功能可選用。基於個人對 google 的偏愛,決定先採用 google signin 的方法,實作 login logout,並用 idToken 作為與後台 api server 溝通的驗證依據,測通呼叫流程。

實作步驟:

必須先在 Firebase 的 管理後台 準備相關資源:建立專案,在專案內建立應用,應用有很多種類型,選擇 iOS 類,各類別有相對應的 sdk 和文件。除了身份驗證以外,Firebase 作為一個應用系統的「容器」,提供非常多樣的功能,App 連上它就像擁有了一座寶庫成為堅實的後盾,真令人興奮!但這屬於後台技術故不在此詳述,若有合適的機會,再另闢新的寫作系列。


在 iOS 方面的實作工作,首先要從後台下載一個準備好的參數檔「GoogleService-Info.plist」,加到 Swift 專案內。然後用 Xcode 的套件管理員安裝 sdk,套件存放的位置在此:https://github.com/firebase/firebase-ios-sdk;此套件提供基礎的 email and password 驗證方式。因我期望用 google account 的認證方式,故還要再加上 GoogleSignIn 套件,有不同的安裝位址,均可從文件中取得。


套件安裝完成後,進到程式開發階段,需要從一個 iOS App 的程式入口開始做起,就是 appNewmanApp.swift,其中 appNewmanApp 為 App 的名稱。其中定義了一個 AppDelegate 的 class,透過特定的宣告語法,讓相關物件在程式啟動的初期就載入,在整個執行期間都能發揮效用。這些語法與物件有點高深莫測,是架構設計者最厲害的技術結晶,老實講我並不十分了解,只能先照文件依樣葫蘆。而 isRunningTests 的判斷機制是為了讓 Xcode 開發過程的 preview 不要出錯。

import SwiftUI
import SwiftData
import FirebaseCore
import FirebaseAuth
import GoogleSignIn

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        if !isRunningTests {
          FirebaseApp.configure()
        }
        return true
    }
    func application(_ app: UIApplication,
                     open url: URL,
                     options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        return GIDSignIn.sharedInstance.handle(url)
    }
    var isRunningTests: Bool {
        return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
    }
}

@main
struct appNewmanApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject private var authViewModel = AuthViewModel()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(authViewModel)
        }
    }
}

接下來定義一個 class,import 相關的函示庫後,必須注意繼承 ObservableObject,並將其最重要的兩個屬性,就是登入狀態,和當登入成功後所取得的 User 物件,標示為 Published,以便在系統的任何一個畫面都可以使用:

import FirebaseCore
import FirebaseAuth
import GoogleSignIn

class AuthViewModel: ObservableObject {
    @Published var user: User?
    @Published var isSignedIn: Bool = false
}

在此 class 裡面定義 signIn, signOut 的方法,依循「官方文件」按部就班一路操作。

func signInByGoogle() {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
let config = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = config
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController 
else {
print("There is no root view controller!")
return
}
GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) { [unowned self] result, error in
if let error = error {
print("Error signing in: \(error.localizedDescription)")
return
}
// 取得外部 Google 系統所定義的 User 物件​
guard let googleUser = result?.user,
let idToken = googleUser.idToken?.tokenString
else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken,
accessToken: googleUser.accessToken.tokenString)
// 用 Google User 的 credential 取得 Firebase 系統所定義的 User​
Auth.auth().signIn(with: credential) { result, error in
if let error = error {
print("Firebase sign in error: \(error.localizedDescription)")
return
}
self.user = result?.user
self.isSignedIn = true
}
}
}

func signOut() {
do {
try Auth.auth().signOut()
GIDSignIn.sharedInstance.signOut()
self.user = nil
self.isSignedIn = false
} catch let signOutError as NSError {
print("Error signing out: \(signOutError.localizedDescription)")
}
}

有一個細節必須特別注意,就是在專案屬性中的 Info 頁籤中,有一個 URL Types,必須加入一筆資料,資料來源是 GoogleService-Info.plist 中的 REVERSED_CLIENT_ID 的值。透過這個 URL 設定,讓專案允許 google 這個外部服務,更動專案內資料以執行我們所期望的 signIn, signOut 相關任務。

raw-image
raw-image

這種跨系統整合通常藏有非常多的坑,同樣名稱為 User 的物件卻有兩種,經由 google signIn 首先取得的是 google 系統所定義的 User 物件,無法直接使用於我們的應用系統,需要用它的 idToken and accessToken 取得 credential,再傳給 Firebase SDK 的 signIn 程序,才能取得 Firebase 所定義的 User 物件,用於程式中的畫面和後台溝通。運作機制有點複雜,最好仔細思考建立較完整的知識架構,否則後續的維護工作將是令人恐懼的!因為不管 iOS 端的升級,或是 google 端的升級,都會影響到程式結構,維護工作是無法逃避的。


最後來到畫面的部分,基本的元素就是一個 Sign In 按鈕,當 Sign In 成功之後顯示使用者頭像,並實作一個到後台取資料的程序,完成畫面如下,左一為未登入狀態,右一登入完成狀態,中間的登入過程為 Firebase SDK 和 Google 認證服務後台所主控,客製程式無法介入,如此也表示安全性由 google 官方所確保。若採用 email and password 登入,這些密碼資料是會經過我們的程式的,要讓使用者信任我們程式開發單位,不會惡意使用,這是難的,此問題還是丟給 google 處理比較妥當。

raw-image

此畫面的程式命名為 LoginView.swift,主 Layout 和狀態變數列出如下:

	@EnvironmentObject var authViewModel: AuthViewModel
@State var imageURL: URL?
@State var idToken : String = ""
@State var portfolioList: [Portfolio] = []


var body: some View {
if authViewModel.isSignedIn {
VStack {
Text("Welcome, \(authViewModel.user?.email ?? "")!")
if let imageURL = imageURL {
AsyncImage(url: imageURL) { image in
image
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
.clipShape(Circle())
} placeholder: {
ProgressView()
.frame(width: 100, height: 100)
}
} else {
Text("Invalid URL")
}
HStack {
Button(action: {
authViewModel.signOut()
}) {
Text("Sign Out")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Button(action: {
getPortfolioList()
}) {
Text("持 token 向後台取資料")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
VStack {
ForEach(portfolioList) { item in
Text(item.portfolioName)
}
}
}
.padding()
.onAppear {
showPhoto()
}
} else {
VStack {
Button(action: {
authViewModel.signInByGoogle()
}) {
Text("Sign In")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.padding()
}
}

當 Sign In 成功時,接續執行 showPhoto(),因圖像顯示並不是靜態存在的,如同先前預先匯入專案 Asset 檔案中那樣,而是搭配畫面的 AsyncImage 元件,動態指定位址。另外我們也順便把 idToken 載入到畫面的狀態變數,以便下一個動作需要使用,就是向後台取資料,這也要用非同步的程式寫法。

func showPhoto() {
if authViewModel.isSignedIn {
imageURL = authViewModel.user?.photoURL
Task {
idToken = try await getIdToken(authViewModel: authViewModel)
}
}
}

最後是用 idToken 去後台取資料的部分,先用一個簡單的資料結構 Model1 承接,今天的重點只在測通。

func getPortfolioList() {
Task {
let data = try await  fetchApiDataAsync(urlString: "https://xxxserver/portfolioList", idToken: self.idToken)
let decoder = JSONDecoder()
let xx = try decoder.decode(Model1.self, from: data)
self.portfolioList = xx.portfolio
}
}

struct Portfolio: Codable, Identifiable {
let portfolioId: Int
let portfolioName: String
var id: Int {
portfolioId
}
}

struct Model1: Codable {
let message: String
let portfolio: [Portfolio] 
}

fetchApiDataAsync 因為多傳 idToken,寫法有小幅更改。至於後台怎麼解開 idToken 則不在本文範圍。

func fetchApiDataAsync(urlString: String, idToken: String) async throws -> Data {
guard let url = URL(string: urlString)
else {
throw MyError.FileUrlError
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(idToken)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw MyError.HttpRequestError
}
return data
}

以上一個普通的功能,所費工夫不少,寫起來也是又臭又長,最終完成了,就是爽快。

Newman 2024/6/23

導覽頁:紐曼的技術筆記-索引


留言
avatar-img
newman的沙龍
31會員
149內容數
漫步是一種境界。
newman的沙龍的其他內容
2025/04/01
Reinforcement Learning (強化學習) 的理論非常有趣,可能是因為其中許多方法,與人類的學習歷程極為相似,如試錯,獎懲,改進策略,持續優化等等。現在準備來爬這座山了,我把學習階段大致分成三個小山峰,依序為 Q-Learning --> DQN --> Actor-Critic,
Thumbnail
2025/04/01
Reinforcement Learning (強化學習) 的理論非常有趣,可能是因為其中許多方法,與人類的學習歷程極為相似,如試錯,獎懲,改進策略,持續優化等等。現在準備來爬這座山了,我把學習階段大致分成三個小山峰,依序為 Q-Learning --> DQN --> Actor-Critic,
Thumbnail
2025/03/08
稍微看一下 Telegram 官方文件,哇!好強喔,功能說明的第一項赫然出現「可以取代整個網站」!口氣真的很大。雖然我的需求應該很低,但能夠確認前面是一座豐富的寶藏,還是很令人興奮的,待基本功能掌握之後,可以再評估和決定要不要往下挖。 發送訊息 要達成這第一個目標,首先必須建立一個 bot。
Thumbnail
2025/03/08
稍微看一下 Telegram 官方文件,哇!好強喔,功能說明的第一項赫然出現「可以取代整個網站」!口氣真的很大。雖然我的需求應該很低,但能夠確認前面是一座豐富的寶藏,還是很令人興奮的,待基本功能掌握之後,可以再評估和決定要不要往下挖。 發送訊息 要達成這第一個目標,首先必須建立一個 bot。
Thumbnail
2025/03/01
Line Notify 即將停止服務,隨著時間越來越緊迫,隱約聽到許多人在哀嚎。印象中有許多廠商,把 Line Notify 用得淋漓盡致,甚至可以一個客戶建一個群組,把許多客製化服務都用程式管理的井井有條,得到很好的滿意度。但這種好康,無限免費的即時訊息,沒有了,時間就在 2025/3/31!公告
Thumbnail
2025/03/01
Line Notify 即將停止服務,隨著時間越來越緊迫,隱約聽到許多人在哀嚎。印象中有許多廠商,把 Line Notify 用得淋漓盡致,甚至可以一個客戶建一個群組,把許多客製化服務都用程式管理的井井有條,得到很好的滿意度。但這種好康,無限免費的即時訊息,沒有了,時間就在 2025/3/31!公告
Thumbnail
看更多
你可能也想看
Thumbnail
如果你也是那種在職場上追求極致效率,對生活品質有堅持,且渴望一段成熟、穩定、不拖泥帶水關係的專業人士,那麼 Ping! 會是你目前市面上最值得嘗試的選擇。 成熟的大人,不需要在低效的社交中消磨熱情。讓 Ping!,為你的情感生活進行「降噪」,把精力和時間,留給那個真正能與你靈魂共鳴、頻率一致的人。
Thumbnail
如果你也是那種在職場上追求極致效率,對生活品質有堅持,且渴望一段成熟、穩定、不拖泥帶水關係的專業人士,那麼 Ping! 會是你目前市面上最值得嘗試的選擇。 成熟的大人,不需要在低效的社交中消磨熱情。讓 Ping!,為你的情感生活進行「降噪」,把精力和時間,留給那個真正能與你靈魂共鳴、頻率一致的人。
Thumbnail
厭倦只看外貌的交友方式嗎?Ping!主打真實、安全的深度交友體驗,透過真人驗證與多樣化的個人化問答,幫助使用者在認識彼此之前,先理解價值觀、關係期待與交友目標。即使是慢熟的 I 人,也能透過提問找到適合的人選,避免聊到一半才發現方向不同。適合想被理解、重視心理連結與安心互動的你。
Thumbnail
厭倦只看外貌的交友方式嗎?Ping!主打真實、安全的深度交友體驗,透過真人驗證與多樣化的個人化問答,幫助使用者在認識彼此之前,先理解價值觀、關係期待與交友目標。即使是慢熟的 I 人,也能透過提問找到適合的人選,避免聊到一半才發現方向不同。適合想被理解、重視心理連結與安心互動的你。
Thumbnail
Ping!主打真人驗證機制,透過AI人臉比對確保用戶真實性,讓人放心。獨特的照片主題功能、個性化標籤和趣味文字問答,讓用戶更深入展現自我,為開啟話題提供契機,甚至有機會找到擁有相似冷門興趣的同好。Ping!注重高品質的交友關係,透過共同點建立雙方的連結,為現代人提供一個舒適、真實且有意義的交友環境。
Thumbnail
Ping!主打真人驗證機制,透過AI人臉比對確保用戶真實性,讓人放心。獨特的照片主題功能、個性化標籤和趣味文字問答,讓用戶更深入展現自我,為開啟話題提供契機,甚至有機會找到擁有相似冷門興趣的同好。Ping!注重高品質的交友關係,透過共同點建立雙方的連結,為現代人提供一個舒適、真實且有意義的交友環境。
Thumbnail
也許不是我不適合交友,而是我適合的節奏,本來就比較慢。 比起快速認識很多人,我更在意人與人怎麼相遇,才不會那麼累。當對話可以慢慢發生,當我們從想法開始靠近彼此,那種剛剛好的距離,反而讓人更願意走近。
Thumbnail
也許不是我不適合交友,而是我適合的節奏,本來就比較慢。 比起快速認識很多人,我更在意人與人怎麼相遇,才不會那麼累。當對話可以慢慢發生,當我們從想法開始靠近彼此,那種剛剛好的距離,反而讓人更願意走近。
Thumbnail
你有幾個 Google 帳號?Google 提供許多免費的應用程式,包括大家最常使用的地圖、YouTube、 email、日曆、Meet、Gemini、雲端硬碟、翻譯、文件、Google表單…等,如果你還沒有申請過 Google 帳號,請 抽空到此申請,免費體驗各種功能豐富的應用程式。 下圖右邊都
Thumbnail
你有幾個 Google 帳號?Google 提供許多免費的應用程式,包括大家最常使用的地圖、YouTube、 email、日曆、Meet、Gemini、雲端硬碟、翻譯、文件、Google表單…等,如果你還沒有申請過 Google 帳號,請 抽空到此申請,免費體驗各種功能豐富的應用程式。 下圖右邊都
Thumbnail
你最近有特別有興趣的產業嗎,有興趣的股票嗎?你是每天去刷網頁嗎? 這裡有個方法可以幫你收集相關的網路新聞,固定送到你的信箱。 這個方法就是Google帳號的功能,快訊。 一、        首先你要有一個Google account及Gmail 然後是login in的狀態! 二、     
Thumbnail
你最近有特別有興趣的產業嗎,有興趣的股票嗎?你是每天去刷網頁嗎? 這裡有個方法可以幫你收集相關的網路新聞,固定送到你的信箱。 這個方法就是Google帳號的功能,快訊。 一、        首先你要有一個Google account及Gmail 然後是login in的狀態! 二、     
Thumbnail
這是一篇介紹如何使用Google Maps API在網頁上呈現地圖,並進行店家名稱的搜尋的文章。內容包含如何申請Google Cloud Platform的API金鑰、初始化地圖、設定搜尋功能等步驟。文章中提供了部分程式碼範例,並附上圖片說明操作過程。
Thumbnail
這是一篇介紹如何使用Google Maps API在網頁上呈現地圖,並進行店家名稱的搜尋的文章。內容包含如何申請Google Cloud Platform的API金鑰、初始化地圖、設定搜尋功能等步驟。文章中提供了部分程式碼範例,並附上圖片說明操作過程。
Thumbnail
近期 Google 搜尋引擎API機密文件的外流事件,絕對是近期震撼數位行銷世界的一大頭條,其內容揭示了一些有關 Google 搜尋結果生成原理的重要細節。今天本男爵就來跟各位聊聊這其中獲得的寶貴洞察,或許會對您在設計網站內容時有一些不同的想法!
Thumbnail
近期 Google 搜尋引擎API機密文件的外流事件,絕對是近期震撼數位行銷世界的一大頭條,其內容揭示了一些有關 Google 搜尋結果生成原理的重要細節。今天本男爵就來跟各位聊聊這其中獲得的寶貴洞察,或許會對您在設計網站內容時有一些不同的想法!
Thumbnail
需求情境: 為了讓多人使用 App,必須有驗證程序,以識別特定使用者,存取各自擁有的資源。 解決方案: 引用 google 所提供的雲端服務平台 Firebase,其中有多種驗證功能可選用。基於個人對 google 的偏愛,決定先採用 google signin 的方法,實作 login lo
Thumbnail
需求情境: 為了讓多人使用 App,必須有驗證程序,以識別特定使用者,存取各自擁有的資源。 解決方案: 引用 google 所提供的雲端服務平台 Firebase,其中有多種驗證功能可選用。基於個人對 google 的偏愛,決定先採用 google signin 的方法,實作 login lo
Thumbnail
當使用Google架設網站時,請考慮以下幾點。 目標設定:明確網站目的,選擇適合模板。 設計一致性:統一色系、字體和風格。 內容優化:用吸引力的標題、圖片、影片增加內容價值。 響應式設計:確保內容在各設備良好呈現。 專業域名:購買並設定獨特網域。 SEO優化:優化標題、描述、關鍵字和內部
Thumbnail
當使用Google架設網站時,請考慮以下幾點。 目標設定:明確網站目的,選擇適合模板。 設計一致性:統一色系、字體和風格。 內容優化:用吸引力的標題、圖片、影片增加內容價值。 響應式設計:確保內容在各設備良好呈現。 專業域名:購買並設定獨特網域。 SEO優化:優化標題、描述、關鍵字和內部
Thumbnail
工作時常會需要固定發廣告信,或業務開發信的話,可以考慮用google app script,呼叫gmail 的方式來處理。
Thumbnail
工作時常會需要固定發廣告信,或業務開發信的話,可以考慮用google app script,呼叫gmail 的方式來處理。
Thumbnail
大家有沒有試過專注模式?我覺得這功能真的很不錯,可以用Siri語音輕鬆開啟預設模式,但是自訂模式卻沒辦法,所以今天來介紹如何使用Siri語音開啟自訂模式。 當然,你需要先有一個自訂模式,先使用「認真專注模式」作為示範,再來到「捷徑」功能新增一個名叫「開啟認真模式」的捷徑,然後將內容如圖所示進行設定
Thumbnail
大家有沒有試過專注模式?我覺得這功能真的很不錯,可以用Siri語音輕鬆開啟預設模式,但是自訂模式卻沒辦法,所以今天來介紹如何使用Siri語音開啟自訂模式。 當然,你需要先有一個自訂模式,先使用「認真專注模式」作為示範,再來到「捷徑」功能新增一個名叫「開啟認真模式」的捷徑,然後將內容如圖所示進行設定
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News