Firebase Anonymous Auth, Nil User - ios

I'm using Firebase database and offer anonymous login. The first anonymous login made on a single device works as expected. If I sign out and attempt any more anonymous logins, it succeeds, the completion block has no error and returns a user.
However, once it's all done and we're out of the completion block, Auth.auth().currentUser() is nil.
If I run a simple Timer checking Auth.auth().currentUser() every second, throughout the entire login process it is always nil and never changes.
Quick breakdown of code:
Login anonymously.
Check if id exists in db.
Update profile displayName with id for easy referral later.
Fetch client in db.
All go wrong!
Tap a button to sign in.
#IBAction func clientLoginBtnTap(_ sender : UIButton) {
self.clientActivityIndicator?.showActivityIndicator()
Auth.auth().signInAnonymously { (user, error) in
if error == nil {
//check id matches available client
self.checkClient(id: (self.clientIdField?.text)!, completion: { (isValid) in
if isValid == true {
//now signed in, update client id
let profileChangeRequest = user?.createProfileChangeRequest()
profileChangeRequest?.displayName = self.clientIdField?.text
profileChangeRequest?.commitChanges(completion: { (error) in
if error == nil {
//done
UserDefaults.standard.set(true, forKey: kIS_USER_CLIENT_NOT_TRAINER)
self.dismiss(animated: true, completion: {
//self.delegate?.didLoginAsClient()
})
}
else {
self.logout()
self.clientIdField?.shake()
self.clientActivityIndicator?.hideActivityIndicator()
}
})
}
else {
self.logout()
self.clientIdField?.shake()
self.clientActivityIndicator?.hideActivityIndicator()
}
})
}
else {
}
}
}
func checkClient(id : String, completion: #escaping (_ isValid : Bool) -> Void) {
let ref = Database.database().reference().child("v2").child("clients").child(id)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() { completion(true) }
else { completion(false) }
}) { (error) in
completion(false)
}
}
func logout() {
do {
try Auth.auth().signOut()
}
catch let error as NSError {
print (error.localizedDescription)
}
}
Login is successful.
Then this runs after login and the user exists but Auth.auth().currentUser() is nil. When a client login happens, I try to get the client data but permission is denied because we have no user.
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
self.currentUser = user
if user == nil {
self.updateForNoUser()
}
else {
self.updateForUser()
}
}
func updateForUser() {
//Trainer Logged in
if UserDefaults.standard.bool(forKey: kIS_USER_CLIENT_NOT_TRAINER) == false {
self.performSegue(withIdentifier: "master", sender: self)
}
//Client Logged in
else {
if let id = Auth.auth().currentUser?.displayName {
let ref = Database.database().reference().child("v2").child("clients").child(id)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
self.client = Client(snapshot: snapshot)
self.performSegue(withIdentifier: "masterClient", sender: self)
}
}) { (error) in }
}
else {
do {
try Auth.auth().signOut()
}
catch let error as NSError {
print (error.localizedDescription)
}
}
}
}

Related

How can I call for a segue in the AppDelegate?

The segue needs to take place only after the user has been authenticated with their Google account. This authentication process takes place in the AppDelegate. I have a segue between two view controllers rather than a button so when the button is tapped, the segue will only be called for once.
The following code takes place within the sign function in the AppDelegate for anyone who is familiar with Firebase:
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("Firebase sign In error")
print(error)
return
} else {
let db = Firestore.firestore()
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
print("Accessing email")
for document in querySnapshot!.documents {
if document.get("uid") as? String != Auth.auth().currentUser?.uid {
db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in
if error != nil {
print("Error: User data not saved")
return
}
}
}
}
}
}
self.window?.rootViewController!.performSegue(withIdentifier: "googleSegue", sender: nil)
print("User is signed in with Firebase")
}
}
}
However the self.window?.rootViewController!.performSegue(withIdentifier: "googleSegue", sender: nil) does nothing when it should be occurring and taking the user to the connected view controller after they sign in. The print statement does occur so there is nothing wrong in that regard.
For reference, this is what happens in the viewController file:
#IBAction func googleSignInTapped(_ sender: Any) {
print("User has chosen to sign with Google.")
GIDSignIn.sharedInstance().signIn()
}

Swift Firebase: how to check user is verified after send email?

