iOS Swift Facebook Google Apple 第三方登入

Swift實作Facebook、Google、Apple ID第三方登入功能

林怡瑄 2021/07/07 21:34:17
11235

現今APP的用途很廣泛,無論是生活還是娛樂用,很多APP採用了會員系統,讓使用者必須使用一組帳號密碼來做登入,使用者方面可以記錄自己的喜好,而開發者也能保存使用者的資料。但我們都知道要多記住一組帳密其實是很麻煩的事情,如果可以選擇使用FacebookGoogle,或是iOS使用者必備的Apple帳號來作為會員登入,相信對諸多使用者來說都是很方便的,而開發者也能夠藉此取得更多資訊,真是一舉數得可喜可賀(?)。

這篇文章將會以這三個平台為例子,介紹開發者如何串接第三方登入,以及可以從中得到哪些使用者資料。

 

一、使用Facebook作為第三方登入平台

 

以下會介紹詳細的使用步驟,亦可直接參閱官方說明文件:

https://developers.facebook.com/docs/facebook-login/ios

我們先建立一個XCODE專案,接著至FB的開發者平台(https://developers.facebook.com/

在註冊為開發者後,進入開發者首頁,點擊右上「我的應用程式」,並建立一個應用程式。

 

選擇消費者。

 

填入應用程式顯示名稱,注意不能直接使用FB、Facebook等包含官方名稱的文字,然後點擊建立應用程式。

 


之後在左邊的側邊欄選取設定>基本資料,拉到最底下新增平台。

 

選擇iOS。

 


套件組和編號即為專案中的Bundle ID,以及記得把單一登入改為「是」,然後點擊右下方的「儲存變更」。

接著回到XCODE專案裡。跟著官方文件在 File> Swift Packages> Add Package Dependency中,輸入框填入:https://github.com/facebook/facebook-ios-sdk

預設已為Up to Next Major。

 

選擇FacebookLogin後,點擊Finish,接著就可以看到左邊多了一欄。

接著我們設定專案,按照說明,在專案的Info.plist選擇以原始碼開啟(右鍵>Open As>Source Code),在<dict>...</dict>中貼上代碼。

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>fb2963XXXXXXXXXXX</string>
        </array>
    </dict>
</array>
<key>FacebookAppID</key>
<string>2963XXXXXXXXXXX</string>
<key>FacebookDisplayName</key>
<string>SocialLoginDemo</string>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>fbapi</string>
    <string>fbapi20130214</string>
    <string>fbapi20130410</string>
    <string>fbapi20130702</string>
    <string>fbapi20131010</string>
    <string>fbapi20131219</string>
    <string>fbapi20140410</string>
    <string>fbapi20140116</string>
    <string>fbapi20150313</string>
    <string>fbapi20150629</string>
    <string>fbapi20160328</string>
    <string>fbauth</string>
    <string>fb-messenger-share-api</string>
    <string>fbauth2</string>
    <string>fbshareextension</string>
</array>

2963開頭的那串數字請換成自己的應用程式編號,在FB的開發者頁面上方可以找到。

SocialLoginDemo也要換成自己在FB建立的應用程式名稱(非XCODE專案名稱)。

 

再來到AppDelegate.swift裡,把裡面的程式碼直接改為下面這段:

import UIKit
import FacebookLogin

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
        return true
    }
    
    func application(_ app:UIApplication, open url:URL, options: [UIApplication.OpenURLOptionsKey :Any] = [:]) -> Bool {
        ApplicationDelegate.shared.application(app, open: url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, annotation: options[UIApplication.OpenURLOptionsKey.annotation])
    }
    
}

 

這段的作用,讓我借用一下官方文件說明:

『此程式碼會在應用程式啟動時初始化 SDK,並在執行「登入」或「分享」動作時,讓 SDK 處理原生 Facebook 應用程式的結果。』

 

另外在SceneDelegate.swift中,先import FBSDKCoreKit後,加入以下程式碼:

func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }
        ApplicationDelegate.shared.application( UIApplication.shared, open: url, sourceApplication: nil, annotation: [UIApplication.OpenURLOptionsKey.annotation] )      
    }

這段程式碼將處理開啟網址的功能。

 

再來是置入按鈕的部分,官方有提供按鈕的圖示,如果要直接使用的話,在ViewController裡,先import FacebookLogin,並在viewDidLoad裡放進程式碼:

let loginButton = FBLoginButton()
loginButton.center = view.convert(CGPoint(x: view.center.x, y: 300), from: self.view)
view.addSubview(loginButton)

 

不過我這次自己做了三個按鈕。

所以我在按鈕裡放入:

let loginManager = LoginManager()

loginManager.logIn(permissions: [.publicProfile, .email], viewController : nil) { loginResult in
    switch loginResult {
    case .failed(let error):
         print(error)
    case .cancelled:
         print("-------User cancelled login")
    case .success(granted: _, declined: _, token: _):
         print("-------Logged in")
        }
 }

 

點擊按鈕即會出現:

 

在登入帳號後,會提醒使用者這個APP要求的權限,即為程式碼中permissions: [.publicProfile, .email]的部分,隨著要求權限的不同也會有不同提醒。

 

需注意的地方是,目前可以直接取得的使用者資料只有公開檔案與電子信箱這兩種,其餘都需要經由FB官方審查,開發者可從使用者的帳號裡獲得的資訊可以參考官方網頁:

https://developers.facebook.com/docs/permissions/reference

如果程式碼中有尚未審查的權限,會出現像這樣的警告。

FB的登入審查相關請見官方文件:

https://developers.facebook.com/docs/facebook-login/permissions/review

官方會檢驗APP中是否真的需要使用到這些涉及隱私的權限,文件中有較詳細的說明可以參考。

 

此外,我們需要檢查使用者的登入狀態,可以從AccessToken.current 

是否有值來確認登入狀態,並且取得userID,例如加上這樣的程式碼:

if let accessToken = AccessToken.current {
                
    print("-----------userID: \(accessToken.userID)")
    print("-----------tokenString: \(accessToken.tokenString)")
    } else {
            print("not login")
    }

 

因此我現在可以從log裡看到這樣的結果:

 

以及再加上這幾行,這樣就能看到已登入的使用者資訊:

let request = GraphRequest.init(graphPath: "me", parameters: ["fields":"name, email"]).start { connection, result, error in
   let result = result as! NSDictionary
   print(result)
}

 

 

log印出結果:

 

如果需要登出的話,在登出的地方加上loginManager.logout()就可以了,這樣就完成使用Facebook第三方登入的功能啦,這邊僅列出最基本的流程,其餘的操作請至FB的開發者平台研究囉。

 

 

二、使用Google作為第三方登入平台

 

FB的部分結束了,接著來實作Google的第三方登入,他的官方文件在這邊:https://developers.google.com/identity/sign-in/ios/start-integrating

下面也會每個步驟逐一截圖說明。

 

取得Google Sign-in SDK的方式,官方建議使用CocoaPods安裝(pod 'GoogleSignIn')

,雖然也可以直接下載,不過我這邊就照著建議的方式吧,使用CocoaPods的過程就先省略了,網路上有很多的教學。

 

安裝完後,改開.xcworkspace結尾的檔案,按照說明我們需要取得OAuth客戶端ID。


記得先複製一下Client ID,然後貼到專案裡TARGETS-Info,新增一項URL Types。

 

這邊要注意的是,我們剛剛複製的Client ID在URL Schemes前後是反過來的,需要調換順序,前面是com.googleusercontent.apps,後面就直接貼上即可。

 

 

