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
- User enters their email address
- SDK sends an OTP to their email
- User enters the OTP code
- 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
- User enters their phone number and country code
- SDK sends an OTP via SMS
- User enters the OTP code
- 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:
- Social Authentication - Add social login options like Apple and Google
- Wallet Creation - Create embedded wallets for authenticated users
- 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.