After creating account i sending an email with verification link:
func sendVerificationMail() {
if self.authUser != nil && !self.authUser!.isEmailVerified {
self.authUser!.sendEmailVerification(completion: { (error) in
// TODO Notify user email was sent or not because of error
})
} else {
// TODO Notify everything is OK
}
}
And in other place i checking it confirmed:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if Auth.auth().currentUser != nil && Auth.auth().currentUser!.isEmailVerified {
self.performSegue(withIdentifier: "toMainScreen", sender: self)
} else {
self.performSegue(withIdentifier: "notLoggedView", sender: self)
}
}
Even if confirmed, i always go to notLoggedView. Somebody can explain why?
I have faced the similar issue and to solve it I have to reload the profile. Try this.
func loginUser() {
Auth.auth().currentUser?.reload(completion: { (error) in
if let error = error {
print(error)
} else {
if Auth.auth().currentUser != nil && Auth.auth().currentUser!.isEmailVerified {
self.performSegue(withIdentifier: "toMainScreen", sender: self)
} else {
self.performSegue(withIdentifier: "notLoggedView", sender: self).
}
}
})
}
Also, instead of writing Auth.auth().currentUser every time you can store that in a variable. And, you can use this function wherever you want.
if let authUser = Auth.auth().currentUser { //You can also get current user like this in a safe way
//Do your stuff here
}

Firebase Email Verification Swift

I tried to use the Firebase Authentication, but in the login lets me in even when I haven't used the email to confirm the verification.
I have two Viewcontroller , one for Login and the other for signUP.
I can log in and I get the email for verification, but I can also log in in without verification.
public func sendVerificationMail() {
if self.authUser != nil && !self.authUser!.isEmailVerified {
self.authUser!.sendEmailVerification(completion: { (error) in
// Notify the user that the mail has sent or couldn't because of an error.
})
} else {
// Either the user is not available, or the user is already verified.
}
}
#IBAction func signupButtonTapped(_ sender: Any) {
print("Sign up button tapped")
Auth.auth().createUser(withEmail: self.userEmailTextField.text!, password: self.userPasswordTextField.text!) { (user, error) in
if user != nil {
print("User has Signed Up")
self.sendVerificationMail()
}
if error != nil {
print("User cant Sign Up")
}
}
}
#IBAction func signinButtonTapped(_ sender: Any) {
Auth.auth().signIn(withEmail: self.userEmailTextField.text!, password: self.userPasswordTextField.text!) { (user, error) in
if user != nil {
print("User has Signed In")
}
if error != nil {
print("Cant Sign in user")
} else {
self.performSegue(withIdentifier: "toHome", sender: nil)
}
}
}
Firebase Auth doesn't prevent people from signing in when their email isn't verified. If you want to prevent users from advancing when they aren't verified, you will need to code this on the client using the isEmailVerified boolean.
Auth.auth().signIn(withEmail: self.userEmailTextField.text!, password: self.userPasswordTextField.text!) { (authResult, error) in
if let authResult = authResult {
let user = authResult.user
print("User has Signed In")
if user.isEmailVerified {
self.performSegue(withIdentifier: "toHome", sender: nil)
} else {
// do whatever you want to do when user isn't verified
}
}
if let error = error {
print("Cant Sign in user")
}
}
A more comprehensive solution than that provided by #jen-person (and based on together with another SO answer) is this:
final class MySignInView: UIView { // or possibly MySignInViewController: UIViewController
// ... your properties etc...
#IBAction func signInButtonPressed(sender _: AnyObject) {
trySigningIn()
}
}
// MARK: Private
private extension MySignInView {
func trySigningIn() {
guard
let email = userEmailTextField.text,
let password = userPasswordTextField.text
else {
print("Cannot sign in, email or password is 'nil'")
return
}
do {
try signIn(email: email, password: password) { [unowned self] authResult in
self.userDidSignIn(authResult.user)
}
} catch {
// Display error
}
}
func userDidSignIn(_ user: FIRUser) {
// Creds to Jen: https://stackoverflow.com/a/51389154/1311272
guard user.isEmailVerified else {
// TODO display message about non verified email user?
return
}
performSegue(withIdentifier: "toHome", sender: nil)
}
}
// MARK: Error
private extension MySignInView {
enum Error: Strng, Equatable {
case invalidEmail
case userDisabled
case wrongPassword
case userNotFound
case networkError
case unknownError
}
}
// MARK: Firebae specific
private extension MySignInView {
func signIn(
email: String,
password: String,
onSuccessful: (AuthDataResult) -> Void
) throws {
Auth.auth().signIn(
withEmail: email,
password: password
) { (authResult, anyError) in
if let anyError = anyError {
if let error = Error(anyError: anyError) {
throw error
} else {
fatalError("Unsupported error: \(anyError)")
}
}
onSuccessful(authResult)
}
}
}
private extension MySignInView.Error {
init?(anyError: Swift.Error) {
guard let authErrorCode = FIRAuthErrorCode(rawValue: anyError.code) else {
return nil
}
self.init(fireBaseAuthErrorCode: authErrorCode)
}
// Creds goes to to: https://stackoverflow.com/a/39936083/1311272
init(fireBaseAuthErrorCode: FIRAuthErrorCode) {
switch errCode {
case .ErrorCodeInvalidEmail:
self = .invalidEmail
case .ErrorCodeUserDisabled:
self = .userDisabled
case .ErrorCodeWrongPassword:
self = .wrongPassword
case .ErrorCodeUserNotFound:
self = .userNotFound
case .ErrorCodeNetworkError:
self = .networkError
default:
self = .unknownError
}
}
}
You need to inform the user appropriately about the different errors/incorrect states of course, not only print.
This code should probably me moved to a ViewModel, and if your are not using MVVM, I highly recommend it :).
if let email = emailTextfield.text, let password = passwordTextfield.text {
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
if let e = error{
print(e.localizedDescription)
}
else {
//Do whatever you want to do after successful login
}
}
}
You can try this also:
if( !Auth.auth().currentUser!.isEmailVerified) {
// do something
}

