Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI using Firebase Authentication

I'm trying to use SwiftUI + Firebase Authentication via the email/password login. My question is, is there any way to append profile information to the user's authentication information when they create an account, or would I have to use Firebase Auth in tandem with Firestore or Firebase Database? I'm just trying to collect the user's first and last name and possibly city/state/country.

import SwiftUI
import Firebase
import Combine

class SessionStore: ObservableObject {
    var didChange = PassthroughSubject<SessionStore, Never>()
    @Published var session: User? {didSet {self.didChange.send(self) }}
    var handle: AuthStateDidChangeListenerHandle?

    func listen() {
        handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
            if let user = user {
                self.session = User(uid: user.uid, email: user.email, displayName: user.displayName)
            } else {
                self.session = nil
            }
        })
    }

    func signUp(email: String, password: String, handler: @escaping AuthDataResultCallback) {
        Auth.auth().createUser(withEmail: email, password: password, completion: handler)
    }

    func signIn(email: String, password: String, handler: @escaping AuthDataResultCallback) {
        Auth.auth().signIn(withEmail: email, password: password, completion: handler)
    }

    func signOut() {
        do {
            try Auth.auth().signOut()
            self.session = nil
        } catch {
            print("Error Signing Out")
        }
    }

    func unbind() {
        if let handle = handle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }

    deinit {
        unbind()
    }
}

struct User {
    var uid: String
    var email: String?
    var displayName: String?

    init(uid: String, email: String?, displayName: String?) {
        self.uid = uid
        self.email = email
        self.displayName = displayName
    }
}
like image 941
Jason Tremain Avatar asked Nov 20 '19 22:11

Jason Tremain


People also ask

Can I use Firebase just for authentication?

You can use Firebase Authentication to allow users to sign in to your app using one or more sign-in methods, including email address and password sign-in, and federated identity providers such as Google Sign-in and Facebook Login.

How do I integrate Firebase authentication?

Create a Sign-in Flow To begin authentication, a simple Sign-In button is used. In this stage, you'll implement the logic for signing in with Google and then authenticating with Firebase using that Google account. mFirebaseAuth = FirebaseAuth. getInstance();

How does authentication work with Firebase?

How does it work? To sign a user into your app, you first get authentication credentials from the user. These credentials can be the user's email address and password, or an OAuth token from a federated identity provider. Then, you pass these credentials to the Firebase Authentication SDK.


1 Answers

Firebase Authentication manages user authentication - while it does store additional information provided by some of the federated identity providers (such as profile picture URL), it isn't intended to be a profile management solution. If you'd like to store additional information about your users, you can implement this using Cloud Firestore or Firebase Realtime Database.

Please note that the Custom Claims feature is intended for managing advanced role management features - the documentation actually discourages developers from using this feature for storing additional user information, as custom claims are in fact stored in the ID token.

As @krjw correctly mentions, you don't need to use PassthroughObject when using @Published.

Here is a sample implementation:

// File: UserProfileRepository.swift
import Foundation
import Firebase
import FirebaseFirestoreSwift

struct UserProfile: Codable {
  var uid: String
  var firstName: String
  var lastName: String
  var city: String
}

class UserProfileRepository: ObservableObject {
  private var db = Firestore.firestore()

  func createProfile(profile: UserProfile, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
    do {
      let _ = try db.collection("profiles").document(profile.uid).setData(from: profile)
      completion(profile, nil)
    }
    catch let error {
      print("Error writing city to Firestore: \(error)")
      completion(nil, error)
    }
  }

  func fetchProfile(userId: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
    db.collection("profiles").document(userId).getDocument { (snapshot, error) in
      let profile = try? snapshot?.data(as: UserProfile.self)
      completion(profile, error)
    }
  }
}
// File: SessionStore.swift
import Foundation
import Combine
import Firebase

class SessionStore: ObservableObject {
  @Published var session: User?
  @Published var profile: UserProfile?

  private var profileRepository = UserProfileRepository()

