How to Log-in with Firebase in Swift UI? - swift

I can create user through my code and get in to the next view but I can not log in with that mail address and password I just created. On the other hand, when I create a user in Firebase authentication page without programmatically, I can log in with that mail address and password. I don't know why this is happening.
My login button's codes are as following...
Button(action: {
self.verify()
}) {
Text("Log In")
}
func verify(){
if self.mail != "" && self.pass != ""{
Auth.auth().signIn(withEmail: self.mail, password: self.pass) { (res, err) in
if err != nil{
self.error = err!.localizedDescription
self.alert.toggle()
return
}
print("success")
UserDefaults.standard.set(true, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("status"), object: nil)
}
}
else{
self.error = "Please fill all the contents properly"
self.alert.toggle()
}
}
My signUp button's codes are as following...
Button(action: {
self.register()
}) {
Text("Sign Up")
}
func register(){
if self.mail != ""{
if self.pass == self.repass{
Auth.auth().createUser(withEmail: self.mail, password: self.pass) { (res, err) in
if err != nil{
self.error = err!.localizedDescription
self.alert.toggle()
return
}
print("success")
UserDefaults.standard.set(true, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("status"), object: nil)
}
}
else{
self.error = "Password mismatch"
self.alert.toggle()
}
}
else{
self.error = "Please fill all the contents properly"
self.alert.toggle()
}
}

To Sign In in Firebase I recommend some updates in your code:
Use completion to your Sign In and Sign Up functions, this way you know for sure that Firebase is answer you.
Handle listener AuthStateDidChangeListenerHandle. This listener gets called whenever the user's sign-in state changes.
API Authentification:
class AuthService {
static func signInUser(email: String, password: String, onSucces: #escaping() -> Void, onError: #escaping (_ errorMessage : String) -> Void ) {
Auth.auth().signIn(withEmail: email, password: password) { (authData, error) in
if (error != nil) {
print(error!.localizedDescription)
onError(error!.localizedDescription)
return
}
//Sign In Code
onSuccess()
}
static func signUpUser(email: String, password: String, username: String, onSucces: #escaping() -> Void, onError: #escaping (_ errorMessage : String) -> Void ) {
Auth.auth().createUser(withEmail: email, password: password) { (authData, error) in
if (error != nil) {
print(error!.localizedDescription)
onError(error!.localizedDescription)
return
}
//Sign Up Code
onSuccess()
}
}
}
in your View or ViewModel:
func verify() {
if self.mail != "" && self.pass != ""{
AuthService.signUpUser(email: email, password: password, username: username, onSucces: {
print("Sign In!")
}) { (error) in {
print("Error \(error)")
})
}
}
Class Session, to handle Auth Listener
class SessionAuth : ObservableObject {
#Published var isLoggedIn = true
var handle : AuthStateDidChangeListenerHandle?
func listenAuthentificationState() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
//You are log in
// Your login process code
self.isLoggedIn = true
}
else {
//You are not
self.isLoggedIn = false
}
}
}
Your main View:
struct ContentView: View {
#ObservedObject var session : SessionAuth //or EnvironmentObject
func listen() {
session.listenAuthentificationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainLoggedView()
}
else {
SignInView()
}
}.onAppear(perform: listen)
}
}

In if else()
you need to "return ();"
and,
you should create login activity in programa manner.
FOR bETTER ResulT.

Related

How to check if a user already exists in a Firestore collection after signing in with Firebase Auth in SwiftUI

