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, passkeys, social media accounts, or external JWT tokens.
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)
- Valid environment ID configured
- Authentication methods enabled in your Dynamic dashboard
Built-in Authentication UI
The easiest way to add authentication is using the built-in UI:
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
// Show the authentication UI
sdk.ui.showAuth()
This displays a complete authentication flow with all enabled providers.
Email OTP Authentication
Email OTP (One-Time Password) authentication allows users to sign in using their email address.
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
Send OTP to Email
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
func sendEmailOTP(email: String) async {
do {
try await sdk.auth.email.sendOTP(email: email)
print("OTP sent to \(email)")
// Show OTP input UI
} catch {
print("Failed to send OTP: \(error)")
}
}
Verify Email OTP
func verifyEmailOTP(code: String) async {
do {
try await sdk.auth.email.verifyOTP(token: code)
print("Email verified successfully!")
// User is now authenticated
} catch {
print("Invalid OTP: \(error)")
}
}
Resend Email OTP
func resendEmailOTP() async {
do {
try await sdk.auth.email.resendOTP()
print("OTP resent")
} catch {
print("Failed to resend OTP: \(error)")
}
}
SMS OTP Authentication
SMS OTP authentication allows users to sign in using their phone number.
Send OTP via SMS
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
func sendSmsOTP(phoneNumber: String) async {
do {
let phoneData = PhoneData(
dialCode: "+1", // Country dial code
iso2: "US", // ISO country code
phone: phoneNumber // Phone number without country code
)
try await sdk.auth.sms.sendOTP(phoneData: phoneData)
print("SMS OTP sent")
// Show OTP input UI
} catch {
print("Failed to send SMS OTP: \(error)")
}
}
Verify SMS OTP
func verifySmsOTP(code: String) async {
do {
try await sdk.auth.sms.verifyOTP(token: code)
print("Phone verified successfully!")
// User is now authenticated
} catch {
print("Invalid OTP: \(error)")
}
}
Resend SMS OTP
func resendSmsOTP() async {
do {
try await sdk.auth.sms.resendOTP()
print("SMS OTP resent")
} catch {
print("Failed to resend OTP: \(error)")
}
}
Passkey Authentication
Passkeys provide a passwordless authentication experience using biometrics or device credentials.
Sign In with Passkey
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
func signInWithPasskey() async {
do {
_ = try await sdk.auth.passkey.signIn()
print("Signed in with passkey!")
// User is now authenticated
} catch {
print("Passkey sign-in failed: \(error)")
}
}
Register a New Passkey
After authentication, users can register additional passkeys:
func registerPasskey() async {
do {
try await sdk.passkeys.registerPasskey()
print("Passkey registered successfully!")
} catch {
print("Failed to register passkey: \(error)")
}
}
External JWT Authentication
For apps with existing authentication systems, you can authenticate users with an external JWT:
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
func signInWithExternalJwt(jwt: String) async {
do {
try await sdk.auth.externalAuth.signInWithExternalJwt(
props: SignInWithExternalJwtParams(jwt: jwt)
)
print("Signed in with external JWT!")
} catch {
print("External JWT sign-in failed: \(error)")
}
}
Authentication State Management
Check Authentication Status
import DynamicSDKSwift
let sdk = DynamicSDK.instance()
// Check current user
if let user = sdk.auth.authenticatedUser {
print("User is authenticated: \(user.userId)")
print("Email: \(user.email ?? "Not provided")")
} else {
print("User is not authenticated")
}
// Get current auth token
if let token = sdk.auth.token {
print("Auth token: \(token)")
}
Listen for Authentication Changes
Use Combine publishers for reactive state management:
import DynamicSDKSwift
import Combine
class AuthViewModel: ObservableObject {
@Published var user: UserProfile?
@Published var token: String?
private let sdk = DynamicSDK.instance()
private var cancellables = Set<AnyCancellable>()
func startListening() {
// Initial values
user = sdk.auth.authenticatedUser
token = sdk.auth.token
// Listen for user changes
sdk.auth.authenticatedUserChanges
.receive(on: DispatchQueue.main)
.sink { [weak self] newUser in
self?.user = newUser
if newUser == nil {
// User logged out - navigate to login
}
}
.store(in: &cancellables)
// Listen for token changes
sdk.auth.tokenChanges
.receive(on: DispatchQueue.main)
.sink { [weak self] newToken in
self?.token = newToken
}
.store(in: &cancellables)
}
}
Logout
func logout() async {
do {
try await sdk.auth.logout()
print("Logged out successfully")
// Navigation to login is triggered by authenticatedUserChanges
} catch {
print("Logout failed: \(error)")
}
}
Complete Authentication Flow Example
Here’s a complete example using SwiftUI:
import SwiftUI
import DynamicSDKSwift
import Combine
struct LoginView: View {
@StateObject private var viewModel = LoginViewModel()
var body: some View {
VStack(spacing: 20) {
// Email OTP Section
TextField("Email", text: $viewModel.email)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
.keyboardType(.emailAddress)
Button("Send Email OTP") {
viewModel.sendEmailOTP()
}
.disabled(viewModel.email.isEmpty || viewModel.isLoading)
// OTP Input (shown after sending)
if viewModel.showOtpInput {
TextField("Enter OTP", text: $viewModel.otpCode)
.textFieldStyle(.roundedBorder)
.keyboardType(.numberPad)
Button("Verify OTP") {
viewModel.verifyEmailOTP()
}
.disabled(viewModel.otpCode.isEmpty || viewModel.isLoading)
}
Divider()
// Social Login Buttons
Button("Continue with Google") {
viewModel.signInWithGoogle()
}
Button("Continue with Apple") {
viewModel.signInWithApple()
}
Button("Sign in with Passkey") {
viewModel.signInWithPasskey()
}
// Built-in Auth UI
Divider()
Button("Open Auth Flow") {
viewModel.openAuthFlow()
}
// Error Message
if let error = viewModel.errorMessage {
Text(error)
.foregroundColor(.red)
.font(.caption)
}
}
.padding()
}
}
@MainActor
class LoginViewModel: ObservableObject {
@Published var email = ""
@Published var otpCode = ""
@Published var showOtpInput = false
@Published var isLoading = false
@Published var errorMessage: String?
private let sdk = DynamicSDK.instance()
func openAuthFlow() {
sdk.ui.showAuth()
}
func sendEmailOTP() {
guard !email.isEmpty else { return }
isLoading = true
errorMessage = nil
Task {
do {
try await sdk.auth.email.sendOTP(email: email)
showOtpInput = true
} catch {
errorMessage = "Failed to send OTP: \(error.localizedDescription)"
}
isLoading = false
}
}
func verifyEmailOTP() {
guard !otpCode.isEmpty else { return }
isLoading = true
errorMessage = nil
Task {
do {
try await sdk.auth.email.verifyOTP(token: otpCode)
// User is now authenticated - navigation handled by auth state listener
} catch {
errorMessage = "Invalid OTP: \(error.localizedDescription)"
}
isLoading = false
}
}
func signInWithGoogle() {
errorMessage = nil
Task {
do {
try await sdk.auth.social.connect(provider: .google)
} catch {
errorMessage = "Google sign-in failed: \(error.localizedDescription)"
}
}
}
func signInWithApple() {
errorMessage = nil
Task {
do {
try await sdk.auth.social.connect(provider: .apple)
} catch {
errorMessage = "Apple sign-in failed: \(error.localizedDescription)"
}
}
}
func signInWithPasskey() {
errorMessage = nil
Task {
do {
_ = try await sdk.auth.passkey.signIn()
} catch {
errorMessage = "Passkey sign-in failed: \(error.localizedDescription)"
}
}
}
}
Best Practices
Error Handling
Always handle errors gracefully and provide meaningful feedback to users:
do {
try await sdk.auth.email.verifyOTP(token: code)
} catch {
// Provide user-friendly error messages
let userMessage = getErrorMessage(for: error)
showAlert(message: userMessage)
}
func getErrorMessage(for error: Error) -> String {
// Customize based on error type
return "Authentication failed. Please try again."
}
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 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 more social login options
- Wallet Operations - Work with user wallets
- Networks - Configure blockchain networks
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.