接著寫一點程式碼,在AppDelegate.swiftimport GoogleSignIn,並在AppDelegate的類別中加入GIDSignInDelegate。
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {

 

application:didFinishLaunchingWithOptions:方法中加入:

GIDSignIn.sharedInstance().clientID = "你的Client ID"
GIDSignIn.sharedInstance().delegate = self

 

 

application:openURL:options:方法中也加入:

return GIDSignIn.sharedInstance().handle(url)

 

此外,為了支援iOS 8以下版本,官方建議加入已棄用的application:openURL:sourceApplication:annotation:方法,但如果APP本身沒打算支援較低的版本,可以不用加入這一段。

func application(_ application: UIApplication,
                 open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
  return GIDSignIn.sharedInstance().handle(url)
}

 

 

在剛剛加入GIDSignInDelegate時,XCODE應該會出現要求增加協議的警告,需要定義didSignInFor這個方法,所以在底下加入:

func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
  if let error = error {
    if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
      print("The user has not signed in before or they have since signed out.")
    } else {
      print("\(error.localizedDescription)")
    }
    return
  }
  // Perform any operations on signed in user here.
  let userId = user.userID                  // For client-side use only!
  let idToken = user.authentication.idToken // Safe to send to the server
  let fullName = user.profile.name
  let givenName = user.profile.givenName
  let familyName = user.profile.familyName
  let email = user.profile.email
}

 

再來是ViewController的部分,一樣先import GoogleSignIn,在viewDidLoad加入:

GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance()?.restorePreviousSignIn()

 

最後是按鈕,我在自定義的按鈕裡加入這一行:

GIDSignIn.sharedInstance().signIn()

上面是處理登入的部分,如果要登出的話就是:

GIDSignIn.sharedInstance().signOut()

 

來看一下結果。

到這邊,使用者已經完成了登入的過程,開發者若要取得使用者資訊,可以從GIDGoogleUser中獲得。官方的教學是把這段程式碼放在AppDelegate裡,如果需要呈現在畫面上,可以傳值到ViewController裡,或是直接在ViewController裡加入GIDSignInDelegate。

 

官方文件中列出了一些可能會使用到的基本資料,如使用者ID,ID Token、姓名、email等等,直接從log印出來看看,我把程式改成這樣:

if let userId = user.userID {
     print("---------\(userId)")
} // For client-side use only!
if let idToken = user.authentication.idToken {
     print("---------\(idToken)")
} // Safe to send to the server
if let fullName = user.profile.name {
     print("---------\(fullName)")
}
if let email = user.profile.email {
     print("---------\(email)")
}

 

印出來的結果:

以上到這邊是Google第三方登入的部分,更多的應用可以參考官方文件。

 

 

三、使用Apple ID作為第三方登入平台

 

對iOS使用者來說,使用Apple ID登入或許是目前最安全的方式,開發者只會得到使用者的名字以及email,在登入時若選擇「隱藏我的電子郵件」,則開發者只會看到Apple幫忙建立的一組對應的、由系統產生的專屬帳號(網域為privaterelay.appleid.com),在保護隱私上更為徹底。

 

接著來著手操作吧,首先至Signing & Capacilities中,新增Capability。

(附註說明,如果不是有加入開發者計劃或是企業的Apple帳號的話,Capability裡是不會有Sign in with Apple這個選項的喔)

接著回到ViewController,先import AuthenticationServices,然後在自定義的按鈕裡加上:

let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.performRequests()

到這邊會出現一個錯誤提示,要求加入ASAuthorizationControllerDelegate,所以要加一個extension,連帶放入這些程式:
extension ViewController: ASAuthorizationControllerDelegate {
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let credential = authorization.credential as? ASAuthorizationAppleIDCredential {
            let userId = credential.user
            let fullname = credential.fullName
            let email = credential.email
            let idToken = credential.identityToken
            print("---------\(userId)")
            print("---------\(fullname)")
            print("---------\(email)")
            print("---------\(idToken)")
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print(error.localizedDescription)
    }
    
}

 

Error的部分也可以用switch去細分為不同的case,在ASAuthorizationError裡面可以看到failedcanceled等等的狀態,可以個別判斷為何種錯誤。

 