#Published var isNewUser: Bool?
init() {
self.isNewUser = false
}
func checkIfTheUserExistsInDataBase(
userID: String?, completion: #escaping (_ isNewuser: Bool) -> Void
) {
let docRef = db.collection("users").whereField("user_id", isEqualTo: userID!).limit(to: 1)
docRef.getDocuments { querySnapshot, error in
if error != nil {
print(error?.localizedDescription)
} else {
if let doc = querySnapshot?.documents, doc.isEmpty {
completion(true)
} else {
completion(false)
}
}
}
}
func login(
email: String, password: String,
completion: #escaping (_ error: Error?, _ isEmailVerified: Bool) -> Void
) {
Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in
if error == nil {
if authDataResult!.user.isEmailVerified {
DispatchQueue.main.async {
self.checkIfTheUserExistsInDataBase(userID: authDataResult?.user.uid) { isNewUser in
self.isNewUser = isNewUser
}
}
UserDefaults.standard.set(authDataResult?.user.uid, forKey: CurrentUserDefaults.userID)
completion(error, true)
} else {
print("Email not verified")
completion(error, false)
}
} else {
completion(error, false)
}
}
}
I tried to use DispatchSemaphore to let a longer running function execute first which is checkIfTheUserExistsInDataBase, but it froze my app. Is there a better way to do this?
Firebase supports async/await (see this short, this video, and this blog post I created to explain this in detail.
To answer your question: you should use async/await to call signing in the user, waiting for the result, checking if the user exists in your Firestore collection, and the updating the UI.
The following code snippet (which is based on this sample app) uses the new COUNT feature in Firestore to count the number of documents in the users collection to determine if there is at least one user with the ID of the user that has just signed in.
func isNewUser(_ user: User) async -> Bool {
let userId = user.uid
let db = Firestore.firestore()
let collection = db.collection("users")
let query = collection.whereField("userId", isEqualTo: userId)
let countQuery = query.count
do {
let snapshot = try await countQuery.getAggregation(source: .server)
return snapshot.count.intValue >= 0
}
catch {
print(error)
return false
}
}
func signInWithEmailPassword() async -> Bool {
authenticationState = .authenticating
do {
let authResult = try await Auth.auth().signIn(withEmail: self.email, password: self.password)
if await isNewUser(authResult.user) {
}
return true
}
catch {
print(error)
errorMessage = error.localizedDescription
authenticationState = .unauthenticated
return false
}
}
See this video for more details about how to implement Firebase Authentication in SwiftUI apps.

Bug while error handling Firebase Authentication with swiftUI

This is my AuthViewModel:
#Published var userSession: User?
#Published var currentUser: AppUser?
#Published var signupError: Error?
#Published var loginError: Error?
static let shared = AuthViewModel()
init() {
self.userSession = Auth.auth().currentUser
fetchUser()
}
func fetchUser() {
guard let uid = userSession?.uid else { return }
USER_COLLECTION.document(uid).getDocument { snapshot, _ in
guard let user = try? snapshot?.data(as: AppUser.self) else { return }
self.currentUser = user
}
}
func registerUser(withEmail email: String, password: String) {
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
self.signupError = error
print("error")
return
}
guard let user = result?.user else { return }
let data: [String: Any] = ["uid": user.uid, "email": user.email ?? ""]
USER_COLLECTION.document(user.uid).setData(data) { err in
self.userSession = user
self.fetchUser()
}
}
}
func login(withEmail email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
self.loginError = error
return
}
guard let user = result?.user else { return }
self.userSession = user
self.fetchUser()
}
}
I have 2 published variables which are updated every time there is an error in their respective functions. However, when I click the 'sign up' button the first time with invalid credentials, I don't get an alert. I have to click it the second time to see the alert. Attached below is the code from the sign up view SwiftUI button with action and label. The same applies to the login view.
Button {
authViewModel.registerUser(withEmail: email, password: password)
print("called")
if let error = authViewModel.signupError {
alertTitle = "Error signing up!"
alertMessage = error.localizedDescription
alertShowing = true
print(alertShowing)
}
print(alertShowing)
} label: {
CustomAuthButton(text: "Sign Up")
}
Below is the code attached to the sign up view (navigation view):
.alert(alertTitle, isPresented: $alertShowing) {
Button("OK") {
authViewModel.signupError = nil
}
} message: {
Text(alertMessage)
}
Attached below is the code for registering a user. I'll put the result of those print statements below.
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
DispatchQueue.main.async {
self.signupError = error
print("error")
return
}
}
called
false
2022-07-06 12:59:54.424510+0530 InfoMax[62528:2751506] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
error
As you can see, error is being printed late. When I tap the button the second time, it is printed before, so I get the alert.

Why is my swift code executing out of order? Firebase authentication

