| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- // Copyright 2020 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // For Sign in with Facebook
- import FBSDKLoginKit
- import FirebaseAuth
- // [START auth_import]
- import FirebaseCore
- // For Sign in with Google
- // [START google_import]
- import GoogleSignIn
- import UIKit
- // For Sign in with Apple
- import AuthenticationServices
- import CryptoKit
- private let kFacebookAppID = "ENTER APP ID HERE"
- class AuthViewController: UIViewController, DataSourceProviderDelegate {
- var dataSourceProvider: DataSourceProvider<AuthMenu>!
- override func loadView() {
- view = UITableView(frame: .zero, style: .insetGrouped)
- }
- override func viewDidLoad() {
- super.viewDidLoad()
- configureNavigationBar()
- configureDataSourceProvider()
- }
- // MARK: - DataSourceProviderDelegate
- func didSelectRowAt(_ indexPath: IndexPath, on tableView: UITableView) {
- let item = dataSourceProvider.item(at: indexPath)
- let providerName = item.isEditable ? item.detailTitle! : item.title!
- guard let provider = AuthMenu(rawValue: providerName) else {
- // The row tapped has no affiliated action.
- return
- }
- switch provider {
- case .settings:
- performSettings()
- case .google:
- performGoogleSignInFlow()
- case .apple:
- performAppleSignInFlow()
- case .facebook:
- performFacebookSignInFlow()
- case .twitter, .microsoft, .gitHub, .yahoo:
- performOAuthLoginFlow(for: provider)
- case .emailPassword:
- performDemoEmailPasswordLoginFlow()
- case .passwordless:
- performPasswordlessLoginFlow()
- case .phoneNumber:
- performPhoneNumberLoginFlow()
- case .anonymous:
- performAnonymousLoginFlow()
- case .custom:
- performCustomAuthLoginFlow()
- case .initRecaptcha:
- performInitRecaptcha()
- case .customAuthDomain:
- performCustomAuthDomainFlow()
- }
- }
- // MARK: - Firebase 🔥
- private func performSettings() {
- let settingsController = SettingsViewController()
- navigationController?.pushViewController(settingsController, animated: true)
- }
- private func performGoogleSignInFlow() {
- // [START headless_google_auth]
- guard let clientID = FirebaseApp.app()?.options.clientID else { return }
- // Create Google Sign In configuration object.
- // [START_EXCLUDE silent]
- // TODO: Move configuration to Info.plist
- // [END_EXCLUDE]
- let config = GIDConfiguration(clientID: clientID)
- GIDSignIn.sharedInstance.configuration = config
- // Start the sign in flow!
- GIDSignIn.sharedInstance.signIn(withPresenting: self) { [unowned self] result, error in
- guard error == nil else {
- // [START_EXCLUDE]
- return displayError(error)
- // [END_EXCLUDE]
- }
- guard let user = result?.user,
- let idToken = user.idToken?.tokenString
- else {
- // [START_EXCLUDE]
- let error = NSError(
- domain: "GIDSignInError",
- code: -1,
- userInfo: [
- NSLocalizedDescriptionKey: "Unexpected sign in result: required authentication data is missing.",
- ]
- )
- return displayError(error)
- // [END_EXCLUDE]
- }
- let credential = GoogleAuthProvider.credential(withIDToken: idToken,
- accessToken: user.accessToken.tokenString)
- // [START_EXCLUDE]
- signIn(with: credential)
- // [END_EXCLUDE]
- }
- // [END headless_google_auth]
- }
- func signIn(with credential: AuthCredential) {
- // [START signin_google_credential]
- AppManager.shared.auth().signIn(with: credential) { result, error in
- // [START_EXCLUDE silent]
- guard error == nil else { return self.displayError(error) }
- // [END_EXCLUDE]
- // At this point, our user is signed in
- // [START_EXCLUDE silent]
- // so we advance to the User View Controller
- self.transitionToUserViewController()
- // [END_EXCLUDE]
- }
- // [END signin_google_credential]
- }
- // For Sign in with Apple
- var currentNonce: String?
- private func performAppleSignInFlow() {
- do {
- let nonce = try CryptoUtils.randomNonceString()
- currentNonce = nonce
- let appleIDProvider = ASAuthorizationAppleIDProvider()
- let request = appleIDProvider.createRequest()
- request.requestedScopes = [.fullName, .email]
- request.nonce = CryptoUtils.sha256(nonce)
- let authorizationController = ASAuthorizationController(authorizationRequests: [request])
- authorizationController.delegate = self
- authorizationController.presentationContextProvider = self
- authorizationController.performRequests()
- } catch {
- // In the unlikely case that nonce generation fails, show error view.
- displayError(error)
- }
- }
- private func performFacebookSignInFlow() {
- // The following config can also be stored in the project's .plist
- Settings.shared.appID = kFacebookAppID
- Settings.shared.displayName = "AuthenticationExample"
- // Create a Facebook `LoginManager` instance
- let loginManager = LoginManager()
- loginManager.logIn(permissions: ["email"], from: self) { result, error in
- guard error == nil else { return self.displayError(error) }
- guard let accessToken = AccessToken.current else { return }
- let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
- self.signin(with: credential)
- }
- }
- // Maintain a strong reference to an OAuthProvider for login
- private var oauthProvider: OAuthProvider!
- private func performOAuthLoginFlow(for provider: AuthMenu) {
- oauthProvider = OAuthProvider(providerID: provider.id)
- oauthProvider.getCredentialWith(nil) { credential, error in
- guard error == nil else { return self.displayError(error) }
- guard let credential = credential else { return }
- self.signin(with: credential)
- }
- }
- private func performDemoEmailPasswordLoginFlow() {
- let loginController = LoginController()
- loginController.delegate = self
- navigationController?.pushViewController(loginController, animated: true)
- }
- private func performPasswordlessLoginFlow() {
- let passwordlessViewController = PasswordlessViewController()
- passwordlessViewController.delegate = self
- let navPasswordlessAuthController =
- UINavigationController(rootViewController: passwordlessViewController)
- navigationController?.present(navPasswordlessAuthController, animated: true)
- }
- private func performPhoneNumberLoginFlow() {
- let phoneAuthViewController = PhoneAuthViewController()
- phoneAuthViewController.delegate = self
- let navPhoneAuthController = UINavigationController(rootViewController: phoneAuthViewController)
- navigationController?.present(navPhoneAuthController, animated: true)
- }
- private func performAnonymousLoginFlow() {
- AppManager.shared.auth().signInAnonymously { result, error in
- guard error == nil else { return self.displayError(error) }
- self.transitionToUserViewController()
- }
- }
- private func performCustomAuthLoginFlow() {
- let customAuthController = CustomAuthViewController()
- customAuthController.delegate = self
- let navCustomAuthController = UINavigationController(rootViewController: customAuthController)
- navigationController?.present(navCustomAuthController, animated: true)
- }
- private func signin(with credential: AuthCredential) {
- AppManager.shared.auth().signIn(with: credential) { result, error in
- guard error == nil else { return self.displayError(error) }
- self.transitionToUserViewController()
- }
- }
- private func performInitRecaptcha() {
- Task {
- do {
- try await AppManager.shared.auth().initializeRecaptchaConfig()
- print("Initializing Recaptcha config succeeded.")
- } catch {
- print("Initializing Recaptcha config failed: \(error).")
- }
- }
- }
- private func performCustomAuthDomainFlow() {
- let prompt = UIAlertController(title: nil, message: "Enter Custom Auth Domain For Auth:",
- preferredStyle: .alert)
- prompt.addTextField()
- let okAction = UIAlertAction(title: "OK", style: .default) { action in
- let domain = prompt.textFields?[0].text ?? ""
- AppManager.shared.auth().customAuthDomain = domain
- print("Successfully set auth domain to: \(domain)")
- }
- prompt.addAction(okAction)
- present(prompt, animated: true)
- }
- // MARK: - Private Helpers
- private func configureDataSourceProvider() {
- let tableView = view as! UITableView
- dataSourceProvider = DataSourceProvider(dataSource: AuthMenu.sections, tableView: tableView)
- dataSourceProvider.delegate = self
- }
- private func configureNavigationBar() {
- navigationItem.title = "Firebase Auth"
- guard let navigationBar = navigationController?.navigationBar else { return }
- navigationBar.prefersLargeTitles = true
- navigationBar.titleTextAttributes = [.foregroundColor: UIColor.systemOrange]
- navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.systemOrange]
- }
- private func transitionToUserViewController() {
- // UserViewController is at index 1 in the tabBarController.viewControllers array
- tabBarController?.transitionToViewController(atIndex: 1)
- }
- }
- // MARK: - LoginDelegate
- extension AuthViewController: LoginDelegate {
- public func loginDidOccur() {
- transitionToUserViewController()
- }
- }
- // MARK: - Implementing Sign in with Apple with Firebase
- extension AuthViewController: ASAuthorizationControllerDelegate,
- ASAuthorizationControllerPresentationContextProviding {
- // MARK: ASAuthorizationControllerDelegate
- func authorizationController(controller: ASAuthorizationController,
- didCompleteWithAuthorization authorization: ASAuthorization) {
- guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
- else {
- print("Unable to retrieve AppleIDCredential")
- return
- }
- guard let nonce = currentNonce else {
- fatalError("Invalid state: A login callback was received, but no login request was sent.")
- }
- guard let appleIDToken = appleIDCredential.identityToken else {
- print("Unable to fetch identity token")
- return
- }
- guard let appleAuthCode = appleIDCredential.authorizationCode else {
- print("Unable to fetch authorization code")
- return
- }
- guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
- print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
- return
- }
- guard let _ = String(data: appleAuthCode, encoding: .utf8) else {
- print("Unable to serialize auth code string from data: \(appleAuthCode.debugDescription)")
- return
- }
- // use this call to create the authentication credential and set the user's full name
- let credential = OAuthProvider.appleCredential(withIDToken: idTokenString,
- rawNonce: nonce,
- fullName: appleIDCredential.fullName)
- AppManager.shared.auth().signIn(with: credential) { result, error in
- // Error. If error.code == .MissingOrInvalidNonce, make sure
- // you're sending the SHA256-hashed nonce as a hex string with
- // your request to Apple.
- guard error == nil else { return self.displayError(error) }
- // At this point, our user is signed in
- // so we advance to the User View Controller
- self.transitionToUserViewController()
- }
- }
- func authorizationController(controller: ASAuthorizationController,
- didCompleteWithError error: Error) {
- // Ensure that you have:
- // - enabled `Sign in with Apple` on the Firebase console
- // - added the `Sign in with Apple` capability for this project
- print("Sign in with Apple failed: \(error)")
- }
- // MARK: ASAuthorizationControllerPresentationContextProviding
- func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
- return view.window!
- }
- }
|