Skipping provider screen in FirebaseUI - ios

I am trying to find a way to skip provider options screen in FirebaseUI.
I just need phone authentication and there is no need to show user provider options.
Is there a way to take user directly to phone authentication screen?
Here is my code on viewcontroller
override func viewDidLoad() {
super.viewDidLoad()
//createGradientLayer()
checkLoggedIn()
}
func checkLoggedIn() {
Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
// User is signed in.
} else {
// No user is signed in.
self.login()
}
}
}
func login() {
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self as? FUIAuthDelegate
let providers: [FUIAuthProvider] = [
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!),
]
authUI?.providers = providers
FUIAuth.defaultAuthUI()?.isSignInWithEmailHidden = true
let authViewController = authUI?.authViewController()
self.present(authViewController!, animated: true, completion: nil)
}
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
if error != nil {
//Problem signing in
login()
}else {
//User is in! Here is where we code after signing in
}
}

You were almost there. After FUIAuthProvider initialization start Phone Auth flow directly:
FUIPhoneAuth *provider = self.authUI.providers.firstObject;
[provider signInWithPresentingViewController:self];
Here is sample code.
In order to add logo to Welcome screen subclass FUIAuthPickerViewController and implement FUIAuthDelegate delegate method:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController
Here is one more sample for this.

Let's supose that you have a view controller with a button to start the phone validation. This is the code that should be included in the button (obj-c)
- (IBAction)btnPhoneValidation:(id)sender {
FUIAuth *authUI = [FUIAuth defaultAuthUI];
authUI.delegate = self;
//The following array may contain diferente options for validate the user (with Facebook, with google, e-mail...), in this case we only need the phone method
NSArray<id<FUIAuthProvider>> * providers = #[[[FUIPhoneAuth alloc]initWithAuthUI:[FUIAuth defaultAuthUI]]];
authUI.providers = providers;
//You can present the screen asking for the user number with the following method.
FUIPhoneAuth *provider = authUI.providers.firstObject;
[provider signInWithPresentingViewController:self phoneNumber:nil];
//This is the default way to present several options.
// UINavigationController *authViewController = [authUI authViewController];
// [self presentViewController:authViewController animated:YES completion:nil];
}
The same process but with e-mail authentication, replacing the provider type:
NSArray<id<FUIAuthProvider>> * providers = #[[[FUIEmailAuth alloc]init]];
authUI.providers = providers;
FUIEmailAuth *provider = authUI.providers.firstObject;
[provider signInWithPresentingViewController:self email:nil];

According to the FirebaseAuthUI documentation, you cannot customize the flow. (See the section on custom email/password screens)

Related

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

Navigate to a ViewController from AppDelegate after Google Firebase login is finished

I have integrated Firebase, Google Sign In in my ios app's AuthViewController (Set as initial ViewController).
Once user has signed in using Google sign in button, He is redirected back to the
same AuthViewController (i.e. showing the Google Sign In button though he is signed In).
Question:
Is there any way i can configure redirect to a particular ViewController after User has signed in ? If not i need to know how can i do it programmatically in AppDelegate's using below function
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
if let error = error {
// ...
return
}
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,
accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential) { (user, error) in
if let error = error {
// ...
return
}
// User is signed in
// ...
}
}
I already have a segue defined in my AuthViewController to segue to the MainViewController, But looks like Firebase authentication is a async call and userAuthenticated() function returns false by the time we call in viewDidAppear function
override func viewDidAppear(_ animated: Bool) {
print("--------View Did Appear \(userAuthenticated())")
if userAuthenticated() {
self.performSegue(withIdentifier: "showMain", sender: nil)
}
}
func userAuthenticated() -> Bool {
return (Auth.auth().currentUser != nil) ? true : false
}
EDIT:
With Yarn's approach, Though it solves the problem .. but it first shows AuthViewController's view and then segues to MainViewController ... Its a bad UI experience to show .. I want user to directly goto MainViewController once signed in.
To programmatically segue from AppDelegate you could call
if let rootVC = window?.rootViewController {
rootVC.performSegue(withIdentifier: "segue", sender: self)
}

How to check if user has valid Auth Session Firebase iOS?

