เพิ่มการลงชื่อเข้าใช้ในแอป iOS ด้วย FirebaseUI

FirebaseUI สำหรับ SwiftUI เป็นไลบรารีสมัยใหม่ที่เน้น SwiftUI เป็นหลักซึ่งสร้างขึ้นบน Firebase Authentication ซึ่งมีขั้นตอนการลงชื่อเข้าใช้ที่สร้างไว้ล่วงหน้าสำหรับแอปของคุณ

FirebaseUI สำหรับ SwiftUI มีประโยชน์ดังนี้

  • UI เริ่มต้นที่กำหนดไว้: เพิ่มขั้นตอนการลงชื่อเข้าใช้ที่สมบูรณ์ด้วย AuthPickerView
  • ปรับแต่งได้: แสดงผลปุ่มเริ่มต้นในเลย์เอาต์ของคุณเอง หรือสร้างประสบการณ์การใช้งานที่กำหนดเองทั้งหมด
  • การลิงก์บัญชีแบบไม่ระบุชื่อ: อัปเกรดผู้ใช้ที่ไม่ระบุชื่อ (ไม่บังคับ) แทนการแทนที่ผู้ใช้เหล่านั้น
  • การจัดการบัญชี: ขั้นตอนในตัวสำหรับการลงชื่อสมัครใช้ การกู้คืนรหัสผ่าน และการจัดการบัญชี
  • ผู้ให้บริการหลายราย: อีเมล/รหัสผ่าน, ลิงก์อีเมล, การตรวจสอบสิทธิ์ทางโทรศัพท์, Apple, Google, Facebook, Twitter และผู้ให้บริการ OAuth2/OIDC มาตรฐาน
  • ฟีเจอร์การตรวจสอบสิทธิ์ที่ทันสมัย: การรองรับในตัวสำหรับการตรวจสอบสิทธิ์แบบหลายปัจจัย (MFA) และ API แบบไม่พร้อมกัน/รอ

ก่อนเริ่มต้น

การติดตั้ง

FirebaseUI สำหรับ SwiftUI มีให้บริการเป็นแพ็กเกจ Swift หากต้องการติดตั้งแพ็กเกจในโปรเจ็กต์ Xcode ให้ทำดังนี้

  1. ใน Xcode ให้คลิกไฟล์ > เพิ่มทรัพยากร Dependency ของแพ็กเกจ

  2. ป้อน URL ของแพ็กเกจ:

    https://github.com/firebase/FirebaseUI-iOS
    
  3. ในเมนูกฎการขึ้นต่อกัน ให้เลือกจนถึงเวอร์ชันหลักถัดไป และตั้งค่าเวอร์ชันขั้นต่ำเป็นรุ่นล่าสุด

  4. คลิกเพิ่มแพ็กเกจ แล้วเลือกไลบรารีที่จะเพิ่มลงในโปรเจ็กต์

    ต้องใช้ไลบรารีต่อไปนี้เสมอ ซึ่งรวมถึงทรัพยากร Dependency หลัก ตลอดจนการรองรับการลงชื่อเข้าใช้ด้วยอีเมลและรหัสผ่านและการลงชื่อเข้าใช้ด้วยลิงก์อีเมล

    • FirebaseAuthSwiftUI

     หากต้องการรองรับวิธีการลงชื่อเข้าใช้อื่นๆ ให้เลือกไลบรารีต่อไปนี้อย่างน้อย 1 รายการด้วย

    • FirebaseAppleSwiftUI (ลงชื่อเข้าใช้ด้วย Apple)
    • FirebaseGoogleSwiftUI (ลงชื่อเข้าใช้ด้วย Google)
    • FirebaseFacebookSwiftUI (ลงชื่อเข้าใช้ด้วย Facebook)
    • FirebasePhoneAuthSwiftUI (การตรวจสอบสิทธิ์ทางโทรศัพท์)
    • FirebaseTwitterSwiftUI (ลงชื่อเข้าใช้ด้วย Twitter)
    • FirebaseOAuthSwiftUI (ผู้ให้บริการ OAuth และ OIDC มาตรฐาน เช่น GitHub, Microsoft, Yahoo)
  5. คลิกเพิ่มแพ็กเกจเพื่อติดตั้งไลบรารีที่เลือก

  6. กำหนดค่า AuthService ของ FirebaseUI ในตัวเริ่มต้นของ View ระดับบนสุด และส่งบริการไปยังมุมมองย่อยโดยใช้สภาพแวดล้อม

    import FirebaseAuthSwiftUI
    import SwiftUI
    
    struct ContentView: View {
      let authService: AuthService
    
      init() {
        let configuration = AuthConfiguration()
    
        authService = AuthService(configuration: configuration)
          .withEmailSignIn() // Or whatever sign-in methods you want to support.
                             // See the next section.
      }
    
      var body: some View {
        AuthPickerView { // AuthPickerView (the prebuilt View) or a custom View.
            Text("Welcome to your app!")
        }
        .environment(authService)
      }
    }
    

ตั้งค่าวิธีการลงชื่อเข้าใช้

วิธีการลงชื่อเข้าใช้ที่รองรับแต่ละวิธีต้องมีขั้นตอนการตั้งค่าเพิ่มเติมบางอย่าง ขยายส่วนด้านล่างและทำตามวิธีการเพื่อตั้งค่าผู้ให้บริการแต่ละราย

อีเมลและรหัสผ่าน

  1. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้ผู้ให้บริการอีเมล/รหัสผ่าน

  2. ลงทะเบียนผู้ให้บริการในอินสแตนซ์ AuthService โดยทำดังนี้

    let authService = AuthService()
      .withEmailSignIn()
    

