技術筆記-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會員
150內容數
漫步是一種境界。
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
債券投資,不只是高資產族群的遊戲 在傳統的投資觀念中,海外債券(Overseas Bonds)常被貼上「高資產族群專屬」的標籤。過去動輒 1 萬甚至 10 萬美元的最低申購門檻,讓許多想尋求穩定配息的小資族望而卻步。 然而,在股市波動劇烈的環境下,尋求穩定的美元現金流與被動收入成為許多投資人
Thumbnail
債券投資,不只是高資產族群的遊戲 在傳統的投資觀念中,海外債券(Overseas Bonds)常被貼上「高資產族群專屬」的標籤。過去動輒 1 萬甚至 10 萬美元的最低申購門檻,讓許多想尋求穩定配息的小資族望而卻步。 然而,在股市波動劇烈的環境下,尋求穩定的美元現金流與被動收入成為許多投資人
Thumbnail
透過川普的近期債券交易揭露,探討債券作為資產配置中「穩定磐石」的重要性。文章分析降息對債券的潛在影響,以及股神巴菲特的操作策略。並介紹玉山證券「小額債」平臺,如何讓小資族也能低門檻參與海外債券市場,實現「低門檻、低波動、固定收益」的務實投資方式。
Thumbnail
透過川普的近期債券交易揭露,探討債券作為資產配置中「穩定磐石」的重要性。文章分析降息對債券的潛在影響,以及股神巴菲特的操作策略。並介紹玉山證券「小額債」平臺,如何讓小資族也能低門檻參與海外債券市場,實現「低門檻、低波動、固定收益」的務實投資方式。
Thumbnail
解析「債券」如何成為資產配置中的穩定錨,提供低風險高回報的投資選項。 藉由玉山證券的低門檻債券服務,投資者可輕鬆入手,平衡風險並穩定財務。
Thumbnail
解析「債券」如何成為資產配置中的穩定錨,提供低風險高回報的投資選項。 藉由玉山證券的低門檻債券服務,投資者可輕鬆入手,平衡風險並穩定財務。
Thumbnail
相較於波動較大的股票,債券能提供固定現金流,而玉山證券推出的小額債,更以1000 美元的低門檻,讓學生與新手也能參與全球優質企業債投資。玉山E-Trader平台即時報價、條件式篩選與清楚的交易流程等特色,大幅降低投資難度,對於希望分散風險、建立穩定現金流的人來說,玉山小額債是一個值得嘗試的理財起點。
Thumbnail
相較於波動較大的股票,債券能提供固定現金流,而玉山證券推出的小額債,更以1000 美元的低門檻,讓學生與新手也能參與全球優質企業債投資。玉山E-Trader平台即時報價、條件式篩選與清楚的交易流程等特色,大幅降低投資難度,對於希望分散風險、建立穩定現金流的人來說,玉山小額債是一個值得嘗試的理財起點。
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