Overview

The Dynamic Swift SDK provides multiple authentication methods that are secure, user-friendly, and easy to implement. Users can sign in using their email address, phone number, or social media accounts, and the SDK handles all the complexity of OTP generation, verification, and session management.
For testing purposes, we recommend starting with Email OTP authentication as it doesn’t require real phone numbers and is easier to set up.

Prerequisites

Before you begin, make sure you have:
  • Dynamic SDK initialized (see Installation Guide)
  • Dynamic Client instance created
  • Valid environment ID configured
  • Authentication methods enabled in your Dynamic dashboard

Email OTP Authentication

Email OTP (One-Time Password) authentication allows users to sign in using their email address. This is one of the most popular authentication methods as it’s familiar to users and doesn’t require additional setup.

How It Works

  1. User enters their email address
  2. SDK sends an OTP to their email
  3. User enters the OTP code
  4. SDK verifies the code and authenticates the user

Step 1: Send OTP to Email

First, you’ll need to send an OTP to the user’s email address:
import DynamicSwiftSDK

class AuthenticationViewController: UIViewController {
    var dynamicClient: DynamicClient!
    var currentOtpVerification: OTPVerification?

    @IBAction func sendEmailOtpTapped(_ sender: UIButton) {
        guard let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please enter a valid email address")
            return
        }

        Task {
            do {
                let otpVerification = try await sendEmailOtp(
                    client: dynamicClient,
                    email: email
                )

                // Store the verification for the next step
                currentOtpVerification = otpVerification

                // Update UI to show OTP input
                DispatchQueue.main.async {
                    self.showOtpInputView()
                }

            } catch {
                DispatchQueue.main.async {
                    self.showAlert(message: "Failed to send OTP: \(error.localizedDescription)")
                }
            }
        }
    }
}

Step 2: Verify Email OTP

Once the user receives the OTP and enters it, you can verify it:
@IBAction func verifyOtpTapped(_ sender: UIButton) {
    guard let otpCode = otpTextField.text, !otpCode.isEmpty else {
        showAlert(message: "Please enter the OTP code")
        return
    }

    guard let otpVerification = currentOtpVerification else {
        showAlert(message: "No OTP verification in progress")
        return
    }

    Task {
        do {
            let authenticatedUser = try await verifyOtp(
                otpVerification: otpVerification,
                verificationToken: otpCode
            )

            DispatchQueue.main.async {
                self.showSuccessMessage("Welcome \(authenticatedUser.email ?? "")")
                self.navigateToMainApp()
            }

        } catch {
            DispatchQueue.main.async {
                self.showAlert(message: "Invalid OTP: \(error.localizedDescription)")
            }
        }
    }
}

SMS OTP Authentication

SMS OTP authentication allows users to sign in using their phone number. This method is great for users who prefer mobile-based authentication.

How It Works

  1. User enters their phone number and country code
  2. SDK sends an OTP via SMS
  3. User enters the OTP code
  4. SDK verifies the code and authenticates the user

Step 1: Send OTP via SMS

@IBAction func sendSmsOtpTapped(_ sender: UIButton) {
    guard let phoneNumber = phoneTextField.text, !phoneNumber.isEmpty else {
        showAlert(message: "Please enter a valid phone number")
        return
    }

    let phoneCountryCode = "+1" // Get from UI
    let isoCountryCode = "US"   // Get from UI

    Task {
        do {
            let otpVerification = try await sendSmsOtp(
                client: dynamicClient,
                phoneNumber: phoneNumber,
                phoneCountryCode: phoneCountryCode,
                isoCountryCode: isoCountryCode
            )

            currentOtpVerification = otpVerification

            DispatchQueue.main.async {
                self.showOtpInputView()
            }

        } catch {
            DispatchQueue.main.async {
                self.showAlert(message: "Failed to send SMS OTP: \(error.localizedDescription)")
            }
        }
    }
}

Step 2: Verify SMS OTP

The verification process is the same as email OTP, but uses the verifySmsOtp function:
@IBAction func verifySmsOtpTapped(_ sender: UIButton) {
    guard let otpCode = otpTextField.text, !otpCode.isEmpty else {
        showAlert(message: "Please enter the OTP code")
        return
    }

    guard let otpVerification = currentOtpVerification else {
        showAlert(message: "No OTP verification in progress")
        return
    }

    Task {
        do {
            let authenticatedUser = try await verifySmsOtp(
                otpVerification: otpVerification,
                verificationToken: otpCode
            )

            DispatchQueue.main.async {
                self.showSuccessMessage("Welcome user with phone: \(authenticatedUser.phoneNumber ?? "")")
                self.navigateToMainApp()
            }

        } catch {
            DispatchQueue.main.async {
                self.showAlert(message: "Invalid OTP: \(error.localizedDescription)")
            }
        }
    }
}

Authentication State Management

Once a user is authenticated, you’ll want to manage their authentication state throughout your app.

Check Authentication Status

func checkAuthenticationStatus() {
    if let currentUser = dynamicClient.user {
        print("User is authenticated: \(currentUser.id)")
        print("Email: \(currentUser.email ?? "Not provided")")
        print("Phone: \(currentUser.phoneNumber ?? "Not provided")")
        print("Token: \(currentUser.token ?? "No token")")

        // User is logged in, show main app
        showMainApp()
    } else {
        print("User is not authenticated")
        // User is not logged in, show login screen
        showLoginScreen()
    }
}