วิธีใช้การลงชื่อเข้าใช้ด้วยลิงก์อีเมลแบบไม่ใช้รหัสผ่าน

  1. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้อีเมล/รหัสผ่าน จากนั้นเปิดใช้การลงชื่อเข้าใช้ด้วยลิงก์อีเมล โปรดทราบว่าคุณต้องเปิดใช้การลงชื่อเข้าใช้ด้วยอีเมลหรือรหัสผ่านเพื่อใช้การลงชื่อเข้าใช้ด้วยลิงก์อีเมล

  2. เพิ่มโดเมนลิงก์ลงในโดเมนที่ได้รับอนุญาต

  3. กำหนดค่า ActionCodeSettings ส่งไปยัง AuthConfiguration และลงทะเบียนผู้ให้บริการ:

    let actionCodeSettings = ActionCodeSettings()
    actionCodeSettings.handleCodeInApp = true
    actionCodeSettings.url = URL(string: "https://yourapp.firebaseapp.com")
    
    guard let bundleID = Bundle.main.bundleIdentifier else {
      fatalError("Missing bundle identifier for email link authentication setup.")
    }
    actionCodeSettings.setIOSBundleID(bundleID)
    
    let configuration = AuthConfiguration(
      emailLinkSignInActionCodeSettings: actionCodeSettings
    )
    
    let authService = AuthService(configuration: configuration)
      .withEmailLinkSignIn()
    
  4. ใน AppDelegate เดียวกันกับที่คุณเรียกใช้ FirebaseApp.configure() ให้เพิ่มตรรกะลงในเมธอด application(_:open:options:) ที่แสดงผล true เมื่อ URL ที่เปิดเป็นลิงก์ลงชื่อเข้าใช้ Firebase Authentication ตรรกะนี้บ่งชี้ว่า FirebaseUI จัดการลิงก์แล้ว และไม่ควรประมวลผลโดยเครื่องมือจัดการลิงก์อื่นๆ

    import FacebookCore
    import FirebaseAuth
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return true
      }
    
      func application(
        _ application: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
      ) -> Bool {
        if Auth.auth().canHandle(url) {
          return true
        }
        return false
      }
    }
    
  5. หากคุณสร้างมุมมองที่กำหนดเอง ให้เรียกใช้ authService.handleSignInLink(url:) เมื่อลิงก์เปิดแอปของคุณ

Apple

วิธีใช้ฟีเจอร์ลงชื่อเข้าใช้ด้วย Apple

  1. วิธีกำหนดค่าฟีเจอร์ลงชื่อเข้าใช้ด้วย Apple

    1. เปิดใช้ฟีเจอร์ลงชื่อเข้าใช้ด้วย Apple สำหรับแอปในหน้าใบรับรอง ตัวระบุ และโปรไฟล์ของเว็บไซต์นักพัฒนาแอป Apple
    2. เชื่อมโยงเว็บไซต์กับแอปตามที่อธิบายไว้ในส่วนแรกของกำหนดค่าการลงชื่อเข้าใช้ด้วย Apple สำหรับเว็บ เมื่อได้รับข้อความแจ้ง ให้ลงทะเบียน URL ต่อไปนี้เป็น URL สำหรับการคืนสินค้า
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      คุณดูรหัสโปรเจ็กต์ Firebase ได้ในหน้าการตั้งค่าคอนโซล Firebase เมื่อเสร็จแล้ว ให้จดบันทึกรหัสบริการใหม่ซึ่งคุณจะต้องใช้ในส่วนถัดไป
    3. สร้างคีย์ส่วนตัวสำหรับการลงชื่อเข้าใช้ด้วย Apple คุณจะต้องใช้คีย์ส่วนตัวและรหัสคีย์ใหม่ในส่วนถัดไป
    4. หากคุณใช้ฟีเจอร์ของ Firebase Authentication ที่ส่งอีเมลถึงผู้ใช้ ซึ่งรวมถึงการลงชื่อเข้าใช้ด้วยลิงก์อีเมล การยืนยันอีเมล การเพิกถอนการเปลี่ยนแปลงบัญชี และอื่นๆ ให้กำหนดค่าบริการส่งต่ออีเมลส่วนตัวของ Apple และลงทะเบียน noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (หรือโดเมนเทมเพลตอีเมลที่กำหนดเอง) เพื่อให้ Apple ส่งต่ออีเมลที่ Firebase Authentication ส่งไปยังอีเมล Apple ที่ไม่ระบุตัวตนได้
  2. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้ Apple

    • ระบุรหัสบริการที่คุณสร้างไว้ในส่วนก่อนหน้า
    • ในส่วนการกำหนดค่าโฟลว์โค้ด OAuth ให้ระบุรหัสทีม Apple รวมถึงคีย์ส่วนตัวและรหัสคีย์ที่คุณสร้างไว้ในส่วนก่อนหน้า
  3. ใน Xcode ให้เปิดส่วน Signing & Capabilities ของเครื่องมือแก้ไขโปรเจ็กต์ แล้วเพิ่มความสามารถ Sign in with Apple

  4. ลงทะเบียนผู้ให้บริการในอินสแตนซ์ AuthService โดยทำดังนี้

    let authService = AuthService()
      .withAppleSignIn()
    

Google

