I'm currently adding Firebase Authentication to my iOS application. I can sign up and sign in users, however, I'm struggling to find where I can add a segue to move on to the next screen.
func signInUser(email: String, password: String){
// creates user with the firebase autenthication platform
Auth.auth().signIn(withEmail: email, password: password) { [weak self] authResult, error in
guard let strongSelf = self else { return }
}
// would the segue go here?
}
You should execute your code inside the signIn function completion handler:
func signInUser(email: String, password: String){
//creates user with the firebase autenthication platform
Auth.auth().signIn(withEmail: email, password: password) { [weak self] authResult, error in
guard let strongSelf = self else { return }
if let error = error as? NSError {
// Handle sign in error
switch AuthErrorCode(error.code) {
...
}
} else {
// No errors: Perform segue here...
}
}
}
Related
During the sign in flow in iOS, for few users the Authentication module of the amplify-swift library throws the error The operation couldn't be completed. (Amplify.AuthError error 2). I have searched in the amplify-swift repository for possible solutions but couldn't find any. The version of Amplify pod used in code is 1.15.1.
func nativeLogin(
email: String?, password: String?, completion: #escaping (Result<Authstate, Error>) -> Void
) {
let isAlreadySignedIn = AWSMobileClient.default().isSignedIn
if isAlreadySignedIn {
// Do signout and then sign in
let options = AuthSignOutRequest.Options(globalSignOut: true)
Amplify.Auth.signOut(options: options) {
result in
DispatchQueue.main.async {
if case .success = result {
// completion(.success(.logout))
guard let email = email,
let password = password
else {
self.response(
completion, .failure(AuthError.passwordValidationError("Missing Email/Password")))
return
}
// log event
self.signIn(email: email, password: password, completion: completion)
} else if case .failure(let error) = result {
// log event
completion(.failure(error))
}
}
}
} else {
self.signIn(email: email, password: password, completion: completion)
}
}
The reason that the signOut method is invoked before actually signing in is to ensure that last logged in user is signed out first.
Can anyone explain where am I going wrong? Or has this error been already fixed in the latest version (2.2.1) of the amplify-swift library?
So I need help creating an async-await for a firebase sign-in button. I need the full-screen cover I have to disappear when the message that Firebase has authenticated the user has gone through. Basically, I have a Full-Screen Cover that is awaiting a response from the cloud saying that the user has been authenticated and the full-screen cover can now close. (Basically a login button on the sign up view)
The problem is that when you click login once the full-screen cover doesn't close because the information from the cloud does not go through fast enough which means you have to click the login button twice so that the full-screen cover successfully closes.
Button:
Button(action: {
guard !email.isEmpty, !password.isEmpty else {
return
}
fireViewModel.signIn(email: email, password: password)
if fireViewModel.signedIn { presentationMode.wrappedValue.dismiss() }
}, label: {
Text("Login")
})
Code for Firebase Sign in:
func signIn(email: String, password: String) {
auth.signIn(withEmail: email, password: password) { result, error in
guard result != nil, error == nil else {
print(error ?? "")
return
}
DispatchQueue.main.async {
// Success
self.signedIn = true
}
}
}
Can anyone help me do that? I am new to swift and have no experience with async-await... Thank you!
you could try this, using a completion handler, such as:
func signIn(email: String, password: String, completion: #escaping (Bool) -> Void) {
auth.signIn(withEmail: email, password: password) { result, error in
guard result != nil, error == nil else {
print(error ?? "")
completion(false) // <-- here
return
}
DispatchQueue.main.async {
// Success
self.signedIn = true
completion(true) // <-- here
}
}
}
and
Button(action: {
guard !email.isEmpty, !password.isEmpty else {
return
}
fireViewModel.signIn(email: email, password: password) { isSignedIn in
if isSignedIn {
presentationMode.wrappedValue.dismiss()
} else {
// do something else
}
}
}, label: {
Text("Login")
})
Adjust the logic to your needs.
I have some simple App, which have auth.
nearest code check if you already entered:
func signingManager(){
Auth.auth().addStateDidChangeListener { [weak self] (auth, user) in
guard let self = self else {return}
if user != nil {
self.showNextVC()
print("You are already entered")
}
}
}
It's works when you first open the app and if you entered func "showNextVC" will open next VC.
In the same time i have login button with code :
#IBAction func logInTapped(_ sender: UIButton) {
guard let email = emailTextField.text, let password = passwordTextField.text, email != "", password != "" else {
displayWarningLabel(withText: "info is incorrect")
return
}
Auth.auth().signIn(withEmail: email, password: password, completion: { [weak self] (user, error) in
if error != nil {
self?.displayWarningLabel(withText: "error occured")
return
}
if user != nil {
self?.showNextVC()
print("Congratulations, you have successfully logged in!")
}
self?.displayWarningLabel(withText: "no such user")
}
)}
Now about the problem: if I click the "login" button, the "signingManager ()" method and it's "showNextVC" are triggered first, and only then the "logInTapped" method itself and again "showNextVC".
As a result, I have 2 VCs and two messages:
"You are already entered" and
"Congratulations, you have successfully logged in!"
What am I doing wrong? Thanks!
Since you're listening for for auth state changes, you don't need to handle the self?.showNextVC() in the completion callback for signIn(withEmail:, password:). That code should only be present in the callback for addStateDidChangeListener.
Alternatively, you can:
Use the addStateDidChangeListener to initially detect whether the user is signed-in already.
Inside the callback for the state change:
Remove the listener by calling removeAuthStateDidChangeListener
Start the explicit sign-in flow, and call signIn(withEmail:, password:) like you're doing now.
I want user to login once and not have to reenter their login info everytime they open app unless they logout in the last session.
Login screen is currently displayed everytime the app is open. This is my rootview
struct AppRootView: View {
var body: some View {
AnyView {
// check if user has already logged in here and then route them accordingly
if auth.token != nil {
homeMainView()
} else {
LoginController()
}
}
}
}
currently this is what I use to login users
#objc func signUp() {
setLoading(true);
app.usernamePasswordProviderClient().registerEmail(username!, password: password!, completion: {[weak self](error) in
// Completion handlers are not necessarily called on the UI thread.
// This call to DispatchQueue.main.sync ensures that any changes to the UI,
// namely disabling the loading indicator and navigating to the next page,
// are handled on the UI thread:
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
print("Signup failed: \(error!)")
self!.errorLabel.text = "Signup failed: \(error!.localizedDescription)"
return
}
print("Signup successful!")
// Registering just registers. Now we need to sign in, but we can reuse the existing username and password.
self!.errorLabel.text = "Signup successful! Signing in..."
self!.signIn()
}
})
}
#objc func signIn() {
print("Log in as user: \(username!)");
setLoading(true);
app.login(withCredential: AppCredentials(username: username!, password: password!)) { [weak self](maybeUser, error) in
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
// Auth error: user already exists? Try logging in as that user.
print("Login failed: \(error!)");
self!.errorLabel.text = "Login failed: \(error!.localizedDescription)"
return
}
guard let user = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
//
let hostingController = UIHostingController(rootView: ContentView())
self?.navigationController?.pushViewController(hostingController, animated: true)
}
how could I implement one time login so that users do have to login each time they open the app?
A correctly configured and initialized RealmApp class will persist the session information for you between app restarts, you can check for an existing session using the .currentUser() method from this class. So in your case something like:
if app.currentUser() != nil {
homeMainView()
} else {
LoginController()
}
While using Realm to persist login is a good idea, but I would highly
advice against using it for managing user authentication credentials such
as passwords. A better approach if you want to save sensitive information is
using KeyChain just like what Apple and password manager apps do. With a light
weight keyChain wrapper library such as SwiftKeychainWrapper You can easily
save your login credentials in the most secure way.
Here is a sample using a keyChain wrapper linked above.
With simple modification you can use this helper class to manage your sign in credentials anywhere in your app.
import SwiftKeychainWrapper
class KeyChainService {
// Make a singleton
static let shared = KeyChainService()
// Strings which will be used to map data in keychain
private let passwordKey = "passwordKey"
private let emailKey = "emailKey"
private let signInTokenKey = "signInTokenKey"
// Saving sign in info to keyChain
func saveUserSignInInformation(
email: String,
password: String,
token: String
onError: #escaping() -> Void,
onSuccess: #escaping() -> Void
) {
DispatchQueue.global(qos: .default).async {
let passwordIsSaved: Bool = KeychainWrapper.standard.set(password, forKey: self.passwordKey)
let emailIsSaved: Bool = KeychainWrapper.standard.set(email, forKey: self.emailKey)
let tokenIsSaved: Bool = KeychainWrapper.standard.set(token, forKey: self.signInTokenKey)
DispatchQueue.main.async {
// Verify that everything is saved as expected.
if passwordIsSaved && emailIsSaved && tokenIsSaved {
onSuccess()
}else {
onError()
}
}
}
}
// Retrieve signIn information for auto login
func retrieveSignInInfo(onError: #escaping() -> Void, onSuccess: #escaping(UserModel) -> Void) {
DispatchQueue.main.async {
let retrievedPassword: String? = KeychainWrapper.standard.string(forKey: self.passwordKey)
let retrievedEmail: String? = KeychainWrapper.standard.string(forKey: self.emailKey)
let retrievedToken: String? = KeychainWrapper.standard.string(forKey: self.signInTokenKey)
if let password = retrievedPassword,
let email = retrievedEmail,
let token = retrievedToken {
// Assuming that you have a custom user model named "UserModel"
let user = UserModel(email: email, password: password,token: token)
// Here is your user info which you can use to verify with server if needed and auto login user.
onSuccess(user)
}else {
onError()
}
}
}
}
I am new to Firebase and have been trying to implement a "Sign Up" page for my app. Currently, I am only using the email feature, just to test things out. I am able to create the new user and store it in the "Login & Auth" tab in the dashboard, but for what ever reason I cannot retrieve the UID. The only way is if I close out of my app and reload it, then it can access the UID. Any suggestions on why this is?
Swift 4.1 FireBase User Create/Login Steps:-
STEP 1:- Register new firebase authentication Pod in to your Podfile :
pod 'Firebase/Auth'
STEP 2:- Open terminal and install pod with your related project directory path:
pod install
STEP 3:- import firebase authentication library in your UIViewController where ever you want:
import FirebaseAuth
STEP 4:- On your Registration UIButton Action write this snippet :
Auth.auth().createUser(withEmail: (txtEmail.text ?? ""), password: (txtPass.text ?? "")) { (result, error) in
if let _eror = error {
//something bad happning
print(_eror.localizedDescription )
}else{
//user registered successfully
print(result)
}
}
STEP 5:- If you want to Sign In after Registration use this snippet on your Sign in UIButton:
Auth.auth().signIn(withEmail: (txtEmail.text ?? ""), password: (txtPass.text ?? "")) { (result, error) in
if let _eror = error{
print(_eror.localizedDescription)
}else{
if let _res = result{
print(_res)
}
}
}
Happy Codding ;)!!!!
Example of how to create users:
Auth.auth().createUser(withEmail: email, password: password, completion: { (result, error) -> Void in
if (error == nil) {
UserDefaults.standardUserDefaults().setValue(result.uid, forKey: "uid")
print("Account created :)")
let userDict = ["Name": name!, "Major": major!, "Email": email!]
let uid = result!.uid
self.dismissViewControllerAnimated(true, completion: nil)
}
else{
print(error)
}
})
Hope this helps.
Creating user in Firebase is very easy, just add firebase to your project using cocoa pods and then create a signup screen. You need email and password. Now first import Firebase
import Firebase
Then inside on viewDidLoad() method configure firebase
FIRApp.configure()
Now get email and password from textfields and use the following code for registration.
FIRAuth.auth()?.createUserWithEmail(email!, password: password!, completion: { (user: FIRUser?, error) in
if error == nil {
//registration successful
}else{
//registration failure
}
})
Thats it.
Source: Firebase Swift Tutorial
Before you try any code, make sure your password is at least 6 characters long. Print the error if is possible, I would print the error on each function. sign up and sign in just to keep track of what you are doing.
#IBAction func signUp(_ sender: Any) {
Auth.auth().createUser(withEmail: self.usernameTextField.text!, password: self.passwordTextField.text!) {(user, error) in
if user != nil {
print("User Has SignUp")
}
if error != nil {
print(":(",error)
}
}
Auth.auth().signIn(withEmail: self.usernameTextField.text!, password: self.passwordTextField.text!) {(user, error) in
if user != nil {
print("User Has Sign In")
}
if error != nil {
print(":(",error)
}
}