技術筆記-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

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


11會員
90內容數
漫步,悠閒自在的隨意行走!是行為,是態度,也是一種境界。
留言0
查看全部
發表第一個留言支持創作者!
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 當作系統入口
你可能也想看
Thumbnail
「設計不僅僅是外觀和感覺。設計是其運作的方式。」 — Steve Jobs 身為一個獨立文案,許多人會以為我們的生活只需要面對電腦,從無到有,用精巧的文字填滿空白的螢幕,呈現心目中獨具風格的作品。 ——有的時候可以如此,但其實這是我們夢寐以求的偶發日常。 更多的時候,白天的工作時間總被各種繁雜
Thumbnail
台股、美股近期明顯回檔,市場敘事發生改變,壞消息一樁接一樁出現,下一步該怎麼走呢?本文將探討近期的宏觀經濟事件,並分享個人的操作思考。
大家是否有撰寫的心血因為不明原因,網路異常而造成心血全都消失,這篇純粹是一個抒發文...
Thumbnail
提到「做筆記」你會想到什麼?如果說知識的消化可以分為「輸入、處理和輸出」三個階段,那麼你又認為筆記扮演的任務角色會落到哪一個階段?做筆記只是「輸入」成效不高,想要「輸出」又耗時費力。要如何解決這個問題呢?卡片盒筆記法,就是目前最有效的解方,特別是在數位工具智能化之後,更顯得這套方法的強大。
Thumbnail
數位筆記術的改變力量 您有曾經煩惱過學習的效率嗎?或者是覺得難以捕捉那些稍縱即逝的靈感?在我們的日常生活和學習中,這些問題都是常見的挑戰。但幸運的是,有一種工具可以幫助我們改變這些情況,那就是數位筆記術。接下來,我將會講解三個使用數位筆記術的重要原則,這些原則能幫助我們更有效地學習和捕捉靈感。 原則
Thumbnail
《花漾年華》四連幅是我用當代水墨的抽象語彙,表現臺灣春天茂盛的新綠,充滿生命力,山櫻花盛開。傳統花鳥畫的創作哲學是「一花一世界,一葉一如來」,透過描繪大自然的生機呈現藝術家空靈的精神世界,心靈與自然合一,既超越自然又貼近自然。 在手工宣紙,揮灑充滿想像力的純淨世界,留白,紓解現代人過量的影音刺激。
Thumbnail
2014年人生峰迴路轉,從一個藝術愛好者,開始投身書畫創作,九個月後受邀舉辦第一次個展。江心靜透過墨彩融合的技法展現內在生命能量,在堆疊墨韻、鮮明色彩和象徵性符號中,營造藝術與靈性追求的意境,優遊於具象和抽象水墨間。 我用畫畫寫詩,好的詩打破既有窠臼,以全新角度看待人生,詩有無限可能,可以多重解讀。
Thumbnail
藝術家江心靜投入創作第二年,內在巨大無比的能量爆發,完成了 2015《藍色空間》三連幅當代水墨創作。 詩人的心找到了視覺語言,用深邃迷離的藍,與宇宙的能量結合,與早年創作的詩句呼應,內外合一的文人走向天命之路。
Thumbnail
雲海流動如一首神秘的歌,興起一股衝動,想透過創作表達心中感悟,第一個想法是強調「氣韻生動」的水墨,2012年採訪現代水墨之父劉國松,高齡八十的畫家精神奕奕,親自示範技法。 看著成品,在廚房燈下微笑,黑白構成的畫面,有一股力量,從山間飛到城市,推動著自己繼續,不在意繁華如夢,一步一步,往前走。
Thumbnail
兩年多前,開始有了重新架構網站的想法,所以把 Blogger 架設的網站,移轉至到了新的平台,當年我也做過了一些優缺點評析,剛好最近又再重新整理網站的所有架構,順便也重新啟動了一次 Hugo 架設網站的循環。
Thumbnail
因為要轉換一份新工作,認為自己在工作上必需更提高效率,剛好週圍有些朋友使用電子筆記,對於喜歡寫紙本筆記的人來說,是一個新的嘗試,原因有二:其一,筆記上有很多資料,在用完整本筆記之後,隨之石沉大海;其二,現在越來越多資訊是透過網路傳送,如果沒有好好收藏,資料就"放在"硬碟內了。 我是先去Youtube
Thumbnail
「設計不僅僅是外觀和感覺。設計是其運作的方式。」 — Steve Jobs 身為一個獨立文案,許多人會以為我們的生活只需要面對電腦,從無到有,用精巧的文字填滿空白的螢幕,呈現心目中獨具風格的作品。 ——有的時候可以如此,但其實這是我們夢寐以求的偶發日常。 更多的時候,白天的工作時間總被各種繁雜
Thumbnail
台股、美股近期明顯回檔,市場敘事發生改變,壞消息一樁接一樁出現,下一步該怎麼走呢?本文將探討近期的宏觀經濟事件,並分享個人的操作思考。
大家是否有撰寫的心血因為不明原因,網路異常而造成心血全都消失,這篇純粹是一個抒發文...
Thumbnail
提到「做筆記」你會想到什麼?如果說知識的消化可以分為「輸入、處理和輸出」三個階段,那麼你又認為筆記扮演的任務角色會落到哪一個階段?做筆記只是「輸入」成效不高,想要「輸出」又耗時費力。要如何解決這個問題呢?卡片盒筆記法,就是目前最有效的解方,特別是在數位工具智能化之後,更顯得這套方法的強大。
Thumbnail
數位筆記術的改變力量 您有曾經煩惱過學習的效率嗎?或者是覺得難以捕捉那些稍縱即逝的靈感?在我們的日常生活和學習中,這些問題都是常見的挑戰。但幸運的是,有一種工具可以幫助我們改變這些情況,那就是數位筆記術。接下來,我將會講解三個使用數位筆記術的重要原則,這些原則能幫助我們更有效地學習和捕捉靈感。 原則
Thumbnail
《花漾年華》四連幅是我用當代水墨的抽象語彙,表現臺灣春天茂盛的新綠,充滿生命力,山櫻花盛開。傳統花鳥畫的創作哲學是「一花一世界,一葉一如來」,透過描繪大自然的生機呈現藝術家空靈的精神世界,心靈與自然合一,既超越自然又貼近自然。 在手工宣紙,揮灑充滿想像力的純淨世界,留白,紓解現代人過量的影音刺激。
Thumbnail
2014年人生峰迴路轉,從一個藝術愛好者,開始投身書畫創作,九個月後受邀舉辦第一次個展。江心靜透過墨彩融合的技法展現內在生命能量,在堆疊墨韻、鮮明色彩和象徵性符號中,營造藝術與靈性追求的意境,優遊於具象和抽象水墨間。 我用畫畫寫詩,好的詩打破既有窠臼,以全新角度看待人生,詩有無限可能,可以多重解讀。
Thumbnail
藝術家江心靜投入創作第二年,內在巨大無比的能量爆發,完成了 2015《藍色空間》三連幅當代水墨創作。 詩人的心找到了視覺語言,用深邃迷離的藍,與宇宙的能量結合,與早年創作的詩句呼應,內外合一的文人走向天命之路。
Thumbnail
雲海流動如一首神秘的歌,興起一股衝動,想透過創作表達心中感悟,第一個想法是強調「氣韻生動」的水墨,2012年採訪現代水墨之父劉國松,高齡八十的畫家精神奕奕,親自示範技法。 看著成品,在廚房燈下微笑,黑白構成的畫面,有一股力量,從山間飛到城市,推動著自己繼續,不在意繁華如夢,一步一步,往前走。
Thumbnail
兩年多前,開始有了重新架構網站的想法,所以把 Blogger 架設的網站,移轉至到了新的平台,當年我也做過了一些優缺點評析,剛好最近又再重新整理網站的所有架構,順便也重新啟動了一次 Hugo 架設網站的循環。
Thumbnail
因為要轉換一份新工作,認為自己在工作上必需更提高效率,剛好週圍有些朋友使用電子筆記,對於喜歡寫紙本筆記的人來說,是一個新的嘗試,原因有二:其一,筆記上有很多資料,在用完整本筆記之後,隨之石沉大海;其二,現在越來越多資訊是透過網路傳送,如果沒有好好收藏,資料就"放在"硬碟內了。 我是先去Youtube