Building the UI
On This Page
Building the UI
There are several ways that you can build UI flows in iOS applications. You can use Model-View-ViewModel (MVVM), Viper, or massive view controllers. How you manage transitions between view controllers is up to you, however we recommend the use of the flow coordinator pattern. Your flow coordinator instance handles all of the status updates and manages UI navigation. We've also implemented this pattern in our sample application.
For example, your application could have the following view controllers:
LoginViewController- responsible for reading the username and password information from a user and initiating the authentication flow viaOktaAuthSdkPasswordChangeViewController- responsible for the change password or skip actionsMFARequiredViewController- responsible for showing the user their enrolled active factorsMFAChallengeViewController- responsible for factor verification and retry actions
The flow coordinator decides which view controller to show next based on the current status that is received from the server. For example, if the current status is OktaAuthStatusPasswordChange, then the flow coordinator shows PasswordChangeViewController and injects the current status as a dependency.
Primary authentication flow
With the primary authentication flow (no MFA, no password management, and so on), you typically just need LoginViewController. LoginViewController is your root view controller that expects the username and password to be input by the user. Let's assume that your view controller has the following UI outlets: userNameTextField, passwordTextField, and signInButton. Then, the handler for the tap on signInButton could be:
@IBOutlet private var userNameTextField: UITextField!
@IBOutlet private var passwordTextField: UITextField!
@IBOutlet private var signInButton: UIButton!
@IBAction private func signInTapped() {
guard let username = userNameTextField.text, !username.isEmpty,
let password = passwordTextField.text, !password.isEmpty else { return }
OktaAuthSdk.authenticate(with: URL(string: {yourOktaDomain})!,
username: username,
password: password,
onStatusChange:
{ [weak self] status in
self?.flowCoordinatorDelegate?.onStatusChanged(status: status)
})
{ [weak self] error in
self?.showError(message: error.description)
}
}
If the user is authenticated, then the SDK returns the OktaAuthStatusSuccess status in the onStatusChange closure parameter.
The following is an example of an AuthFlowCoordinator implementation. Let's assume that your LoginViewController has the flowCoordinatorDelegate property. The property is declared with the AuthFlowCoordinatorProtocol type and required for delegating status handling responsilbity to the AuthFlowCoordinator object.
AuthFlowCoordinatorProtocol
protocol AuthFlowCoordinatorProtocol: class {
func onStatusChanged(status: OktaAuthStatus)
func onCancel()
func onReturn(prevStatus: OktaAuthStatus)
}
AuthFlowCoordinator
class AuthFlowCoordinator {
public let rootViewController: UINavigationController
var currentStatus: OktaAuthStatus?
public class func instantiate() -> AuthFlowCoordinator {
let mainStoryboard = UIStoryboard(name: "Login", bundle: nil)
let loginViewController = mainStoryboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
let navigationViewController = UINavigationController(rootViewController: loginViewController)
let flowCoordinator = AuthFlowCoordinator(with: navigationViewController)
loginViewController.flowCoordinatorDelegate = flowCoordinator
return flowCoordinator
}
public init(with rootViewController: UINavigationController) {
self.rootViewController = rootViewController
}
func handleStatus(status: OktaAuthStatus) {
currentStatus = status
switch status.statusType {
case .success:
handleSuccessStatus(status: status as! OktaAuthStatusSuccess)
case .passwordExpired,
.passwordWarning,
.MFARequired,
.MFAChallenge,
.MFAEnroll,
.MFAEnrollActivate,
.recoveryChallenge,
.recovery,
.passwordReset,
.lockedOut,
.unauthenticated:
print("Status is not supported")
default:
print("Received unknown status")
}
}
func handleSuccessStatus(status: OktaAuthStatusSuccess) {
let alert = UIAlertController(title: "Success", message: "We are signed in - \(status.sessionToken!)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
rootViewController.present(alert, animated: true, completion: nil)
}
}
extension AuthFlowCoordinator: AuthFlowCoordinatorProtocol {
func onStatusChanged(status: OktaAuthStatus) {
self.handleStatus(status: status)
}
func onCancel() {
rootViewController.popToRootViewController(animated: true)
}
func onReturn(prevStatus: OktaAuthStatus) {
rootViewController.popViewController(animated: true)
}
}