Installation
Prove provides the iOS SDK in Swift. It has a download size of 2.5 MB and an install size of 1.5 MB for the minimum required components. It relies on iOS native APIs. The iOS SDK supports the earlier three major versions. Prove has seen successful transactions with iOS v11.
Xcode RequirementTo integrate with our iOS SDKs, build apps with Xcode 16.0 or later.
Prove manages a repository with the libraries to enable integration. You can install the SDK using either CocoaPods or Swift Package Manager.
CocoaPods
Swift Package Manager
Execute the following to import CocoaPod from the Prove pod repository:# Run this command to install the cocoapods-art plugin (authored by Artifactory)
gem install cocoapods-art
# Run this command to add the Prove pod repository
pod repo-art add prove.jfrog.io https://prove.jfrog.io/artifactory/api/pods/libs-public-cocoapods
# In your Podfile, paste in the Prove pod repository as a source
plugin 'cocoapods-art', :sources => [
'prove.jfrog.io'
]
# In your Podfile, paste in the SDK pods
pod 'ProveAuth', '6.10.4'
# Run this command to install the SDK pods
pod install
Upgrading the SDK (CocoaPods)When you upgrade, set a single ProveAuth version in your Podfile (for example, pod ‘ProveAuth’, ‘6.10.4’). You do not need to add or pin separate versions for ProveBase, ProveDeviceAuth, or ProveMobileAuth. CocoaPods resolves those as dependencies of Prove Auth, which keeps the components aligned and helps avoid version conflicts.
Step 1: Connect to JFrog registry
Set up the registry globally, required for both Xcode UI and Package.swift:swift package-registry set --global "https://prove.jfrog.io/artifactory/api/swift/libs-public-swift"
swift package-registry login "https://prove.jfrog.io/artifactory/api/swift/libs-public-swift"
# Press Enter when prompted for access token
Public Registry
This is a publicly accessible registry, so you don’t need a password or access token. Press Enter when prompted for an access token.
Configure the registry connection via command line only. Once this setup is complete, you can add packages using either Xcode UI or Package.swift.Step 2: Adding dependencies
Method 1: Xcode
- In Xcode, go to File → Add Package Dependencies
- Search for the package you want,
swift.proveauth
- Select the version and add to your target
The latest stable version is 6.10.4. Select “Exact Version” for production applications to ensure consistent builds.Upgrading the SDK (Swift Package Manager)When you upgrade, change only the swift.proveauth package version. Dependent libraries resolve with that package; you do not coordinate multiple Prove package versions manually.
Once the command line setup is complete, enter the package name, swift.proveauth, in Xcode.Method 2: package.swift
Add dependencies to your package.swift file:// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "YourApp",
platforms: [.iOS(.v12)],
dependencies: [
.package(id: "swift.proveauth", from: "6.10.4"),
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "ProveAuth", package: "swift.proveauth"),
]
)
]
)
Then run the following command to resolve the dependencies:
Send the type of flow: mobile
Unlike the Web SDK, when using the iOS SDK, use the mobile flow. Pass mobile to the Unify() function on the server. In a mobile flow, the mobile phone performs one-time password (OTP) validation.
In the mobile flow, once OTP validation is complete, the AuthFinishStep function executes.
Authenticate()
The SDK requires an authToken as a parameter for the Authenticate() function. This token returns from the Unify() call of the server-side SDK. The token is session specific so it’s used for a single flow. It also expires after 15 minutes.
Retrieve authToken
To start the flow, send a request to your back-end server with the possession type. Include a phone number if you are using Prove’s possession check.
// The below example uses native iOS URLSession, but any other
// alternative networking approaches should also work
func initialize(phoneNumber: String, possessionType: String, completion: @escaping (Result<String, Error>) -> Void) {
guard let url = URL(string: "\(backendUrl)/initialize") else {
completion(.failure(URLError(.badURL)))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// Set up the request body
let body: [String: Any] = [
"phoneNumber": phoneNumber,
"possessionType": possessionType
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
} catch {
completion(.failure(error))
return
}
// Perform the request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// Handle network or connection errors
if let error = error {
completion(.failure(error))
return
}
// Check HTTP response status code
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode != 200 {
let statusError = NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP error with status code: \(httpResponse.statusCode)"])
completion(.failure(statusError))
return
}
guard let data = data else {
let noDataError = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"])
completion(.failure(noDataError))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let authToken = json["authToken"] as? String {
completion(.success(authToken))
} else {
let parsingError = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to parse JSON or authToken is missing"])
completion(.failure(parsingError))
}
} catch {
completion(.failure(error))
}
}
// Start the network call
task.resume()
}
Setup authenticator
Once you have the authToken, build the authenticator for the mobile flow.
// Object implementing ProveAuthFinishStep protocols
let finishStep = FinishAuthStep()
// Objects implementing OtpStartStep/OtpFinishStep protocols
let otpStartStep = MobileOtpStartStep()
let otpFinishStep = MobileOtpFinishStep()
let proveAuthSdk: ProveAuth
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep)
.build()
// Object implementing ProveAuthFinishStep protocols
let finishStep = FinishAuthStep()
let proveAuthSdk: ProveAuth
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.build()
If a mobile data connection is unavailable during testing, use the Builder class. It permits simulated successful session results while connected to a Wi-Fi network. Testing using a Wi-Fi connection is useful in the Sandbox environment.
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.withMobileAuthTestMode() // Test mode flag
.build()
The Prove Auth object is thread safe and used as a singleton. Most Prove Auth methods are blocking and therefore can’t execute in the main app thread. The app employs an executor service with a minimum of two threads to manage threads due to the ability to process concurrent blocking requests.
// authToken retrieved from your server via StartAuthRequest
proveAuthSdk.authenticate(authToken) { error in
DispatchQueue.main.async {
self.messages.finalResultMessage = "ProveAuth.authenticate returned error: \(error.localizedDescription)"
print(self.messages.finalResultMessage)
}
}
Validate the mobile phone
In the AuthFinishStep, specify a function to call once the possession checks complete on the mobile phone. In the following code, notice an endpoint called /verify. This endpoint on your back end server calls the UnifyStatus() function to validate the phone number.
// Send a verify request.
// The below example uses native iOS URLSession, but any other
// alternative networking approaches should also work
func unifyVerify(authId: String, completion: @escaping (Error?) -> Void) {
guard let url = URL(string: "\(backendUrl)/verify") else {
completion(URLError(.badURL))
return
}
// Create the request
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// Set up the request body (empty JSON object) - authId is not needed so you can ignore.
let body: [String: Any] = ["authId": authId]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
} catch {
completion(error)
return
}
// Perform the request
let task = URLSession.shared.dataTask(with: request) { _, response, error in
if let error = error {
completion(error)
return
}
// Check HTTP response status code
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode != 200 {
let statusError = NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP error with status code: \(httpResponse.statusCode)"])
completion(statusError)
return
}
completion(nil)
}
// Start the network call
task.resume()
}
To use the Resend/Retry/Phone Change features, install the iOS SDK version 6.5.1 or later.
To set the One-Time Password (OTP) handler, withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep), requires implementing the OtpStartStep and OtpFinishStep.
The OTP session has a two minute timeout from when it’s sent through SMS to when the customer can enter in the OTP.
Default
Prompt for Phone Number
Resend
Retry OTP
Phone Number Change
Follow these instructions if you are implementing OTP and you are passing in the phone number on the /unify endpoint. In this case, you’ve already prompted for a phone number so don’t prompt for it in the client SDK.Since you passed the phone number in the Unify() function, call callback.onSuccess(input: nil) to communicate to the SDK you have the customer’s agreement to deliver the SMS message.class OtpStartStepNoPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to handle phone number collection for SMS OTP,
// or to obtain user confirmation for initiating an SMS message.
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
// Since no phone number is needed, don't prompt the user.
callback.onSuccess(input: nil)
}
}
Call the callback.onError() method to communicate to the SDK any issues while trying to obtain the phone number or the OTP. Report an error if the customer cancels the SMS transaction or presses the back button to leave the screen.class OtpFinishStepNoPrompt: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Signal to UI components to display OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Signal to your UI components that the last provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Provide the collected OTP value to the SDK for validation.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
}
Follow these instructions if implementing MobileAuth in the US and collecting the phone number only if MobileAuth fails. This implements OTP without allowing SMS re-sends and phone number changes. If you want those capabilities, see the Resend, Retry OTP, and Phone Number Change tabs.International MobileAuth requires phone number input, whereas US MobileAuth allows silent authentication without phone number input.
In the start step, call the callback.onSuccess(input: otpStartInput) method to return the collected phone number to the SDK.class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
Implement the finish step:class OtpFinishStepNoPrompt: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Signal to UI components to display OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Signal to your UI components that the last provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Provide the collected OTP value to the SDK for validation.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
}
Follow these instructions to allow the customer to request a new OTP via SMS using the same phone number. There is a max of three send attempts including the initial message.Implemented the start step:class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
You can then send a new OTP SMS to the same phone number by implementing the finish step like this:class OtpFinishStepMultipleResend: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered in the SMS message.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Update your UI to display the OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Update your UI to indicate that the provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Return the OTP value to the SDK
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Communicate to the SDK any issues when host app trys to obtain the OTP value
// or when users cancel the OTP flow
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
// Call this method to request a new OTP code for the same mobile number.
func sendNewOtp() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onOtpResend()
}
}
Follow these instructions to allow the customer to re-enter the OTP PIN if they type it wrong. There is a max of 3 attempts. To implement this capability, pass in allowOTPRetry=true to the /unify endpoint.Implement the start step:class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
Implement the finish step - no client side code changes necessary. If the OTP is invalid, call the finish step again to prompt the user for a new input. Once you reach the max attempts, the AuthFinish function runs.class OtpFinishStepNoPrompt: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Signal to UI components to display OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Signal to your UI components that the last provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Provide the collected OTP value to the SDK for validation.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
}
Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
Implement the start step:class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
You can prompt for a new phone number by implementing the finish step like this:class OtpFinishStepWithPhoneChange: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Update your UI to display the OTP finish view.
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Update your UI to indicate that the provided OTP is invalid.
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Return the collected OTP value to the SDK.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// When callback.onMobileNumberChange() is evoked, OtpStartStep will be re-initiated
// so that end-users can enter a different phone number via OtpStartStep.
func handleMobileNumberChange() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set")
return
}
callback.onMobileNumberChange()
}
}
Instant Link for iOS is an add-on feature. To enable, contact your Prove representative.
To integrate Instant Link, review the technical prerequisites, followed by the required function calls, and finally the detailed configuration steps for different use cases.
Instant Link prerequisites
For the Instant Link flow on iOS, the customer host app must have a successful deep link setup. To redirect back to the original host app after the user taps the Instant Link in the SMS, the iOS host app must handle deep links.
It’s highly recommended to use Universal Links for iOS. Full instructions: supporting universal links in your app | Apple developer documentation
Required functions
To set the Instant Link handler, withInstantLinkFallback(startStep: InstantLinkStartStep, retryStep?: InstantLinkRetryStep) requires implementing the InstantLinkStartStep protocol and optionally the InstantLinkRetryStep protocol if you wish for advanced capabilities. When returning the phone number, ensure you pass an InstantLinkStartInput with the phoneNumber field to the callback.
The Instant Link session has a five-minute timeout from when it’s sent through SMS to when the customer can click the received link.
Default
Prompt for Phone Number
Resend
Phone Number Change
Follow these instructions if you are implementing Instant Link and you are passing in the phone number in the initial server-side call. In this case, you’ve already prompted the customer for a phone number so you don’t prompt for it in the client SDK.Since you passed the phone number, call callback.onSuccess(input: nil) to communicate to the SDK that you have the customer’s agreement to deliver the SMS message.class InstantLinkStartStepNoPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to handle phone number collection for instant link,
// or to obtain user confirmation for initiating an instant link.
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
// Since no phone number is needed, don't prompt the user.
callback.onSuccess(input: nil)
}
}
Follow these instructions if you are implementing Mobile Auth and collecting the phone number for desktop. Use this to implement Instant Link without allowing the customer to resend the SMS or phone number changes.Call the callback.onSuccess(input: instantLinkStartInput) method to return the collected phone number to the SDK.Call the callback.onError() method to communicate to the SDK any issues while trying to obtain the phone number. Report an error if the customer cancels the Instant Link transaction or presses the back button to leave the Instant Link start step dialog.class InstantLinkStartStepWithPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display InstantLinkStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display InstantLinkStartView if a phone number is needed.
self.sheetObservable.isInstantLinkStartActive = true
}
}
}
// Return collected phone number to the SDK.
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
let instantLinkStartInput = InstantLinkStartInput(phoneNumber: phoneNumber)
callback.onSuccess(input: instantLinkStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the user cancels the instant link flow or exits the start step screen.
func handleInstantLinkStartError() {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
callback.onError()
}
}
To use the Resend/Phone Number Change features, install the iOS SDK version 6.10.2 or later.
Follow these instructions to allow the customer to request a new SMS using the same phone number. There is a max of three send attempts including the initial message.Implement the start step:class InstantLinkStartStepWithPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display InstantLinkStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display InstantLinkStartView if a phone number is needed.
self.sheetObservable.isInstantLinkStartActive = true
}
}
}
// Return collected phone number to the SDK.
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
let instantLinkStartInput = InstantLinkStartInput(phoneNumber: phoneNumber)
callback.onSuccess(input: instantLinkStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the user cancels the instant link flow or exits the start step screen.
func handleInstantLinkStartError() {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
callback.onError()
}
}
You can send a new Instant Link SMS to the same phone number by implementing the InstantLinkRetryStep protocol, for example:class InstantLinkRetryStepMultipleResend: InstantLinkRetryStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkRetryStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to present retry options: resend to the same phone number or cancel.
func execute(callback: InstantLinkRetryStepCallback) {
self.callback = callback
// Update your UI to display the InstantLinkRetryView (e.g. "Did you receive a text message?").
// User can confirm (close modal / finish) or request resend to the same phone number.
DispatchQueue.main.async {
self.sheetObservable.isInstantLinkRetryActive = true
}
}
// Call this method to request a new instant link to the same phone number.
func handleResend() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onResend()
}
// Notify the SDK if the user cancels or if the app fails to handle the retry step.
func handleInstantLinkRetryError() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onError()
}
}
To use the Resend/Phone Number Change features, install the iOS SDK version 6.10.2 or later.
Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
Implement the start step:class InstantLinkStartStepWithPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display InstantLinkStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display InstantLinkStartView if a phone number is needed.
self.sheetObservable.isInstantLinkStartActive = true
}
}
}
// Return collected phone number to the SDK.
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
let instantLinkStartInput = InstantLinkStartInput(phoneNumber: phoneNumber)
callback.onSuccess(input: instantLinkStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the user cancels the instant link flow or exits the start step screen.
func handleInstantLinkStartError() {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
callback.onError()
}
}
You can prompt for a new phone number by implementing the InstantLinkRetryStep protocol, for example:class InstantLinkRetryStepPhoneChange: InstantLinkRetryStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkRetryStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to present retry options: resend, change phone number, or cancel.
func execute(callback: InstantLinkRetryStepCallback) {
self.callback = callback
// Update your UI to display the InstantLinkRetryView (e.g. "Did you receive a text message?").
// User can confirm (close modal / finish), request resend, or request phone number change.
DispatchQueue.main.async {
self.sheetObservable.isInstantLinkRetryActive = true
}
}
// Call this method to request resend to the same phone number.
func handleResend() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onResend()
}
// When this is invoked, InstantLinkStartStep will be re-initiated so that the user
// can enter a different phone number.
func handleMobileNumberChange() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onMobileNumberChange()
}
// Notify the SDK if the user cancels or if the app fails to handle the retry step.
func handleInstantLinkRetryError() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onError()
}
}
Call finishInstantLink to resume auth session after redirect
The Configure Instant Link Steps and calling proveAuth.authenticate(), the app must also call finishInstantLink(redirectUrl:) after the session redirects back to the app when the Instant Link click is complete.
This redirect occurs after the following steps, which take place outside the customer host app:
- The end-user taps the Instant Link delivered via SMS.
- The end-user goes to a stand-alone website to complete Instant Link authentication.
- That page then redirects the user back to the original customer host app via the same deep link provided as the
finalTargetUrl field in the earlier server-to-server call. See: Server Integration Guide — Device Auth with Mobile Auth and Instant Link Fallback.
Upon successful redirect back to the customer host app, the app must call finishInstantLink(redirectUrl:) to continue the session.
/// Finishes the Instant Link authentication flow using the redirect URL from the deep link.
/// This is the URL to which the user is redirected after tapping the Instant Link in SMS.
finishInstantLink(redirectUrl: redirectUrl) { error in
// Handle errors due to invalid format of redirectUrl here
}
Invoke finishInstantLink(redirectUrl:) from your app’s entry point that handles the deep link redirect, application(_:open:options:) or application(_:continue:restorationHandler:). Pass the full redirect URL string returned by the Prove server (delivered to the app after the user opens the Instant Link in SMS).
The redirect URL returned by the Prove server has your original finalTargetUrl plus extra parameters needed to resume the session in the customer host app. For example, if the original finalTargetUrl provided is https://yourDeepLinkUrl.com, the redirect URL that opens your app is https://yourDeepLinkUrl.com?asc=true&authId=some-uuid-string. Those parameters are:
- asc — Must be the string
"true" or "false". Indicates whether the auth session was completed successfully on the server side (true) or not yet (false).
- authId — Must be a valid UUID string. Identifies the authentication session; the SDK uses it to match the redirect to the correct in-progress session.
If the URL is missing these parameters or they’re invalid, the SDK calls onError and doesn’t continue the flow.