วิธีใช้ Google Sign-In

  1. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้ผู้ให้บริการ Google

  2. ดาวน์โหลดสำเนาใหม่ของไฟล์ GoogleService-Info.plist ของโปรเจ็กต์และคัดลอกไปยังโปรเจ็กต์ Xcode เขียนทับเวอร์ชันที่มีอยู่ด้วยเวอร์ชันใหม่

  3. เพิ่ม URL Scheme ที่กำหนดเองลงในโปรเจ็กต์ Xcode โดยทำดังนี้

    1. เปิดการกำหนดค่าโปรเจ็กต์โดยคลิกชื่อโปรเจ็กต์ในมุมมองแบบต้นไม้ทางด้านซ้าย เลือกแอปจากส่วน TARGETS จากนั้นเลือกแท็บ Info แล้วขยายส่วน URL Types

    2. คลิกปุ่ม + แล้วเพิ่ม URL Scheme สำหรับรหัสไคลเอ็นต์ที่กลับกัน หากต้องการค้นหาค่านี้ ให้เปิดไฟล์การกำหนดค่า GoogleService-Info.plist แล้วมองหาคีย์ REVERSED_CLIENT_ID คัดลอกค่าของคีย์นั้น แล้ววางลงในช่อง URL Schemes ในหน้าการกำหนดค่า ปล่อยช่องอื่นไว้โดยไม่ต้องแก้ไข

      เมื่อเสร็จแล้ว การกำหนดค่าควรมีลักษณะคล้ายกับตัวอย่างต่อไปนี้ (แต่มีค่าเฉพาะแอปพลิเคชัน)

  4. ลงทะเบียนผู้ให้บริการในอินสแตนซ์ AuthService โดยทำดังนี้

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

วิธีใช้การเข้าสู่ระบบ Facebook

  1. ตั้งค่า Facebook Login สำหรับ iOS SDK โดยทำตามวิธีการในเว็บไซต์ Meta for Developers ข้ามขั้นตอนสุดท้าย "เพิ่มการเข้าสู่ระบบ Facebook ลงในโค้ด"

  2. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้ผู้ให้บริการ Facebook คุณจะต้องใช้รหัสแอป Facebook และข้อมูลลับของแอปจากเว็บไซต์ Meta for Developers

  3. ลงทะเบียนผู้ให้บริการในอินสแตนซ์ AuthService โดยทำดังนี้

    let authService = AuthService()
     .withFacebookSignIn()
    

หมายเลขโทรศัพท์

วิธีใช้การตรวจสอบสิทธิ์ทางโทรศัพท์

  1. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้ผู้ให้บริการโทรศัพท์

  2. กำหนดค่า APN สำหรับแอปตามวิธีการในส่วนเริ่มรับการแจ้งเตือนแบบไม่มีเสียง

  3. เพิ่ม URL Scheme ที่กำหนดเองลงในโปรเจ็กต์ Xcode โดยทำดังนี้

    1. เปิดการกำหนดค่าโปรเจ็กต์โดยคลิกชื่อโปรเจ็กต์ในมุมมองแบบต้นไม้ทางด้านซ้าย เลือกแอปจากส่วน TARGETS จากนั้นเลือกแท็บ Info แล้วขยายส่วน URL Types

    2. คลิกปุ่ม + แล้วเพิ่มรหัสแอปที่เข้ารหัสเป็น URL Scheme หากต้องการดูค่านี้ ให้เปิดการตั้งค่า > ทั่วไปในคอนโซล Firebase

      เมื่อเสร็จแล้ว การกำหนดค่าควรมีลักษณะคล้ายกับตัวอย่างต่อไปนี้ (แต่มีค่าเฉพาะแอปพลิเคชัน)

  4. ใน AppDelegate เดียวกันกับที่คุณเรียกใช้ FirebaseApp.configure() ให้เพิ่มเครื่องมือจัดการโทเค็น APNs ดังนี้

    import FacebookCore
    import FirebaseAuth
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return true
      }
    
      func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
      ) {
        #if DEBUG
        Auth.auth().setAPNSToken(deviceToken, type: .sandbox)
        #else
        Auth.auth().setAPNSToken(deviceToken, type: .prod)
        #endif
      }
    }
    
  5. ลงทะเบียนผู้ให้บริการในอินสแตนซ์ AuthService โดยทำดังนี้

    let authService = AuthService()
      .withPhoneSignIn()
    

Twitter (X)

วิธีใช้การเข้าสู่ระบบ Twitter

  1. สร้างข้อมูลเข้าสู่ระบบ X API โดยทำตามวิธีการในเว็บไซต์ X Developers

  2. จากส่วนความปลอดภัย > การตรวจสอบสิทธิ์ > วิธีการลงชื่อเข้าใช้ของคอนโซล Firebase ให้เปิดใช้ผู้ให้บริการ Twitter คุณจะต้องมีคีย์ API และ API Secret จากคอนโซล X Developer

  3. ลงทะเบียนผู้ให้บริการในอินสแตนซ์ AuthService โดยทำดังนี้

    let authService = AuthService()
      .withTwitterSignIn()
    

ผู้ให้บริการ OAuth2 และ OIDC มาตรฐาน

นอกจากนี้ FirebaseUI ยังรองรับผู้ให้บริการ OAuth ในตัว เช่น GitHub, Microsoft และ Yahoo รวมถึงผู้ให้บริการ OIDC ที่กำหนดเองซึ่งกำหนดค่าไว้ในการตรวจสอบสิทธิ์ Firebase

 let authService = AuthService()
  .withOAuthSignIn(OAuthProviderSwift.github())
  .withOAuthSignIn(OAuthProviderSwift.microsoft())
  .withOAuthSignIn(OAuthProviderSwift.yahoo())

สำหรับผู้ให้บริการ OIDC ที่กำหนดเอง ให้กำหนดค่าผู้ให้บริการก่อนใน Firebase Authentication จากนั้นสร้าง OAuthProviderSwift ด้วยรหัสผู้ให้บริการและการกำหนดค่าปุ่ม ดังนี้

let lineProvider = OAuthProviderSwift(
  providerId: "oidc.line",
  buttonLabel: "Sign in with LINE",
  displayName: "LINE",
  iconSystemName: "person.crop.circle.badge.checkmark",
  buttonBackgroundColor: .green,
  buttonForegroundColor: .white
)

