This codelab introduces Multi-Factor Authentication (MFA) concepts using the RDNA iOS SDK framework. You'll learn how the SDK's challenge-response system works, understand different authentication flows, and master the terminology needed for implementing secure authentication in native iOS applications.
The RDNA iOS SDK implements Multi-Factor Authentication through a challenge-response mechanism. This system ensures secure, step-by-step verification of user identity through multiple factors.
The MFA system works on a fundamental challenge-response pattern:
After successful SDK initialization, the authentication process begins immediately:
SDK Initialization Complete → getUser Challenge → MFA Flow Begins
The MFA system uses several categories of challenges:
Category | Purpose | Examples |
Identity Verification | Establish user identity |
|
Multi-Factor Verification | Verify user through multiple channels |
|
Device/Password Authentication | Authenticate using device capabilities/password verification |
|
Device Management | Handle new device scenarios |
|
Session Management | Complete authentication flows |
|
The MFA system is completely event-driven using iOS patterns:
iOS Callback/Event Handling Options:
The RDNA iOS SDK supports three distinct prelogin authentication flows, each designed for specific user scenarios and security requirements.
Purpose: First-time user registration and device setup
When Used:
Key Characteristics:
Flow Outcome: User gets registered successfully with device enrollment.
Purpose: Authenticate returning users on previously registered devices
When Used:
Key Characteristics:
Flow Outcome: Quick authentication leveraging device trust and biometrics.
Purpose: Authenticate users on iOS devices they haven't registered before
When Used:
Key Characteristics:
Flow Outcome: User authenticated with new device added to registered devices.
Each MFA challenge follows a consistent event → API response pattern in iOS. Understanding these mappings is crucial for implementing authentication flows with the RDNA iOS SDK.
Every challenge in the MFA system follows this pattern:
SDK Challenge Callback/Event → iOS UI Collection (UIViewController/Views) → API Response → Flow Continuation
Challenge Name |
|
Callback/Event Name |
|
API Name |
|
Purpose | Collect and verify user identifier |
When Triggered | Always first challenge after initialization |
User Input | Username, email, or user identifier |
iOS Implementation Pattern:
// Callback/Event handling (Delegate)
func sdkDidRequestUser() {
// Show UIViewController with username input
showUsernameViewController()
}
// Callback/Event handling (Closure)
rdnaService.onGetUser = { [weak self] in
self?.showUsernameViewController()
}
// API response
func handleUsernameSubmit(_ username: String) {
rdnaClient.setUser(username) { result in
DispatchQueue.main.async {
switch result {
case .success:
// Wait for next challenge
break
case .failure(let error):
self.handleError(error)
}
}
}
}
Flow Pattern:
SDK → getUser event → Show UITextField/UIViewController → User enters username → setUser API → Next challenge
Challenge Name |
|
Callback/Event Name |
|
API Name |
|
Purpose | Verify user through out-of-band authentication |
When Triggered | After successful user identification |
User Input | OTP code, activation code, or verification code |
iOS Implementation Pattern:
// Callback/Event handling
func sdkDidRequestActivationCode() {
showOTPViewController()
}
// API response
func handleOTPSubmit(_ code: String) {
rdnaClient.setActivationCode(code) { result in
DispatchQueue.main.async {
switch result {
case .success:
// Wait for next challenge
break
case .failure(let error):
self.handleError(error)
}
}
}
}
Flow Pattern:
SDK → getActivationCode event → Show OTP input UI → User enters code → setActivationCode API → Next challenge
Challenge Name |
|
Callback/Event Name |
|
API Name |
|
Purpose | Traditional password-based authentication |
When Triggered | When LDA is NOT available on device |
User Input | User password |
iOS Implementation Pattern:
// Callback/Event handling
func sdkDidRequestPassword() {
showPasswordViewController()
}
// API response
func handlePasswordSubmit(_ password: String) {
rdnaClient.setPassword(password) { result in
DispatchQueue.main.async {
switch result {
case .success:
// Authentication complete
break
case .failure(let error):
self.handleError(error)
}
}
}
}
Flow Pattern:
SDK → getPassword event → Show UITextField (secure entry) → User enters password → setPassword API → Authentication complete
Challenge Name |
|
Callback/Event Name |
|
API Name |
|
Purpose | Biometric or device-based authentication (Face ID, Touch ID) |
When Triggered | When LDA IS available on device |
User Input | Consent for biometric authentication |
iOS Implementation Pattern:
// Callback/Event handling
func sdkDidRequestLDAConsent() {
showBiometricConsentDialog()
}
// API response
func handleBiometricConsent(_ granted: Bool) {
rdnaClient.setUserConsentForLDA(granted) { result in
DispatchQueue.main.async {
switch result {
case .success:
// SDK will trigger native biometric prompt
break
case .failure(let error):
self.handleError(error)
}
}
}
}
Flow Pattern:
SDK → getUserConsentForLDA event → Show UIAlertController consent → User grants → setUserConsentForLDA API → iOS biometric prompt (Face ID/Touch ID)
iOS Biometric Integration:
LAContext for Face ID/Touch IDChallenge Name |
|
Callback/Event Name |
|
API Name |
|
Purpose | Verify legitimacy of new device |
When Triggered | During new device login flow |
User Input | Selection of verification method |
iOS Implementation Pattern:
// Callback/Event handling
func sdkDidRequestDeviceVerification(options: [VerificationOption]) {
showDeviceVerificationViewController(options: options)
}
// API response
func handleVerificationMethodSelected(_ method: VerificationOption) {
rdnaClient.performVerifyAuth(method: method) { result in
DispatchQueue.main.async {
switch result {
case .success:
// Verification process continues
break
case .failure(let error):
self.handleError(error)
}
}
}
}
Flow Pattern:
SDK → addNewDeviceOptions event → Show UITableViewController with options → User selects method → performVerifyAuth API → Verification process
Challenge Name | N/A (Completion Callback/Event) |
Callback/Event Name |
|
API Name | NA |
Purpose | Signal successful authentication completion and user logged in |
When Triggered | After all challenges successfully completed |
User Input | None (automatic) |
iOS Implementation Pattern:
// Callback/Event handling (Delegate)
func sdkDidCompleteLogin(userInfo: UserInfo) {
DispatchQueue.main.async {
self.navigateToMainViewController(userInfo: userInfo)
}
}
// Callback/Event handling (Closure)
rdnaService.onUserLoggedIn = { [weak self] userInfo in
DispatchQueue.main.async {
self?.navigateToMainViewController(userInfo: userInfo)
}
}
Flow Pattern:
Final challenge completed → onUserLoggedIn event → Navigate to main app screen (UINavigationController push/modal present)
Understanding key MFA terminology is essential for implementing and troubleshooting authentication flows in iOS applications.
A security step that the SDK requires the user to complete. Each challenge represents a specific verification requirement.
Example: The checkuser challenge for which SDK triggers getUser event requires the user to provide their username.
A callback sent by the SDK to your iOS application when a challenge needs to be addressed. Callback/Events are delivered via delegates, closures, or NotificationCenter.
Example: The getActivationCode event indicates the SDK needs an OTP from the user.
iOS Callback/Event Delivery:
weak self in closures to prevent retain cyclesThe SDK method your iOS application calls to provide the required information for a challenge.
Example: Calling rdnaClient.setUser("john.doe") { result in ... } responds to the getUser challenge.
iOS API Patterns:
A complete sequence of challenges from start to successful authentication.
Example: Activation Flow might include: getUser → getActivationCode → getUserConsentForLDA → onUserLoggedIn
Device-based authentication using iOS biometrics (Face ID, Touch ID) or device passcode.
iOS-Specific Details:
LocalAuthentication frameworkNSFaceIDUsageDescription in Info.plistBiometric Availability:
// SDK checks automatically, but you can verify:
import LocalAuthentication
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
// Face ID or Touch ID available
}
Security method requiring users to provide two or more verification factors.
iOS Implementation Benefits:
The initial registration process where a user establishes their identity and enrolls their first iOS device.
iOS Specifics:
The process of adding an iOS device to a user's list of trusted devices.
iOS Device Identifiers:
The security relationship between a user account and a registered iOS device, enabling streamlined future authentication.
The authenticated period between successful login and logout, managed through device tokens stored in iOS Keychain.
iOS Session Management:
Detailed numeric error code providing specific information about what went wrong.
Generic error category for broad error classification.
Human-readable error message describing the issue and potential solutions.
Critical Timing Rules:
Correct Flow:
// ✅ Correct: Callback/Event-driven with proper threading
func sdkDidRequestUser() {
DispatchQueue.main.async {
self.showUsernameInput()
}
}
func handleUsernameSubmit(_ username: String) {
rdnaClient.setUser(username) { result in
DispatchQueue.main.async {
// Wait for next event from SDK
self.handleResult(result)
}
}
}
Incorrect Flow:
// ❌ Incorrect: API called before event
rdnaClient.setUser("username") { ... } // No event received yet
// ❌ Incorrect: Duplicate calls
rdnaClient.setUser("username") { ... }
rdnaClient.setUser("username") { ... } // Called again
// ❌ Incorrect: UI update off main thread
func sdkDidRequestUser() {
self.showUsernameInput() // May be on background thread
}
iOS Best Practices:
// Use weak self to prevent retain cycles
rdnaService.onGetUser = { [weak self] in
guard let self = self else { return }
DispatchQueue.main.async {
self.showUsernameViewController()
}
}
// Handle errors gracefully
rdnaClient.setUser(username) { result in
DispatchQueue.main.async { [weak self] in
switch result {
case .success:
// Wait for next challenge
break
case .failure(let error):
self?.showError(error)
}
}
}
Congratulations! You've mastered the fundamentals of Multi-Factor Authentication (MFA) with the RDNA iOS SDK:
✅ Challenge-Response Architecture: Understanding how SDK events map to API responses in iOS ✅ Flow Types: Activation, Same Device Login, and New Device Login characteristics ✅ Callback/Event Integration: iOS-specific patterns (Delegates, Closures, NotificationCenter) ✅ API Patterns: Proper response patterns with completion handlers and error handling ✅ MFA Terminology: Essential security and iOS-specific technical concepts ✅ Best Practices: Threading, memory management, and implementation guidelines for iOS
You now understand:
weak self to prevent retain cyclesYou now understand:
With this foundation, you're ready for: