Search code examples
iosswiftmongodbswiftuirealm

RealmSwift: Implementing one time login with MongoDB Realm in swift


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?


Solution

  • 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()
                    }
                
            }
        }
    
    
    }