let authService = AuthService()
  .withOAuthSignIn(lineProvider)

การใช้มุมมองการตรวจสอบสิทธิ์ที่สร้างไว้ล่วงหน้า

FirebaseUI สำหรับ SwiftUI มี AuthPickerView ซึ่งเป็น UI การตรวจสอบสิทธิ์ที่สร้างไว้ล่วงหน้าและมีความเห็นซึ่งจะจัดการขั้นตอนการตรวจสอบสิทธิ์ทั้งหมดให้คุณ วิธีนี้เป็นวิธีที่ง่ายที่สุดในการเพิ่มการตรวจสอบสิทธิ์ลงในแอป

ตัวอย่าง

ต่อไปนี้คือตัวอย่างการใช้งาน AuthPickerView ที่มีผู้ให้บริการและตัวเลือกการกำหนดค่าหลายรายการ

import FirebaseAppleSwiftUI
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import SwiftUI

struct ContentView: View {
  let authService: AuthService

  init() {
    // Create configuration with options
    let configuration = AuthConfiguration(
      tosUrl: URL(string: "https://example.com/tos"),
      privacyPolicyUrl: URL(string: "https://example.com/privacy"),
      shouldAutoUpgradeAnonymousUsers: true
    )

    // Initialize AuthService with multiple providers
    authService = AuthService(configuration: configuration)
      .withEmailSignIn()
      .withAppleSignIn()
      .withGoogleSignIn()
  }

  var body: some View {
    AuthPickerView {
      authenticatedContent
    }
    .environment(authService)
  }

  var authenticatedContent: some View {
    NavigationStack {
      VStack(spacing: 20) {
        if authService.authenticationState == .authenticated {
          Text("Authenticated")
          
          Button("Manage Account") {
            authService.isPresented = true
          }
          .buttonStyle(.bordered)
          
          Button("Sign Out") {
            Task {
              try? await authService.signOut()
            }
          }
          .buttonStyle(.borderedProminent)
        } else {
          Text("Not Authenticated")
          
          Button("Sign In") {
            authService.isPresented = true
          }
          .buttonStyle(.borderedProminent)
        }
      }
      .navigationTitle("My App")
    }
    .onChange(of: authService.authenticationState) { _, newValue in
      // Automatically show auth UI when not authenticated
      if newValue != .authenticating {
        authService.isPresented = (newValue == .unauthenticated)
      }
    }
  }
}

การปรับแต่งทั่วไป

แม้ว่าพารามิเตอร์ AuthConfiguration ทั้งหมดจะไม่บังคับ แต่แอปส่วนใหญ่จะปรับแต่งการตั้งค่าต่อไปนี้เป็นอย่างน้อย

let configuration = AuthConfiguration(
    logo: ImageResource.exampleLogoAsset,
    customStringsBundle: .main,
    tosUrl: URL(string: "https://example.com/tos"),
    privacyPolicyUrl: URL(string: "https://example.com/privacy"),
)
  • logo: รูปภาพโลโก้ที่แสดงในชีตการตรวจสอบสิทธิ์ ดูข้อมูลเกี่ยวกับการรวมชิ้นงานรูปภาพของคุณเองได้ที่หัวข้อการเพิ่มรูปภาพลงในโปรเจ็กต์ Xcode

  • customStringsBundle: ใช้สตริงที่กำหนดเองจาก Bundle ที่ระบุ ซึ่งใช้สำหรับการแปลภาษา รวมถึงการปรับแต่งสตริงเริ่มต้นที่ AuthPickerView ใช้ ตัวอย่างเช่น หากต้องการตั้งค่าข้อความที่แสดงที่ด้านบนของแผ่นการตรวจสอบสิทธิ์ ให้สร้าง Localizable.strings โดยมีเนื้อหาดังนี้

    "Sign in with Firebase" = "Sign in to use ExampleApp";
    

ข้อมูลที่รวมอยู่ในมุมมองที่สร้างไว้ล่วงหน้า

เมื่อใช้ AuthPickerView คุณจะได้รับสิ่งต่อไปนี้

  1. การนำเสนอชีต: UI การตรวจสอบสิทธิ์จะปรากฏเป็นชีตโมดัล
  2. การนำทางในตัว: การนำทางอัตโนมัติระหว่างหน้าจอการลงชื่อเข้าใช้ การกู้คืนรหัสผ่าน, MFA, ลิงก์อีเมล และการยืนยันทางโทรศัพท์
  3. การจัดการสถานะการตรวจสอบสิทธิ์: สลับระหว่าง UI การตรวจสอบสิทธิ์กับเนื้อหาของคุณโดยอัตโนมัติตาม authService.authenticationState
  4. ควบคุมผ่าน isPresented: ควบคุมเวลาที่ชีตการตรวจสอบสิทธิ์จะปรากฏขึ้นโดยการตั้งค่า authService.isPresented = true/false

ลักษณะการทำงานที่กำหนดไว้ล่วงหน้า

AuthPickerView เริ่มต้นจะมีความเห็นเกี่ยวกับวิธีจัดการสถานการณ์ที่ซับซ้อนหลายอย่าง ดังนี้

1. การแก้ไขความขัดแย้งของบัญชี

เมื่อเกิดความขัดแย้งของบัญชี (เช่น การลงชื่อเข้าใช้ด้วยข้อมูลเข้าสู่ระบบที่ลิงก์กับบัญชีอื่นอยู่แล้ว) AuthPickerView จะจัดการโดยอัตโนมัติดังนี้

  • ความขัดแย้งในการอัปเกรดแบบไม่ระบุตัวตน: หากเปิดใช้ shouldAutoUpgradeAnonymousUsers และเกิดความขัดแย้งระหว่างการอัปเกรดแบบไม่ระบุตัวตน ระบบจะนำผู้ใช้ที่ไม่ระบุตัวตนออกจากระบบโดยอัตโนมัติและลงชื่อเข้าใช้ด้วยข้อมูลเข้าสู่ระบบใหม่
  • ความขัดแย้งอื่นๆ: สำหรับความขัดแย้งของข้อมูลเข้าสู่ระบบระหว่างบัญชีที่ไม่ระบุตัวตน ระบบจะจัดเก็บข้อมูลเข้าสู่ระบบที่รอดำเนินการและพยายามลิงก์ข้อมูลดังกล่าวหลังจากลงชื่อเข้าใช้สำเร็จ

ซึ่งจัดการโดย AccountConflictModifier ที่ใช้ในระดับ NavigationStack

2. การตรวจสอบสิทธิ์แบบหลายปัจจัย (MFA)

เมื่อเปิดใช้ MFA ในการกำหนดค่าของคุณ

  • ตรวจหาโดยอัตโนมัติเมื่อต้องใช้ MFA ระหว่างการลงชื่อเข้าใช้
  • แสดงหน้าจอการแก้ปัญหา MFA ที่เหมาะสม (SMS หรือ TOTP)
  • จัดการขั้นตอนการลงทะเบียนและการจัดการ MFA
  • รองรับทั้งปัจจัยรหัสผ่านแบบใช้ครั้งเดียว (TOTP) ที่อิงตาม SMS และอิงตามเวลา
3. การจัดการข้อผิดพลาด

มุมมองเริ่มต้นจะมีการจัดการข้อผิดพลาดในตัว ดังนี้

  • แสดงข้อความแสดงข้อผิดพลาดที่ใช้ง่ายในกล่องโต้ตอบการแจ้งเตือน
  • กรองข้อผิดพลาดที่จัดการภายในออกโดยอัตโนมัติ (เช่น ข้อผิดพลาดในการยกเลิก ข้อขัดแย้งที่จัดการโดยอัตโนมัติ)
  • ใช้ข้อความแสดงข้อผิดพลาดที่แปลแล้วผ่าน StringUtils
  • ข้อผิดพลาดจะเผยแพร่ผ่านคีย์สภาพแวดล้อม reportError

เมื่อกำหนดค่าการลงชื่อเข้าใช้ด้วยลิงก์อีเมลแล้ว สิ่งที่จะเกิดขึ้นมีดังนี้

  • จัดเก็บอีเมลในพื้นที่เก็บข้อมูลของแอปโดยอัตโนมัติ
  • จัดการการไปยังส่วนต่างๆ ของ Deep Link จากอีเมล
  • จัดการขั้นตอนการยืนยันอีเมลทั้งหมด
  • รองรับการอัปเกรดผู้ใช้ที่ไม่ระบุตัวบุคคลผ่านลิงก์อีเมล
5. การอัปเกรดผู้ใช้ที่ไม่ระบุชื่อโดยอัตโนมัติ

เมื่อเปิดใช้ shouldAutoUpgradeAnonymousUsers

  • พยายามลิงก์บัญชีที่ไม่ระบุตัวตนกับข้อมูลเข้าสู่ระบบใหม่โดยอัตโนมัติ
  • เก็บรักษาข้อมูลผู้ใช้โดยการอัปเกรดแทนการแทนที่เซสชันที่ไม่ระบุตัวตน
  • จัดการความขัดแย้งในการอัปเกรดได้อย่างราบรื่น
6. การตรวจสอบสิทธิ์อีกครั้งในมุมมองเริ่มต้น

การดำเนินการที่มีความละเอียดอ่อน เช่น การลบบัญชี การอัปเดตรหัสผ่าน หรือการยกเลิกการลงทะเบียนปัจจัย MFA ต้องมีการตรวจสอบสิทธิ์ล่าสุด เมื่อใช้มุมมองเริ่มต้น ระบบจะจัดการการตรวจสอบสิทธิ์ซ้ำโดยอัตโนมัติตามผู้ให้บริการลงชื่อเข้าใช้ของผู้ใช้

เมื่อการดำเนินการที่มีความละเอียดอ่อนต้องมีการตรวจสอบสิทธิ์อีกครั้ง มุมมองเริ่มต้นจะแสดงโดยอัตโนมัติ ดังนี้

  • ผู้ให้บริการ OAuth (Google, Apple, Facebook, Twitter ฯลฯ) แสดงการแจ้งเตือนที่ขอให้ผู้ใช้ยืนยัน จากนั้นรับข้อมูลเข้าสู่ระบบใหม่โดยอัตโนมัติและดำเนินการให้เสร็จสมบูรณ์

  • อีเมล/รหัสผ่าน: แสดงชีตที่แจ้งให้ผู้ใช้ป้อนรหัสผ่านก่อนดำเนินการต่อ

  • ลิงก์อีเมล: แสดงการแจ้งเตือนที่ขอให้ส่งอีเมลยืนยัน จากนั้นแสดงชีตพร้อมวิธีการตรวจสอบอีเมล ผู้ใช้แตะลิงก์ในอีเมลเพื่อตรวจสอบสิทธิ์อีกครั้งให้เสร็จสมบูรณ์

  • โทรศัพท์: แสดงการแจ้งเตือนที่อธิบายว่าจำเป็นต้องมีการยืนยัน จากนั้นแสดงชีตสำหรับการยืนยันรหัสทาง SMS

การดำเนินการจะลองอีกครั้งโดยอัตโนมัติหลังจากตรวจสอบสิทธิ์ซ้ำสำเร็จ ไม่จำเป็นต้องใช้โค้ดเพิ่มเติมเมื่อใช้ AuthPickerView หรือมุมมองการจัดการบัญชีในตัว (UpdatePasswordView, SignedInView ฯลฯ)