Logout

When users want to sign out, you can clear their authentication state:
@IBAction func logoutTapped(_ sender: UIButton) {
    Task {
        do {
            try await logout(client: dynamicClient)

            DispatchQueue.main.async {
                self.showSuccessMessage("Logged out successfully")
                self.showLoginScreen()
            }

        } catch {
            DispatchQueue.main.async {
                self.showAlert(message: "Logout failed: \(error.localizedDescription)")
            }
        }
    }
}

Complete Authentication Flow

Here’s a complete example of how to implement email OTP authentication in a view controller:
import DynamicSwiftSDK
import UIKit

class LoginViewController: UIViewController {
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var otpTextField: UITextField!
    @IBOutlet weak var sendOtpButton: UIButton!
    @IBOutlet weak var verifyOtpButton: UIButton!
    @IBOutlet weak var otpStackView: UIStackView!

    var dynamicClient: DynamicClient!
    var currentOtpVerification: OTPVerification?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize Dynamic client
        let config = DynamicClientConfig(
            environmentId: ProcessInfo.processInfo.environment["DYNAMIC_ENVIRONMENT_ID"] ?? ""
        )
        dynamicClient = createDynamicClient(config: config)

        // Hide OTP input initially
        otpStackView.isHidden = true
    }

    @IBAction func sendOtpTapped(_ sender: UIButton) {
        guard let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please enter a valid email address")
            return
        }

        sendOtpButton.isEnabled = false
        sendOtpButton.setTitle("Sending...", for: .normal)

        Task {
            do {
                let otpVerification = try await sendEmailOtp(
                    client: dynamicClient,
                    email: email
                )

                currentOtpVerification = otpVerification

                DispatchQueue.main.async {
                    self.otpStackView.isHidden = false
                    self.sendOtpButton.setTitle("Resend OTP", for: .normal)
                    self.sendOtpButton.isEnabled = true
                    self.showSuccessMessage("OTP sent to \(email)")
                }

            } catch {
                DispatchQueue.main.async {
                    self.sendOtpButton.isEnabled = true
                    self.sendOtpButton.setTitle("Send OTP", for: .normal)
                    self.showAlert(message: "Failed to send OTP: \(error.localizedDescription)")
                }
            }
        }
    }

    @IBAction func verifyOtpTapped(_ sender: UIButton) {
        guard let otpCode = otpTextField.text, !otpCode.isEmpty else {
            showAlert(message: "Please enter the OTP code")
            return
        }

        guard let otpVerification = currentOtpVerification else {
            showAlert(message: "No OTP verification in progress")
            return
        }

        verifyOtpButton.isEnabled = false
        verifyOtpButton.setTitle("Verifying...", for: .normal)

        Task {
            do {
                let authenticatedUser = try await verifyOtp(
                    otpVerification: otpVerification,
                    verificationToken: otpCode
                )

                DispatchQueue.main.async {
                    self.showSuccessMessage("Welcome \(authenticatedUser.email ?? "")")
                    self.navigateToMainApp()
                }

            } catch {
                DispatchQueue.main.async {
                    self.verifyOtpButton.isEnabled = true
                    self.verifyOtpButton.setTitle("Verify OTP", for: .normal)
                    self.showAlert(message: "Invalid OTP: \(error.localizedDescription)")
                }
            }
        }
    }

    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }

    private func showSuccessMessage(_ message: String) {
        let alert = UIAlertController(title: "Success", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }

    private func navigateToMainApp() {
        // Navigate to your main app interface
        // This could be a tab bar controller, navigation controller, etc.
    }
}

Best Practices

Error Handling

Always handle errors gracefully and provide meaningful feedback to users:
do {
    let result = try await someAuthenticationFunction()
} catch {
    // Provide user-friendly error messages
    let userMessage = getErrorMessage(for: error)
    showAlert(message: userMessage)
}

func getErrorMessage(for error: Error) -> String {
    if let nsError = error as NSError? {
        switch nsError.code {
        case 1004:
            return "Invalid OTP code. Please try again."
        case 1005:
            return "Network error. Please check your connection."
        default:
            return "Authentication failed. Please try again."
        }
    }
    return "An unexpected error occurred."
}

User Experience

  • Show loading states during authentication
  • Provide clear error messages
  • Allow users to resend OTP codes
  • Implement proper validation for email and phone numbers

Security

  • Never store OTP codes in UserDefaults or other persistent storage
  • Clear sensitive data when users log out
  • Use secure network connections (HTTPS)
  • Implement proper session management

What’s Next

Now that you have authentication working, you can:
  1. Social Authentication - Add social login options like Apple and Google
  2. Wallet Creation - Create embedded wallets for authenticated users
  3. Networks - Configure blockchain networks
For a complete reference of all authentication functions and types, check out the Swift SDK Reference in the SDK/API References section.
The authentication flow is designed to be secure and user-friendly. The SDK handles all the complex security details, including OTP generation, verification, and session management, so you can focus on building great user experiences.