I wanna check if the user has still a valid session, before I present the Home View controller of my app. I use the latest Firebase API. I think if I use the legacy, I'll be able to know this.
Here's what I did so far:
I posted my question on Slack community of Firebase, no one is answering. I found this one, but this is for Android: https://groups.google.com/forum/?hl=el#!topic/firebase-talk/4HdhDvVRqHc
I tried reading the docs of Firebase for iOS, but I can't seem to comprehend it: https://firebase.google.com/docs/reference/ios/firebaseauth/interface_f_i_r_auth
I tried typing in Xcode like this:
FIRApp().currentUser()
FIRUser().getCurrentUser()
But I can't seem to find that getCurrentUser function.
if FIRAuth.auth().currentUser != nil {
presentHome()
} else {
//User Not logged in
}
For updated SDK
if Auth.auth().currentUser != nil {
}
Updated answer
Solution for latest Firebase SDK - DOCS
// save a ref to the handler
private var authListener: AuthStateDidChangeListenerHandle?
// Check for auth status some where
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
authListener = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// User is signed in
// let the user in?
if user.isEmailVerified {
// Optional - check if the user verified their email too
// let the user in?
}
} else {
// No user
}
}
}
// Remove the listener once it's no longer needed
deinit {
if let listener = authListener {
Auth.auth().removeStateDidChangeListener(authListener)
}
}
Original solution
Solution in Swift 3
override func viewDidLoad() {
super.viewDidLoad()
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
if user != nil {
self.switchStoryboard()
}
}
}
Where switchStoryboard() is
func switchStoryboard() {
let storyboard = UIStoryboard(name: "NameOfStoryboard", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerName") as UIViewController
self.present(controller, animated: true, completion: nil)
}
Source
Solution in Swift 4
override func viewDidLoad() {
super.viewDidLoad()
setupLoadingControllerUI()
checkIfUserIsSignedIn()
}
private func checkIfUserIsSignedIn() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if user != nil {
// user is signed in
// go to feature controller
} else {
// user is not signed in
// go to login controller
}
}
}
if Auth.auth().currentUser?.uid != nil {
//user is logged in
}else{
//user is not logged in
}
While you can see if there is such a user using Auth.auth().currentUser, this will only be telling you if there was a user authenticated, regardless of whether that users account still exists or is valid.
Complete Solution
The real solution to this should be using Firebase's re-authentication:
open func reauthenticate(with credential: AuthCredential, completion: UserProfileChangeCallback? = nil)
This assures (upon the launch of the application) that the previously signed in / authenticated user still in fact is and can be authenticated through Firebase.
let user = Auth.auth().currentUser // Get the previously stored current user
var credential: AuthCredential
user?.reauthenticate(with: credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
override func viewDidLoad() {
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
// 2
if user != nil {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
}
}
}
Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
An objective-c solution would be (iOS 11.4):
[FIRAuth.auth addAuthStateDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) {
if (user != nil) {
// your logic
}
}];
All the provided answers only check on currentUser. But you could check the auth session by simple user reload like below:
// Run on the background thread since this is just a Firestore user reload, But you could also directly run on the main thread.
DispatchQueue.global(qos: .background).async {
Auth.auth().currentUser?.reload(completion: { error in
if error != nil {
DispatchQueue.main.async {
// Authentication Error
// Do the required work on the main thread if necessary
}
} else {
log.info("User authentication successfull!")
}
})
}

How to handle touchID when loading app from background Swift

I'm implementing the login possibility with touchID using Swift.
Following: when the App is started, there is a login screen and a touchID popup - that's working fine. The problem occurs, when the app is loaded from background: I want the touchID popup appear over a login screen if a specific timespan hasn't been exceeded yet - but this time I want the touchID to go to the last shown view before the app entered background. (i.e. if the user wants to cancel the touchID, there is a login screen underneath where he then can authenticate via password, which leads him to the last shown view OR if the touchID authentication succeeded, the login screen should be dismissed and the last shown view presented.)
I really tried everything on my own, and searched for answers - nothing did help me. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
//notify when foreground or background have been entered -> in that case there are two methods that will be invoked: willEnterForeground and didEnterBackground
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "willEnterForeground", name:UIApplicationWillEnterForegroundNotification, object: nil)
notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
password.secureTextEntry = true
if (username != nil) {
username.text = "bucketFit"
}
username.delegate = self
password.delegate = self
if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown == nil){
authenticateWithTouchID()
}
}
}
willEnterForeground:
func willEnterForeground() {
//save locally that the guide already logged in once and the application is just entering foreground
//the variable alreadyShown is used for presenting the touchID, see viewDidAppear method
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
showLoginView()
} else {
def.removeObjectForKey("alreadyShown")
showLoginView()
}
}
}
authenticateWithTouchID():
func authenticateWithTouchID() {
let context : LAContext = LAContext()
context.localizedFallbackTitle = ""
var error : NSError?
let myLocalizedReasonString : NSString = "Authentication is required"
//check whether the iphone has the touchID possibility at all
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
//if yes then execute the touchID and see whether the finger print matches
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString as String, reply: { (success : Bool, evaluationError : NSError?) -> Void in
//touchID succeded -> go to students list page
if success {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.performSegueWithIdentifier("studentsList", sender: self)
})
} else {
// Authentification failed
print(evaluationError?.description)
//print out the specific error
switch evaluationError!.code {
case LAError.SystemCancel.rawValue:
print("Authentication cancelled by the system")
case LAError.UserCancel.rawValue:
print("Authentication cancelled by the user")
default:
print("Authentication failed")
}
}
})
}
}
shouldPerformSegueWithIdentifier:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if (false) { //TODO -> username.text!.isEmpty || password.text!.isEmpty
notify("Login failed", message: "Please enter your username and password to proceed")
return false
} else if (false) { //TODO when backend ready! -> !login("bucketFit", password: "test")
notify("Incorrect username or password", message: "Please try again")
return false
//if the login page is loaded after background, dont proceed (then we need to present the last presented view on the stack before the app leaved to background)
} else if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown != nil){
//TODO check whether login data is correct
dismissLoginView()
return false
}
}
return true
}
Thank you in advance.
What you could do is create a AuthenticationManager. This manager would be a shared instance which keep track of whether authentication needs to be renewed. You may also want this to contain all of the auth methods.
class AuthenticationManager {
static let sharedInstance = AuthenticationManager()
var needsAuthentication = false
}
In AppDelegate:
func willEnterForeground() {
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
AuthenticationManager.sharedInstance.needsAuthentication = true
}
}
}
Then, subclass UIViewController with a view controller named SecureViewController. Override viewDidLoad() in this subclass
override fun viewDidLoad() {
super.viewDidLoad()
if (AuthenticationManager.sharedInstance().needsAuthentication) {
// call authentication methods
}
}
Now, make all your View Controllers that require authentication subclasses of SecureViewController.