ขั้นสูง: การสร้างมุมมองการตรวจสอบสิทธิ์ที่กำหนดเอง

หากต้องการควบคุม UI หรือขั้นตอนการนําทางมากขึ้น คุณสามารถสร้างมุมมองการตรวจสอบสิทธิ์ที่กําหนดเองได้โดยยังคงใช้ประโยชน์จาก AuthService สําหรับตรรกะการตรวจสอบสิทธิ์

คุณสามารถผสมผสานองค์ประกอบของ FirebaseUI กับตรรกะที่กำหนดเองได้หลายวิธี ส่วนต่อไปนี้มีตัวอย่างแนวทางการปรับแต่งบางอย่างที่คุณอาจใช้

แนวทางที่ 1: ปุ่มที่กำหนดเองพร้อม registerProvider()

หากต้องการควบคุมลักษณะที่ปรากฏของปุ่มอย่างสมบูรณ์ คุณสามารถสร้างการติดตั้งใช้งาน AuthProviderUI ที่กำหนดเองซึ่งรวมผู้ให้บริการไว้และแสดงผลมุมมองปุ่มที่กำหนดเอง

การสร้าง UI ของผู้ให้บริการที่กำหนดเอง

ตัวอย่างวิธีสร้างปุ่ม Twitter ที่กำหนดเองมีดังนี้

import FirebaseAuthSwiftUI
import FirebaseTwitterSwiftUI
import SwiftUI

// Step 1: Create your custom button view
struct CustomTwitterButton: View {
  let provider: TwitterProviderSwift
  @Environment(AuthService.self) private var authService
  @Environment(\.mfaHandler) private var mfaHandler

  var body: some View {
    Button {
      Task {
        do {
          let outcome = try await authService.signIn(provider)

          // Handle MFA if required
          if case let .mfaRequired(mfaInfo) = outcome,
             let onMFA = mfaHandler {
            onMFA(mfaInfo)
          }
        } catch {
          // Do Something Else
        }
      }
    } label: {
      HStack { // Your custom icon
        Text("Sign in with Twitter")
          .fontWeight(.semibold)
      }
      .frame(maxWidth: .infinity)
      .padding()
      .background(
        LinearGradient(
          colors: [Color.blue, Color.cyan],
          startPoint: .leading,
          endPoint: .trailing
        )
      )
      .foregroundColor(.white)
      .cornerRadius(12)
      .shadow(radius: 4)
    }
  }
}

// Step 2: Create a custom AuthProviderUI wrapper
class CustomTwitterProviderAuthUI: AuthProviderUI {
  private let typedProvider: TwitterProviderSwift
  var provider: AuthProviderSwift { typedProvider }
  let id: String = "twitter.com"

  init(provider: TwitterProviderSwift = TwitterProviderSwift()) {
    typedProvider = provider
  }

  @MainActor func authButton() -> AnyView {
    AnyView(CustomTwitterButton(provider: typedProvider))
  }
}

// Step 3: Use it in your app
struct ContentView: View {
  let authService: AuthService

  init() {
    let configuration = AuthConfiguration()
    authService = AuthService(configuration: configuration)

    // Register your custom provider UI
    authService.registerProvider(
      providerWithButton: CustomTwitterProviderAuthUI()
    )
    authService.isPresented = true
  }

  var body: some View {
    AuthPickerView {
      usersApp
    }
    .environment(authService)
  }

  var usersApp: some View {
    NavigationStack {
      VStack {
        Button {
          authService.isPresented = true
        } label: {
          Text("Authenticate")
        }
      }
    }
  }
}

ตัวอย่างปุ่มที่กำหนดเองแบบง่าย

นอกจากนี้ คุณยังสร้างปุ่มที่กำหนดเองที่เรียบง่ายขึ้นสำหรับผู้ให้บริการรายใดก็ได้ ดังนี้

import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI

// Custom Google Provider UI
class CustomGoogleProviderAuthUI: AuthProviderUI {
  private let typedProvider: GoogleProviderSwift
  var provider: AuthProviderSwift { typedProvider }
  let id: String = "google.com"
  
  init() {
    typedProvider = GoogleProviderSwift()
  }
  
  @MainActor func authButton() -> AnyView {
    AnyView(CustomGoogleButton(provider: typedProvider))
  }
}

struct CustomGoogleButton: View {
  let provider: GoogleProviderSwift
  @Environment(AuthService.self) private var authService
  
  var body: some View {
    Button {
      Task {
        try? await authService.signIn(provider)
      }
    } label: {
      HStack {
        Image(systemName: "g.circle.fill")
        Text("My Custom Google Button")
      }
      .frame(maxWidth: .infinity)
      .padding()
      .background(Color.purple) // Your custom color
      .foregroundColor(.white)
      .cornerRadius(10)
    }
  }
}

// Then use it
struct ContentView: View {
  let authService: AuthService
  
  init() {
    let configuration = AuthConfiguration()
    authService = AuthService(configuration: configuration)
      .withAppleSignIn() // Use default Apple button
    
    // Use custom Google button
    authService.registerProvider(
      providerWithButton: CustomGoogleProviderAuthUI()
    )
  }
  
  var body: some View {
    AuthPickerView {
      Text("App Content")
    }
    .environment(authService)
  }
}

วิธีนี้ใช้ได้กับผู้ให้บริการทุกราย ได้แก่ Google, Apple, Twitter, Facebook, โทรศัพท์ และผู้ให้บริการ OAuth เพียงสร้างมุมมองปุ่มที่กำหนดเองและรวมไว้ในคลาสที่สอดคล้องกับ AuthProviderUI

แนวทางที่ 2: ปุ่มเริ่มต้นที่มีมุมมองที่กำหนดเอง

