FirebaseUI for iOS — Auth

FirebaseUI is an open-source library for iOS that provides simple, customizable UI bindings on top of Firebase SDKs to eliminate boilerplate code and promote best practices.

FirebaseUI provides a drop-in auth solution that handles the UI flows for signing in users with email addresses and passwords, and federated identity providers such as Google Sign-In and Facebook Login. It is built on top of Firebase Auth.

The FirebaseUI Auth component implement best practices for authentication on mobile devices and websites, which can maximize sign-in and sign-up conversion for your app. It also handles edge cases like account recovery and account linking that can be security sensitive and error-prone to handle correctly.

FirebaseUI can be easily customized to fit in with the rest of your app's visual style, and it is open source, so you aren't constrained in realizing the user experience you want.

Compatible FirebaseUI clients are also available for Android and Web.

Installation

Importing FirebaseUI components for auth

Add the following to your Podfile:

pod 'FirebaseUI/Auth'

pod 'FirebaseUI/Google'
pod 'FirebaseUI/Facebook'
pod 'FirebaseUI/Twitter'
pod 'FirebaseUI/Phone'

Configuring sign-in providers

To use FirebaseUI to authenticate users you first need to configure each provider you want to use in their own developer app settings. Please read the Before you begin section of the Firebase Auth guides at the following links:

Using FirebaseUI for Authentication

Configuration

All operations, callbacks, UI customizations are done through an FUIAuth instance. The FUIAuth instance associated with the default Firebase Auth instance can be accessed as follows:

// Swift
import FirebaseUI

/* ... */

FirebaseApp.configure()
let authUI = FUIAuth.defaultAuthUI()
// You need to adopt a FUIAuthDelegate protocol to receive callback
authUI?.delegate = self
// Objective-C
@import FirebaseUI;
...
[FIRApp configure];
FUIAuth *authUI = [FUIAuth defaultAuthUI];
// You need to adopt a FUIAuthDelegate protocol to receive callback
authUI.delegate = self;

This instance can then be configured with the providers you wish to support:

// Swift
import FirebaseUI

let providers: [FUIAuthProvider] = [
  FUIEmailAuth(),
  FUIGoogleAuth(),
  FUIFacebookAuth(),
  FUITwitterAuth(),
  FUIPhoneAuth(authUI: FUIAuth.defaultAuthUI()),
]
self.authUI?.providers = providers
// Objective-C
@import FirebaseUI;

// ...

NSArray<id<FUIAuthProvider>> *providers = @[
  [[FUIEmailAuth alloc] init],
  [[FUIGoogleAuth alloc] init],
  [[FUIFacebookAuth alloc] init],
  [[FUITwitterAuth alloc] init],
  [[FUIPhoneAuth alloc] initWithAuthUI:[FUIAuth defaultAuthUI]]
];
_authUI.providers = providers;

For Google Sign-in support, add custom URL schemes to your Xcode project (step 1 of the implement Google Sign-In documentation).

For Facebook Login support, follow step 3 and 4 of Facebook login documentation, and add custom URL schemes in step 4 of Facebook SDK for iOS-Getting started documentation.

Finally, add a call to handle the URL that your application receives at the end of the Google/Facebook authentication process.

// Swift
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
  let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String?
  if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication) ?? false {
    return true
  }
  // other URL handling goes here.
  return false
}
// Objective-C
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
  NSString *sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey];
  return [[FUIAuth defaultAuthUI] handleOpenURL:url sourceApplication:sourceApplication];
}

Sign In

To start the authentication flow, obtain an authViewController instance from FUIAuth. In order to leverage FirebaseUI for iOS you must display the authViewController; you can present it as the first view controller of your app or present it from another view controller within your app. In order to present the authViewController obtain as instance as follows:

// Swift

// Present the auth view controller and then implement the sign in callback.
let authViewController = authUI!.authViewController()

func authUI(_ authUI: FUIAuth, didSignInWithAuthDataResult authDataResult: AuthDataResult?, error: Error?) {
  // handle user (`authDataResult.user`) and error as necessary
}
// Objective-C
UINavigationController *authViewController = [authUI authViewController];
// Use authViewController as your root view controller,
// or present it on top of an existing view controller.

- (void)authUI:(FUIAuth *)authUI
    didSignInWithAuthDataResult:(nullable FIRAuthDataResult *)authDataResult
         error:(nullable NSError *)error {
  // Implement this method to handle signed in user (`authDataResult.user`) or error if any.
}

Customizing FirebaseUI for authentication

Custom terms of Service (ToS) URL

The Terms of Service URL for your application, which is displayed on the email/password account creation screen, can be specified as follows:

// Swift
let kFirebaseTermsOfService = URL(string: "https://firebase.google.com/terms/")!
authUI?.tosurl = kFirebaseTermsOfService
// Objective-C
authUI.TOSURL = [NSURL URLWithString:@"https://example.com/tos"];

Custom strings

You can override the default messages and prompts shown to your users. This can be useful for things such as adding support for languages other than English.

In order to do so:

// Swift
authUI?.customStringsBundle = NSBundle.mainBundle() // Or any custom bundle.
// Objective-C
authUI.customStringsBundle = [NSBundle mainBundle]; // Or any custom bundle.

The bundle should include .strings files that have the same names as the default files, namely FirebaseAuthUI, FirebaseGoogleAuthUI, and FirebaseFacebookAuthUI. Each string in these files should have the same key as its counterpart in the default .strings files.

Custom sign-in screen

You can customize everything about the authentication method picker screen, except for the actual sign-in buttons and their position.