The method return the varible without modifying it

i have a problem with my code in Swift
the method verifyInput should return false when there's an error
but it always return true no matter what + when there's an error it print "error" but its just return true
please help
#IBAction func register(_ sender: UIButton) {
let check = verifyInput(email :email.text! ,password: password.text!)
if(check==true){
self.performSegue(withIdentifier: "goToAmazon", sender: nil)
} else if(check==false) {
self.message.text = "Sorry! there's an error"
}
}
func verifyInput(email: String, password: String) -> Bool {
var check = true
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if error != nil {
print("error")
check = false
} else if(error==nil){
check = true
print("registered!")
}
}
return check
}
The problem is that verifyInput is being called synchronously from register but within it is an asynchronous call to Auth.auth().createUser with a completion block.
The check result is being returned before the asynchronous call ever completes. You need to change your method to be asynchronous as well.
Something vaguely like this is what you want:
#IBAction func register(_ sender: UIButton) {
if let email = email.text, let password = password.text {
verifyInput(email: email, password: password) { (check) in
DispatchQueue.main.async {
// only run UI code on the main thread
if(check){
self.performSegue(withIdentifier: "goToAmazon", sender: nil)
} else {
self.message.text = "Sorry! there's an error"
}
}
}
}
}
func verifyInput(email: String, password: String, escaping completion:#escaping (Bool)->Void) {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if error != nil {
print("error")
completion(false)
} else if(error==nil){
print("registered!")
completion(true)
}
}
}

Facebook LoginManager Callback/PrepareforSegue Swift

I'm trying to pass a property to my next VC using prepareforSegue. It is called, however my other VC does not load.
Original
#IBAction func onSignupPressed(sender: AnyObject) {
FBSDKLoginManager().logInWithReadPermissions(permissions,
fromViewController: self) { result, error in
guard error == nil else { print("Login Error"); return }
guard result.grantedPermissions.contains("email") else {
print("No Email Permissions"); return }
self.getFBUserData()
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSegue", sender: self)
}
}
}
I've been able to get the next VC showing by doing calling my doLogin function below. And there I instantiate a VC from the storyboard and present it. I believe it is due to the timing of the Facebook login window that pops up and closes. I searched for a delegate method, but have not found anything
#IBAction func onSignupPressed(sender: AnyObject) {
FBSDKLoginManager().logInWithReadPermissions(permissions,
fromViewController: self) { result, error in
guard error == nil else { print("Login Error"); return }
guard result.grantedPermissions.contains("email") else {
print("No Email Permissions"); return }
self.getFBUserData()
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSegue", sender: self)
}
}
}
func getFBUserData(){
let params = "id, name, first_name, last_name, picture.type(large),friends, email, birthday, work, photos, education, location, hometown, religion, likes, about"
if((FBSDKAccessToken.currentAccessToken()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields":params]).startWithCompletionHandler({ (connection, result, error) -> Void in
if (error == nil){
//everything works print the user data
print(result)
let resultdict = result as? NSDictionary
self.user = Mapper<User>().map(result)
self.doLogin()
}
})
}
}
func doLogin() {
let successVC = self.storyboard?.instantiateViewControllerWithIdentifier("LoginSucessViewController")
self.presentViewController(successVC!, animated: true, completion: nil)
}
Very silly mistake! Didn't realize I could simply set the property of instantiating the VC!
func doLogin() {
let successVC = self.storyboard?.instantiateViewControllerWithIdentifier("LoginSucessViewController") as! LoginSucessViewController
successVC.currentUser = user
self.presentViewController(successVC, animated: true, completion: nil)
}

Resources