คุณสามารถใช้ AuthService.renderButtons() และข้าม AuthPickerView เพื่อแสดงผลปุ่มการตรวจสอบสิทธิ์เริ่มต้นขณะที่ระบุเลย์เอาต์และการนำทางของคุณเองได้ ดังนี้

import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI

struct CustomAuthView: View {
  @Environment(AuthService.self) private var authService

  var body: some View {
    VStack(spacing: 30) {
      // Your custom logo/branding
      Image("app-logo")
        .resizable()
        .frame(width: 150, height: 150)
      
      Text("Welcome to My App")
        .font(.largeTitle)
        .fontWeight(.bold)
      
      Text("Sign in to continue")
        .font(.subheadline)
        .foregroundStyle(.secondary)
      
      // Render default auth buttons
      authService.renderButtons(spacing: 12)
        .padding()
    }
    .padding()
  }
}

struct ContentView: View {
  init() {
    let configuration = AuthConfiguration()
    
    authService = AuthService(configuration: configuration)
      .withGoogleSignIn()
      .withAppleSignIn()
  }
  
  let authService: AuthService

  var body: some View {
    NavigationStack {
      if authService.authenticationState == .authenticated {
        Text("Authenticated!")
      } else {
        CustomAuthView()
      }
    }
    .environment(authService)
  }
}

แนวทางที่ 3: มุมมองที่กำหนดเองพร้อมการนำทางที่กำหนดเอง

หากต้องการควบคุมขั้นตอนทั้งหมดอย่างสมบูรณ์ คุณสามารถข้าม AuthPickerView และสร้างระบบการนำทางของคุณเองได้โดยทำดังนี้

import FirebaseAuth
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import SwiftUI

enum CustomAuthRoute {
  case signIn
  case phoneVerification
  case mfaResolution
}

struct ContentView: View {
  private let authService: AuthService
  @State private var navigationPath: [CustomAuthRoute] = []
  @State private var errorMessage: String?

  init() {
    let configuration = AuthConfiguration()
    self.authService = AuthService(configuration: configuration)
      .withGoogleSignIn()
      .withPhoneSignIn()
  }

  var body: some View {
    NavigationStack(path: $navigationPath) {
      Group {
        if authService.authenticationState == .authenticated {
          authenticatedView
        } else {
          customSignInView
        }
      }
      .navigationDestination(for: CustomAuthRoute.self) { route in
        switch route {
        case .signIn:
          customSignInView
        case .phoneVerification:
          customPhoneVerificationView
        case .mfaResolution:
          customMFAView
        }
      }
    }
    .environment(authService)
    .alert("Error", isPresented: .constant(errorMessage != nil)) {
      Button("OK") {
        errorMessage = nil
      }
    } message: {
      Text(errorMessage ?? "")
    }
  }

  var customSignInView: some View {
    VStack(spacing: 20) {
      Text("Custom Sign In")
        .font(.title)
      
      Button("Sign in with Google") {
        Task {
          do {
            let provider = GoogleProviderSwift(clientID: Auth.auth().app?.options.clientID ?? "")
            let outcome = try await authService.signIn(provider)
            
            // Handle MFA if required
            if case .mfaRequired = outcome {
              navigationPath.append(.mfaResolution)
            }
          } catch {
            errorMessage = error.localizedDescription
          }
        }
      }
      .buttonStyle(.borderedProminent)
      
      Button("Phone Sign In") {
        navigationPath.append(.phoneVerification)
      }
      .buttonStyle(.bordered)
    }
    .padding()
  }

  var customPhoneVerificationView: some View {
    Text("Custom Phone Verification View")
    // Implement your custom phone auth UI here
  }

  var customMFAView: some View {
    Text("Custom MFA Resolution View")
    // Implement your custom MFA UI here
  }

  var authenticatedView: some View {
    VStack(spacing: 20) {
      Text("Welcome!")
      Text("Email: \(authService.currentUser?.email ?? "N/A")")
      
      Button("Sign Out") {
        Task {
          try? await authService.signOut()
        }
      }
      .buttonStyle(.borderedProminent)
    }
  }
}

ข้อควรพิจารณาที่สำคัญสำหรับมุมมองที่กำหนดเอง

เมื่อสร้างมุมมองที่กำหนดเอง คุณต้องจัดการหลายสิ่งด้วยตนเองซึ่ง AuthPickerView จะจัดการให้โดยอัตโนมัติ ดังนี้

  1. ความขัดแย้งของบัญชี: ใช้กลยุทธ์การแก้ไขความขัดแย้งของคุณเองโดยใช้ AuthServiceError.accountConflict
  2. การจัดการ MFA: ตรวจสอบ SignInOutcome เพื่อหา .mfaRequired และจัดการการแก้ปัญหา MFA ด้วยตนเอง
  3. การอัปเกรดผู้ใช้ที่ไม่ระบุตัวตน: จัดการการลิงก์บัญชีที่ไม่ระบุตัวตนหากเปิดใช้ shouldAutoUpgradeAnonymousUsers
  4. สถานะการนำทาง: จัดการการนำทางระหว่างหน้าจอการตรวจสอบสิทธิ์ต่างๆ (การยืนยันทางโทรศัพท์ การกู้คืนรหัสผ่าน ฯลฯ)
  5. สถานะการโหลด: แสดงสัญญาณบอกสถานะการโหลดระหว่างการดำเนินการตรวจสอบสิทธิ์แบบไม่พร้อมกันโดยสังเกต authService.authenticationState
  6. การตรวจสอบสิทธิ์ซ้ำ: จัดการข้อผิดพลาดในการตรวจสอบสิทธิ์ซ้ำสำหรับการดำเนินการที่มีความละเอียดอ่อน (ดูการตรวจสอบสิทธิ์ซ้ำในมุมมองที่กำหนดเองด้านล่าง)