Swift Google+ login GPPSignIn.sharedInstance().userID equals always nil

I'm trying to implement Google+ login in iOS app using Swift language.
My code looks like this:
var kClientId = "My Client ID from Dev Console"
var signIn = GPPSignIn.sharedInstance()
signIn.shouldFetchGooglePlusUser = true
signIn.shouldFetchGoogleUserEmail = true
signIn.shouldFetchGoogleUserID = true
signIn.clientID = kClientId
signIn.scopes = [kGTLAuthScopePlusLogin]
signIn.delegate = self
signIn.authenticate()
For test purposes I created a lable and wanted to change this to user email who logged in.
if (GPPSignIn.sharedInstance().userID != nil) {
var user = GPPSignIn.sharedInstance().googlePlusUser
userName.text = user.name.JSONString()
if (user.emails != nil){
userEmailLable.text = user.emails.first?.JSONString() ?? "no email"
} else {
userEmailLable.text = "no email"
}
} else {
println("User ID is nil")
}
After I click "Login" button Safari tab opens and I can enter my Google email and password and it asks permissions for certain things and after pressing Accept button it returns back to the application. My userEmailLable is not changed and it prints "User ID is nil" as an output. It happens all the time and there were not a single successful login.
My Google frameworks are all fine, URLs are also correct, in Google Developer console are everything as it should be.
AppDelegate.swift file includes this function also
func application(application: UIApplication, openURL url: NSURL, sourcApplication: String, annotation: AnyObject?) -> Bool {
return GPPURLHandler.handleURL(url, sourceApplication: sourcApplication, annotation: annotation)
}
Anybody knows why is it doing so? Thanks!
This is strange. I tried out building an example following the Google instructions. It works as expected and prints out the right output.
Also, here is another nice step-by-step writeup.
I did the same steps as specified by the guide - create a console app, register URL scheme for the iOS app using my bundle ID.
Here’s my view controller code.
import UIKit
class ViewController: UIViewController, GPPSignInDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let signIn = GPPSignIn.sharedInstance()
signIn.shouldFetchGooglePlusUser = true
signIn.shouldFetchGoogleUserEmail = true // Uncomment to get the user's email
signIn.shouldFetchGoogleUserID = true
signIn.clientID = "… my client ID"
// Uncomment one of these two statements for the scope you chose in the previous step
signIn.scopes = [ kGTLAuthScopePlusLogin ] // "https://www.googleapis.com/auth/plus.login" scope
signIn.delegate = self
signIn.authenticate()
}
// MARK: - GPPSignInDelegate
func finishedWithAuth(auth: GTMOAuth2Authentication!, error: NSError!) {
print("received auth \(auth), error \(error)")
if (GPPSignIn.sharedInstance().userID != nil) {
let user = GPPSignIn.sharedInstance().googlePlusUser
println("user name: " + user.name.JSONString() + "\nemail: ")
if (user.emails != nil){
print(user.emails.first?.JSONString() ?? "no email")
} else {
print("no email")
}
} else {
println("User ID is nil")
}
}
}
In addition to this, I have the app delegate function also (but note that some of the optional specifiers are different)
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool {
return GPPURLHandler.handleURL(url, sourceApplication:sourceApplication, annotation: annotation)
}

Resources