I am calling Authenticator.loginUser() with the expectation that the method will log in a user then call getCurrentUser(). Based on printed output, getCurrentUser() is executing first. Is there a way to force it to execute in order?
class Authenticator: ObservableObject {
#Published var currentUser: UserProfile = UserProfile()
#Published var user: String = ""
#Published var documentId: String = ""
func loginUser (email: String, password: String, viewModel: UsersViewModel) {
FirebaseAuth.Auth.auth().signIn(withEmail: email, password: password, completion: { result, error in
guard error == nil else {
print ("error: \(error!)")
return
}
print ("user signed in")
})
self.user = self.getCurrentUser(viewModel: viewModel)
}
func getCurrentUser(viewModel: UsersViewModel) -> String {
guard let userID = Auth.auth().currentUser?.uid else {
return ""
}
viewModel.users.forEach { i in
if (i.userId == userID) {
currentUser = i
}
}
documentId = currentUser.documentId!
print("auth.documentId \(documentId)")
return userID
}
}
FirebaseAuth.Auth.auth().signIn is asynchronous - it runs in the background and not on the main thread. This means that signIn will be called some time in the future.
A solution is to put the call to getCurrentUser in the completion block:
func loginUser(email: String, password: String, viewModel: UsersViewModel) {
FirebaseAuth.Auth.auth().signIn(withEmail: email, password: password, completion: { result, error in
guard error == nil else {
print("error: \(error!)")
return
}
print("user signed in")
self.user = self.getCurrentUser(viewModel: viewModel) // move here
})
}

How to use Firebase Auth + Combine to Sign-In and prevent Sign-In