  func signUp(email: String, password: String, firstName: String, lastName: String, city: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
    Auth.auth().createUser(withEmail: email, password: password) { (result, error) in
      if let error = error {
        print("Error signing up: \(error)")
        completion(nil, error)
        return
      }

      guard let user = result?.user else { return }
      print("User \(user.uid) signed up.")

      let userProfile = UserProfile(uid: user.uid, firstName: firstName, lastName: lastName, city: city)
      self.profileRepository.createProfile(profile: userProfile) { (profile, error) in
        if let error = error {
          print("Error while fetching the user profile: \(error)")
          completion(nil, error)
          return
        }
        self.profile = profile
        completion(profile, nil)
      }
    }
  }

  func signIn(email: String, password: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
    Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
      if let error = error {
        print("Error signing in: \(error)")
        completion(nil, error)
        return
      }

      guard let user = result?.user else { return }
      print("User \(user.uid) signed in.")

      self.profileRepository.fetchProfile(userId: user.uid) { (profile, error) in
        if let error = error {
          print("Error while fetching the user profile: \(error)")
          completion(nil, error)
          return
        }

        self.profile = profile
        completion(profile, nil)
      }
    }
  }

  func signOut() {
    do {
      try Auth.auth().signOut()
      self.session = nil
      self.profile = nil
    }
    catch let signOutError as NSError {
      print("Error signing out: \(signOutError)")
    }
  }
}

For the UI:

// File: ContentView.swift
import SwiftUI

struct ContentView: View {
  @State var firstName: String = ""
  @State var lastName: String = ""
  @State var city: String = ""
  @State var email: String = ""
  @State var password: String = ""
  @State var confirmPassword: String = ""

  @State var showSignUpForm = true
  @State var showDetails = false

  @ObservedObject var sessionStore = SessionStore()
  @State var profile: UserProfile?

  var body: some View {
    NavigationView {
      VStack {
        if self.showSignUpForm {
          Form {
            Section {
              TextField("First name", text: $firstName)
                .textContentType(.givenName)
              TextField("Last name", text: $lastName)
                .textContentType(.familyName)
              TextField("City", text: $city)
                .textContentType(.addressCity)
            }
            Section {
              TextField("Email", text: $email)
                .textContentType(.emailAddress)
                .autocapitalization(.none)
              SecureField("Password", text: $password)
              SecureField("Confirm password", text: $confirmPassword)
            }
            Button(action: { self.signUp() }) {
              Text("Sign up")
            }
          }
          .navigationBarTitle("Sign up")
        }
        else {
          Form {
            TextField("Email", text: $email)
              .textContentType(.emailAddress)
              .autocapitalization(.none)
            SecureField("Password", text: $password)
            Button(action: { self.signIn() }) {
              Text("Sign in")
            }
          }
          .navigationBarTitle("Sign in")
        }
        Button(action: { self.showSignUpForm.toggle() }) {
          Text(self.showSignUpForm ? "Have an account? Sign in instead." : "No account yet? Click here to sign up instead.")
        }
      }
      .sheet(isPresented: $showDetails) {
        UserProfileView(userProfile: self.profile ??  UserProfile(uid: "", firstName: "", lastName: "", city: ""))
      }
    }
  }

  func signUp() {
    sessionStore.signUp(email: self.email, password: self.password, firstName: self.firstName, lastName: self.lastName, city: self.city) { (profile, error) in
      if let error = error {
        print("Error when signing up: \(error)")
        return
      }
      self.profile = profile
      self.showDetails.toggle()
    }
  }

  func signIn() {
    sessionStore.signIn(email: self.email, password: self.password) { (profile, error) in
      if let error = error {
        print("Error when signing up: \(error)")
        return
      }
      self.profile = profile
      self.showDetails.toggle()
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
// File: UserProfileView.swift
import SwiftUI

struct UserProfileView: View {
  var userProfile: UserProfile
  var body: some View {
    NavigationView {
      Form {
        Text(userProfile.uid)
        Text(userProfile.firstName)
        Text(userProfile.lastName)
        Text(userProfile.city)
      }
      .navigationBarTitle("User \(userProfile.uid)")
    }
  }
}

struct UserProfileView_Previews: PreviewProvider {
  static var previews: some View {
    let userProfile = UserProfile(uid: "TEST1234", firstName: "Peter", lastName: "Friese", city: "Hamburg")
    return UserProfileView(userProfile: userProfile)
  }
}
like image 90
Peter Friese Avatar answered Oct 30 '22 06:10

Peter Friese