การตรวจสอบสิทธิ์ซ้ำในมุมมองที่กำหนดเอง

เมื่อสร้างมุมมองที่กำหนดเอง ให้จัดการการตรวจสอบสิทธิ์อีกครั้งโดยตรวจจับข้อผิดพลาดที่เฉพาะเจาะจงและใช้โฟลว์ของคุณเอง การดำเนินการที่มีความละเอียดอ่อนจะแสดงข้อผิดพลาดในการตรวจสอบสิทธิ์ซ้ำ 4 ประเภท ซึ่งแต่ละประเภทจะมีข้อมูลบริบท

รูปแบบการติดตั้งใช้งาน

ผู้ให้บริการ OAuth (Google, Apple, Facebook, Twitter ฯลฯ):

ตรวจจับข้อผิดพลาดและเรียกใช้ reauthenticate(context:) ซึ่งจะจัดการขั้นตอน OAuth โดยอัตโนมัติ

do {
  try await authService.deleteUser()
} catch let error as AuthServiceError {
  if case .oauthReauthenticationRequired(let context) = error {
    try await authService.reauthenticate(context: context)
    try await authService.deleteUser() // Retry operation
  }
}

อีเมล/รหัสผ่าน:

ตรวจจับข้อผิดพลาด แจ้งให้ป้อนรหัสผ่าน สร้างข้อมูลเข้าสู่ระบบ และเรียกใช้ reauthenticate(with:):

do {
  try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
  if case .emailReauthenticationRequired(let context) = error {
    // Show your password prompt UI
    let password = await promptUserForPassword()
    let credential = EmailAuthProvider.credential(
      withEmail: context.email,
      password: password
    )
    try await authService.reauthenticate(with: credential)
    try await authService.updatePassword(to: newPassword) // Retry
  }
}

โทรศัพท์:

ตรวจจับข้อผิดพลาด ยืนยันโทรศัพท์ สร้างข้อมูลเข้าสู่ระบบ และเรียกใช้ reauthenticate(with:):

do {
  try await authService.deleteUser()
} catch let error as AuthServiceError {
  if case .phoneReauthenticationRequired(let context) = error {
    // Send verification code
    let verificationId = try await authService.verifyPhoneNumber(
      phoneNumber: context.phoneNumber
    )
    // Show your SMS code input UI
    let code = await promptUserForSMSCode()
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: code
    )
    try await authService.reauthenticate(with: credential)
    try await authService.deleteUser() // Retry
  }
}

Email Link:

ตรวจจับข้อผิดพลาด ส่งอีเมลยืนยัน และจัดการ URL ขาเข้า

do {
  try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
  if case .emailLinkReauthenticationRequired(let context) = error {
    // Send verification email
    try await authService.sendEmailSignInLink(
      email: context.email,
      isReauth: true
    )
    // Show your "Check your email" UI
    await showCheckEmailUI()
    // When user taps the link, it opens your app with a URL
    // Handle it in your URL handler:
    // try await authService.handleSignInLink(url: url)
    // The handleSignInLink method automatically completes reauthentication
    try await authService.updatePassword(to: newPassword) // Retry
  }
}

ออบเจ็กต์บริบทการตรวจสอบสิทธิ์ซ้ำทั้งหมดจะมีพร็อพเพอร์ตี้ .displayMessage สำหรับข้อความที่แสดงต่อผู้ใช้

ผู้ให้บริการ OAuth ที่กำหนดเอง

คุณสามารถสร้างผู้ให้บริการ OAuth ที่กำหนดเองสำหรับบริการนอกเหนือจากบริการในตัวได้ดังนี้

⚠️ สำคัญ: ต้องกำหนดค่าผู้ให้บริการ OIDC (OpenID Connect) ในการตั้งค่าการตรวจสอบสิทธิ์ของโปรเจ็กต์ Firebase ก่อนจึงจะใช้งานได้ ในคอนโซล Firebase ให้ไปที่ Authentication → Sign-in method แล้วเพิ่มผู้ให้บริการ OIDC พร้อมข้อมูลเข้าสู่ระบบที่จำเป็น (รหัสไคลเอ็นต์, รหัสลับไคลเอ็นต์, URL ของผู้ออก) นอกจากนี้ คุณยังต้องลงทะเบียน URI การเปลี่ยนเส้นทาง OAuth ที่ Firebase ให้ไว้ในคอนโซลนักพัฒนาซอฟต์แวร์ของผู้ให้บริการด้วย ดูวิธีการตั้งค่าโดยละเอียดได้ที่เอกสารประกอบ Firebase OIDC

import FirebaseAuthSwiftUI
import FirebaseOAuthSwiftUI
import SwiftUI

struct ContentView: View {
  let authService: AuthService

  init() {
    let configuration = AuthConfiguration()
    
    authService = AuthService(configuration: configuration)
      .withOAuthSignIn(
        OAuthProviderSwift(
          providerId: "oidc.line",  // LINE OIDC provider
          scopes: ["profile", "openid", "email"],  // LINE requires these scopes
          displayName: "Sign in with LINE",
          buttonIcon: Image("line-logo"),
          buttonBackgroundColor: .green,
          buttonForegroundColor: .white
        )
      )
      .withOAuthSignIn(
        OAuthProviderSwift(
          providerId: "oidc.custom-provider",
          scopes: ["profile", "openid"],
          displayName: "Sign in with Custom",
          buttonIcon: Image(systemName: "person.circle"),
          buttonBackgroundColor: .purple,
          buttonForegroundColor: .white
        )
      )
  }

  var body: some View {
    AuthPickerView {
      Text("App Content")
    }
    .environment(authService)
  }
}