So I've been following Log-In SwiftUI tutorials for Firebase and it's doing what it's supposed to for the most part: An error pops up when one or more of the input fields are left blank.
The problem occurs however, when I fill in the username + password fields with random gibberish. The view changes rather than popping up an error saying that the username is invalid (which I see in my console).
I've done some research and found that the problem might be due to the asynchronous behavior of Firebase and I haven't necessarily connected the error toggle to the sign-In result. But as a noob, I don't know how to implement the trailing closure in my code, and unsure where to go from here.
What change do I need to make exactly to ensure that upon failure of signing in with firebase, the view does not change and error pops up?
Here's my Sign-In function:
func signIn(){
error = false
session.signIn(email: user_account, password: password){
(result, error) in
if let errornew = error {
self.inputerror = errornew.localizedDescription
print("\(String(describing:error))")
self.error = true
self.alert.toggle()
} else {
self.user_account = ""
self.password = ""
}
}
}
Parent View:
struct ContentView: View {
#EnvironmentObject var session:SessionStore
#State var setUp = false
func getUser(){
session.listen()
}
var body: some View {
Group{
if(session.session != nil){
Text("App Home Page")
Text("Welcome")
Text("Email: \(session.session?.email ?? "")")
} else {
OpeningView() // Sign-In function is in a child view under OpeningView()
}
}.onAppear(perform: self.getUser)
}
Session Class:
class SessionStore: ObservableObject{
#Published var isSetUp:Bool?
var didChange = PassthroughSubject<SessionStore, Never>()
var session: User? {didSet {self.didChange.send(self)}}
var handle: AuthStateDidChangeListenerHandle?
let user = Auth.auth().currentUser
//checks to see whether or not we have a user
func listen(){
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
//if we have a user, create a new user model
print("Got the user: \(user)")
self.session = User(uid: user.uid, email: user.email!)
} else {
//if not, then session is nil
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 login(withEmail email: String, password: String, _ callback: ((Error?) ->())? = nil){
Auth.auth().signIn(withEmail: email, password: password){(user, error) in
if let e = error{
callback?(e)
return
}
callback?(nil)
print("Login Successful")
}
}
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?
init(uid: String, email: String?){
self.uid = uid
self.email = email
}
}
Your View needs something like this on top to check whether you are logged in or not:
var body: some View {
ZStack {
if Auth.auth().currentUser != nil {
// Your View when you are logged in.
else {
SignInView()
} }

How to have hashed passwords in Firebase using Swift?

I was following an Uber clone tutorial. I can get log in, registration, and logout working, but the passwords don't seem to be hashed; I can see them plainly in my Firebase database.
Here is my code. First login/sign up/logout function saved in a 'plugins' folder separate from controllers.
import Foundation
import FirebaseAuth
typealias LoginHandler = (_ msg: String?) -> Void;
struct LoginErrorCode {
static let INVALID_EMAIL = "Invalid email, please provide a real email address";
static let WRONG_PASSWORD = "Wrong Password, Please Try Again";
static let PROBLEM_CONNECTING = "Problem Connecting to Database. Please Try Later";
static let USER_NOT_FOUND = "User Not Found, Please Register";
static let EMAIL_ALREADY_IN_USE = "Email Already In Use, Please Use Different Email";
static let WEAK_PASSWORD = "Password Should Be At Least 6 Characters";
}
class AuthProvider {
private static let _instance = AuthProvider();
static var Instance: AuthProvider {
return _instance;
}
func login(withEmail: String, password: String, loginHandler: LoginHandler?) {
FIRAuth.auth()?.signIn(withEmail: withEmail, password: password, completion: { (user, error) in
if error != nil {
self.handleErrors(err: error as! NSError, loginHandler: loginHandler);
} else {
loginHandler?(nil);
}
})
} //login func
func signUp(withEmail: String, password: String, loginHandler: LoginHandler?) {
FIRAuth.auth()?.createUser(withEmail: withEmail, password: password, completion: { (user, error) in
if error != nil {
self.handleErrors(err: error as! NSError, loginHandler: loginHandler);
} else {
if user?.uid != nil {
// store the user to database
DBProvider.Instance.saveUser(withID: user!.uid, email: withEmail, password: password)
//log in the user
self.login(withEmail: withEmail, password: password, loginHandler: loginHandler)
}
}
})
} //sign up func
func logOut() -> Bool {
if FIRAuth.auth()?.currentUser != nil {
do {
try FIRAuth.auth()?.signOut();
return true;
} catch {
return false;
}
}
return true
}
private func handleErrors(err: NSError, loginHandler: LoginHandler?) {
if let errCode = FIRAuthErrorCode(rawValue: err.code) {
switch errCode {
case .errorCodeWrongPassword:
loginHandler?(LoginErrorCode.WRONG_PASSWORD);
break;
case .errorCodeInvalidEmail:
loginHandler?(LoginErrorCode.INVALID_EMAIL);
break;
case .errorCodeUserNotFound:
loginHandler?(LoginErrorCode.USER_NOT_FOUND);
break;
case .errorCodeEmailAlreadyInUse:
loginHandler?(LoginErrorCode.EMAIL_ALREADY_IN_USE);
break;
case .errorCodeWeakPassword:
loginHandler?(LoginErrorCode.WEAK_PASSWORD);
break;
default:
loginHandler?(LoginErrorCode.PROBLEM_CONNECTING);
break;
}
}
}
} //class
And the controller:
import UIKit
import FirebaseAuth
class SignInVC: UIViewController {
private let DRIVER_SEGUE = "DriverVC";
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func login(_ sender: Any) {
if emailTextField.text != "" && passwordTextField.text != "" {
AuthProvider.Instance.login(withEmail: emailTextField.text!, password: passwordTextField.text!, loginHandler: { (message) in
if message != nil {
self.alertTheUser(title: "Problem With Authentication", message: message!);
} else {
self.performSegue(withIdentifier: self.DRIVER_SEGUE, sender: nil)
}
});
} else {
alertTheUser(title: "Email And Password Are Required", message: "Please enter email and password");
}
}
#IBAction func signUp(_ sender: Any) {
if emailTextField.text != "" && passwordTextField.text != "" {
AuthProvider.Instance.signUp(withEmail: emailTextField.text!, password: passwordTextField.text!, loginHandler: { (message) in
if message != nil {
self.alertTheUser(title: "Problem With Creating New Account", message: message!)
} else {
self.performSegue(withIdentifier: self.DRIVER_SEGUE, sender: nil)
}
})
} else {
alertTheUser(title: "Email And Password Are Required", message: "Please enter email and password");
}
}
private func alertTheUser(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert);
let ok = UIAlertAction(title: "OK", style: .default, handler: nil);
alert.addAction(ok);
present(alert, animated: true, completion: nil)
}
} //class
Try using updatePassword(_ password: String, completion: FirebaseAuth.FIRUserProfileChangeCallback? = nil) on FIRUser.