之後來試跑看看,我是使用模擬器,前面的步驟沒有問題:

就沒有然後了,點下Contunue以後進入無止盡的轉圈圈。

後來搜尋了一下,在一年前也有人抱怨過這個問題,關於在iOS 14版本的模擬器會出現無法登入Apple ID的狀況:
https://developer.apple.com/forums/thread/651533

不過底下有人提供解法,先去下載13.7版本的模擬器,並把APP的支援版本改成13.6或以下。





再運行一次看看。






成功了!並且可以看到,在我選擇了Hide My Email之後,我log接收到的就是一組專屬的英數字帳號。

 

最後稍微說一下那個836 bytes,他的格式是個有時效性的JWTJSON Web Token),寫成base64的編碼,我把程式改成:
guard let idToken = credential.identityToken else { return }
print("---------\(String(data: idToken, encoding: .utf8))")
 
會得到



這一大串是由HeaderPayloadSignature所組成。有了IdentityToken可以向server驗證token是否過期,流程是這樣:



至於驗證token的方法請參考官方文件:

https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple

https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/verifying_a_user


四、實際應用

在討論實際應用之前,先回顧一下開發者可以從這三個平台獲取的資料。

 

Google和Apple能獲取的資料較為基本,前者能拿到的是姓名、頭像,以及電子信箱;Apple能拿到的東西是姓名與電子信箱(使用者可以選擇顯示為中介mail)。Facebook能拿的東西就很多了,除了基本的名稱、頭像、電子信箱外,還可能取得使用者的眾多個人資料,例如性別、年齡、居住地、教育狀況,甚至是朋友名單(限使用同APP)、點過讚的粉絲專頁、貼文、影片等,畢竟比起其他兩者,FB是社群網站,能掌握的東西多得多。

 

不過綜合上述三種平台來看,開發者提供讓使用者以第三方平台作為快速註冊及登入,最方便的就是能快速取得使用者的名字或電子信箱,可以直接拿來作為使用者的代稱,例如在登入以後可以直接從第三方平台拿到的名字稱呼使用者,然後再提供一個可以修改的地方;也可以收集到使用者信箱,作為行銷推廣使用等等。

 

或者最常見的,是利用分享的功能,在取得貼文的權限以後貼到使用者的公開頁面,藉以達到宣傳的效果,或是列出同樣有使用同個APP的朋友等,這兩點我們在很多的手遊APP裡應該都能看到有類似的機制,比如說請朋友送體力之類的,雖然要求這些權限需要通過FB的審核,但只要附上完整的說明,我想有那麼多前輩APP在先了,應該不會被特別刁難(大概吧)(欸)。

 

不過有個重要的事情,根據文件:https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple

APP中如果提供第三方登入,除非是教育類,或者是企業內部使用的情形,否則一定要加入Sign in with Apple的部分,所以如果要在iOS的APP中加入任何的第三方平台作為登入,勢必也得加上Apple登入。

 

但以我個人經驗,如果是打算以第三方登入所取得的信箱資料作為ID的話,最好不要讓使用者在其他地方有需要填ID的時候,一律在APP內作業,因為在選擇了隱藏信箱後ID就會變成了那一串亂碼般的英數字,然而一些遊戲的獎勵是在網頁上操作,我根本不記得我的代理mail是什麼最後只好重新註冊一個,還好還沒玩多久⋯⋯(扯遠了)。不過整體來說Apple ID是很方便,還可以用Touch ID或Face ID快速登入,如果只是單純作為APP註冊的話,我覺得對iOS使用者來說是最好的選擇了。

 

總之,以上就是目前最普遍使用的三種第三方登入的說明,這邊都只列出基本使用的方法,如果有興趣想活用在更多方面,請至各官方文件進行探索囉。
 
參考資料:
https://developers.facebook.com/docs/facebook-login/ios
https://developers.google.com/identity/sign-in/ios/start-integrating
https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple
https://developer.apple.com/forums/thread/651533
 
林怡瑄