In order to do so, create a subclass of FUIAuthPickerViewController and customize it to your needs. Provide FUIAuth with an instance of your subclass by implementing the delegate method authPickerViewControllerForAuthUI: as follows:

// Swift
func authPickerViewController(for authUI: FUIAuth) -> FUIAuthPickerViewController {
  return CustomAuthPickerViewController(authUI: authUI)
}
// Objective-C
- (FUIAuthPickerViewController *)authPickerViewControllerForAuthUI:(FUIAuth *)authUI {
  return [[CustomAuthPickerViewController alloc] initWithAuthUI:authUI];
}

Custom email/password screens

You can customize all email/password screens, including but not limited to:

  • Hiding the top UINavigationBar
  • Adding a Cancel button
  • Use a UI view other than UITableView

Things that are not customizable:

  • UIAlertController popups (showing error labels instead)
  • Modifying the screen flow (combining screens or skipping particular screens)
  • Disabling validation, including email validation

To customize the email/password screens, create a subclass of appropriate controller and implement it to your needs. Then set up FUIAuth with an instance of your subclass by implementing the following delegate methods:

// Swift
func emailEntryViewController(for authUI: FUIAuth) -> FUIEmailEntryViewController {
  return CustomEmailEntryViewController(authUI: authUI)
}

func passwordSignInViewController(for authUI: FUIAuth, email: String) -> FUIPasswordSignInViewController {
  return CustomPasswordSignInViewController(authUI: authUI, email: email)
}

func passwordSignUpViewController(for authUI: FUIAuth, email: String) -> FUIPasswordSignUpViewController {
  return CustomPasswordSignUpViewController(authUI: authUI, email: email)
}

func passwordRecoveryViewController(for authUI: FUIAuth, email: String) -> FUIPasswordRecoveryViewController {
  return CustomPasswordRecoveryViewController(authUI: authUI, email: email)
}

func passwordVerificationViewController(for authUI: FUIAuth, email: String, newCredential: AuthCredential) -> FUIPasswordVerificationViewController {
  return CustomPasswordVerificationViewController(authUI: authUI, email: email, newCredential: newCredential)
}
// Objective-C
- (FUIEmailEntryViewController *)emailEntryViewControllerForAuthUI:(FUIAuth *)authUI {
  return [[CustomEmailEntryViewController alloc] initWithAuthUI:authUI];

}

- (FUIPasswordSignInViewController *)passwordSignInViewControllerForAuthUI:(FUIAuth *)authUI
                                                                     email:(NSString *)email {
  return [[CustomPasswordSignInViewController alloc] initWithAuthUI:authUI
                                                              email:email];

}

- (FUIPasswordSignUpViewController *)passwordSignUpViewControllerForAuthUI:(FUIAuth *)authUI
                                                                     email:(NSString *)email {
  return [[CustomPasswordSignUpViewController alloc] initWithAuthUI:authUI
                                                              email:email];

}

- (FUIPasswordRecoveryViewController *)passwordRecoveryViewControllerForAuthUI:(FUIAuth *)authUI
                                                                         email:(NSString *)email {
  return [[CustomPasswordRecoveryViewController alloc] initWithAuthUI:authUI
                                                                email:email];

}

- (FUIPasswordVerificationViewController *)passwordVerificationViewControllerForAuthUI:(FUIAuth *)authUI
                                                                             email:(NSString *)email
                                                                     newCredential:(FIRAuthCredential *)newCredential {
  return [[CustomPasswordVerificationViewController alloc] initWithAuthUI:authUI
                                                                    email:email
                                                            newCredential:newCredential];
}

In your custom view controllers, call the same FirebaseUI methods as their parent classes. For example:

  • - (void)onNext:(NSString *)textFieldValue; // Or any action that leads to the next screen
  • - (void)didChangeTextField:(NSString *)textFieldValue; // Usually called in viewWillAppear and after modification of text entry field
  • - (void)onBack;
  • - (void)cancelAuthorization;

Refer to the Objective-C and Swift samples for examples of how you can customize these views.

Handling auto-upgrade of anonymous users

Enabling auto-upgrade of anonymous users increases the complexity of your auth flow by adding several more edge cases that need to be handled. As opposed to normal auth, which only involves one step, auto-upgrade presents three steps with four possibilities total:

  • At app launch, anonymously authenticate the user. User state can be accumulated on the anonymous user and linked to the non-anonymous account later.
  • At some point in your app, present the auth flow and authenticate the user using a non-anonymous auth method.
  • Following a successful auth attempt, if the user signs in to a new account, the anonymous account and the new account can be linked together without issue.
  • Otherwise, if logging into an existing user, FirebaseUI will return a merge conflict error containing the resulting FIRAuthDataResult corresponding to the existing account. This value should be used to login to the existing account without linking to the anonymous user, as the two accounts may have conflicting state (the anonymous account state will be discarded).
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
  if let error = error as NSError?,
      error.code == FUIAuthErrorCode.mergeConflict.rawValue {
    // Merge conflict error, discard the anonymous user and login as the existing
    // non-anonymous user.
    guard let credential = error.userInfo[FUIAuthCredentialKey] as? AuthCredential else {
      print("Received merge conflict error without auth credential!")
      return
    }

    Auth.auth().signInAndRetrieveData(with: credential) { (dataResult, error) in
      if let error = error as NSError? {
        print("Failed to re-login: \(error)")
        return
      }

      // Handle successful login
    }
  } else if let error = error {
    // Some non-merge conflict error happened.
    print("Failed to log in: \(error)")
    return
  }

  // Handle successful login
}