技術筆記-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
23會員
108內容數
漫步是一種境界。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
newman的沙龍 的其他內容
需求情境: 一般的看盤軟體,雖然都能針對一籃子自選股票,列出其即時行情和當天漲幅,但若要看「五日漲幅」呢?那就少見了,但這對我很重要。因為小部位的波段性價差交易是個好策略,這時候若能排序好一整排看下來,可以節省大量點來點去的成本,很有價值,所以就來自己刻。 解決方案: 從大處著眼,UI 最外層
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
技術筆記-用 python 操作 google firestore 的基本方法 (2023/12/26) 技術筆記-以 nodejs 為後台,以 google sheet 充當資料庫 (2022/11/29)
firestore 是 google 所提供的雲端文件式資料庫服務,為各種開發工具提供了方便使用的 sdk,python 的套件名稱為 firebase-admin,用 pip 安裝後就可操作了。 pip install firebase-admin
安裝完成 nodejs 後選用一個工作目錄執行 npm init,npm 會產生一個 package.json 檔案,之後為此專案安裝套件時都會記錄在此,讓專案可以很容易的重建和移植,也可設定 npm start 執行時以哪一個 js 檔當作系統入口。直接開寫了,以下我用 app.js 當作系統入口
需求情境: 一般的看盤軟體,雖然都能針對一籃子自選股票,列出其即時行情和當天漲幅,但若要看「五日漲幅」呢?那就少見了,但這對我很重要。因為小部位的波段性價差交易是個好策略,這時候若能排序好一整排看下來,可以節省大量點來點去的成本,很有價值,所以就來自己刻。 解決方案: 從大處著眼,UI 最外層
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
技術筆記-用 python 操作 google firestore 的基本方法 (2023/12/26) 技術筆記-以 nodejs 為後台,以 google sheet 充當資料庫 (2022/11/29)
firestore 是 google 所提供的雲端文件式資料庫服務,為各種開發工具提供了方便使用的 sdk,python 的套件名稱為 firebase-admin,用 pip 安裝後就可操作了。 pip install firebase-admin
安裝完成 nodejs 後選用一個工作目錄執行 npm init,npm 會產生一個 package.json 檔案,之後為此專案安裝套件時都會記錄在此,讓專案可以很容易的重建和移植,也可設定 npm start 執行時以哪一個 js 檔當作系統入口。直接開寫了,以下我用 app.js 當作系統入口
你可能也想看
Google News 追蹤
提問的內容越是清晰,強者、聰明人越能在短時間內做判斷、給出精準的建議,他們會對你產生「好印象」,認定你是「積極」的人,有機會、好人脈會不自覺地想引薦給你
Thumbnail
你有幾個 Google 帳號?Google 提供許多免費的應用程式,包括大家最常使用的地圖、YouTube、 email、日曆、Meet、Gemini、雲端硬碟、翻譯、文件、Google表單…等,如果你還沒有申請過 Google 帳號,請 抽空到此申請,免費體驗各種功能豐富的應用程式。 下圖右邊都
Thumbnail
你最近有特別有興趣的產業嗎,有興趣的股票嗎?你是每天去刷網頁嗎? 這裡有個方法可以幫你收集相關的網路新聞,固定送到你的信箱。 這個方法就是Google帳號的功能,快訊。 一、        首先你要有一個Google account及Gmail 然後是login in的狀態! 二、     
Thumbnail
Google 提供了免費的雲端服務 Google Apps Script (GAS) ,我們可以撰寫一些簡易的程式APP,串接其他 Google 雲端服務 如 Google Docs ,Sheets …,就能夠幫助我們利用雲端硬碟做日常工作
Thumbnail
當使用Google架設網站時,請考慮以下幾點。 目標設定:明確網站目的,選擇適合模板。 設計一致性:統一色系、字體和風格。 內容優化:用吸引力的標題、圖片、影片增加內容價值。 響應式設計:確保內容在各設備良好呈現。 專業域名:購買並設定獨特網域。 SEO優化:優化標題、描述、關鍵字和內部
中文網站介面 測試:安著手機、蘋果電腦。 瀏覽器:皆是Google瀏覽器。 結果:過多人使用時即使登入系統也無法查看訂單和購物車,但過一陣子會自動更新變回正常。
Thumbnail
授權碼模式連線流程 用戶端請求自己的伺服器。 伺服器發現用戶沒登入,就導向認證伺服器。 認證伺服器顯示授權頁面,等待用戶授權。 用戶確認授權後,授權頁面會向認證伺服器請求授權碼。 用戶獲取授權碼。 用戶將授權碼傳給伺服器。 伺服器拿授權碼向認證伺服器取得token。 應用註冊
提問的內容越是清晰,強者、聰明人越能在短時間內做判斷、給出精準的建議,他們會對你產生「好印象」,認定你是「積極」的人,有機會、好人脈會不自覺地想引薦給你
Thumbnail
你有幾個 Google 帳號?Google 提供許多免費的應用程式,包括大家最常使用的地圖、YouTube、 email、日曆、Meet、Gemini、雲端硬碟、翻譯、文件、Google表單…等,如果你還沒有申請過 Google 帳號,請 抽空到此申請,免費體驗各種功能豐富的應用程式。 下圖右邊都
Thumbnail
你最近有特別有興趣的產業嗎,有興趣的股票嗎?你是每天去刷網頁嗎? 這裡有個方法可以幫你收集相關的網路新聞,固定送到你的信箱。 這個方法就是Google帳號的功能,快訊。 一、        首先你要有一個Google account及Gmail 然後是login in的狀態! 二、     
Thumbnail
Google 提供了免費的雲端服務 Google Apps Script (GAS) ,我們可以撰寫一些簡易的程式APP,串接其他 Google 雲端服務 如 Google Docs ,Sheets …,就能夠幫助我們利用雲端硬碟做日常工作
Thumbnail
當使用Google架設網站時,請考慮以下幾點。 目標設定:明確網站目的,選擇適合模板。 設計一致性:統一色系、字體和風格。 內容優化:用吸引力的標題、圖片、影片增加內容價值。 響應式設計:確保內容在各設備良好呈現。 專業域名:購買並設定獨特網域。 SEO優化:優化標題、描述、關鍵字和內部
中文網站介面 測試:安著手機、蘋果電腦。 瀏覽器:皆是Google瀏覽器。 結果:過多人使用時即使登入系統也無法查看訂單和購物車,但過一陣子會自動更新變回正常。
Thumbnail
授權碼模式連線流程 用戶端請求自己的伺服器。 伺服器發現用戶沒登入,就導向認證伺服器。 認證伺服器顯示授權頁面,等待用戶授權。 用戶確認授權後,授權頁面會向認證伺服器請求授權碼。 用戶獲取授權碼。 用戶將授權碼傳給伺服器。 伺服器拿授權碼向認證伺服器取得token。 應用註冊