Requirements

  • iOS 13.0+
  • Xcode 12.0+
  • Swift 5.5+

This is the new Headless authentication SDK that is significantly faster and more robust than the previous version. This upgrade enhances performance, reliability, and security, ensuring a seamless authentication experience, along with a seamless integration process. We strongly recommend migrating to the new SDK for improved efficiency and better support. To migrate from the old SDK, remove the previous SDK dependency and integration and follow the below mentioned steps.

Step 1: Add SDK Dependency

SDK can be installed via both Cocoapods and Swift Package Manager.

Please find the latest version of the SDK here.

Cocoapods

  • Open your app’s project file .xcodeproj.
  • Add the following line into the dependencies section of your project’s Podfile:
pod 'OtplessBM/Core', 'latest_version'

Make sure to run the following commands in your root folder to fetch the dependency.

pod repo update
pod install

Swift Package Manager

  1. In Xcode, click File > Swift Packages > Add Package Dependency.
  2. In the dialog that appears, enter the repository URL: https://github.com/otpless-tech/otpless-headless-iOS-sdk.git.
  3. Select the dependency rule as exact version and use the latest version.

Step 2: Configure info.plist

Add the following block to your info.plist file:

info.plist
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>otpless.{{YOUR_APP_ID_IN_LOWERCASE}}</string>
        </array>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>otpless</string>
    </dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>whatsapp</string>
    <string>otpless</string>
    <string>gootpless</string>
    <string>com.otpless.ios.app.otpless</string>
    <string>googlegmail</string>
</array>

Add the following block to your info.plist file (Only required if you are using the SNA feature):

info.plist
<dict>
	<key>NSAllowsArbitraryLoads</key>
	<true/>
	<key>NSExceptionDomains</key>
	<dict>
		<key>80.in.safr.sekuramobile.com</key>
		<dict>
			<key>NSIncludesSubdomains</key>
			<true/>
			<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
			<true/>
			<key>NSTemporaryExceptionMinimumTLSVersion</key>
			<string>TLSv1.1</string>
		</dict>
		<key>partnerapi.jio.com</key>
		<dict>
			<key>NSIncludesSubdomains</key>
			<true/>
			<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
			<true/>
			<key>NSTemporaryExceptionMinimumTLSVersion</key>
			<string>TLSv1.1</string>
		</dict>
	</dict>
</dict>

Replace YOUR_APP_ID with your actual App ID provided in your OTPLESS dashboard.

Step 3: Handle Redirection

Add the following code to your App Delegate to handle redirection:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 
    if Otpless.shared.isOtplessDeeplink(url: url){
      Task(priority: .userInitiated) {
          await Otpless.shared.processOtplessDeeplink(url: url) 
      }
    }
    return true 
}

Step 4: Initialize OTPLESS

Import OtplessBM in your signup/sign in file.

import OtplessBM

Initialise OTPLESS in viewDidLoad() function before proceeding further.

ViewController.swift
Otpless.shared.initialise(withAppId: "YOUR_APPID", vc: self)
Otpless.shared.setResponseDelegate(self)

Replace YOUR_APP_ID with your actual App ID provided in your OTPLESS dashboard.

Step 5: Handle Callback

Conform to OtplessResponseDelegate in your signup/sign in file to receive callbacks from OtplessBM.

func onResponse(_ response: OtplessBM.OtplessResponse) {
    Otpless.shared.commitOtplessResponse(response)
    
    switch response.responseType {
    case .INITIATE:
        // Notify that authentication has been initiated
        if response.statusCode == 200 {
            print("Authentication initiated")
        } else {
            handleInitiateError(response)
        }
        
    case .VERIFY:
        // Notify that verification has failed
        handleVerifyError(response)
        
    case .ONETAP:
        // Final response with token
        if response.statusCode == 200 {
            if let data = response.response?["data"] as? [String: Any],
                let token = data["token"] as? String, !token.isEmpty {
                // Process token and proceed to verify the token on your backend.
                print("Received token: \(token)")
            } else {
                print("Token not received")
            }
        } else {
            print("Token verification failed")
        }
        
    case .FALLBACK_TRIGGERED:
        // A fallback occurs when an OTP delivery attempt on one channel fails,  
        // and the system automatically retries via the subsequent channel selected on Otpless Dashboard.  
        // For example, if a merchant opts for SmartAuth with primary channal as WhatsApp and secondary channel as SMS,
        // in that case, if OTP delivery on WhatsApp fails, the system will automatically retry via SMS.
        // The response will contain the deliveryChannel to which the OTP has been sent.
        let newDeliveryChannel = response.response?["deliveryChannel"] as? String // This is the deliveryChannel to which the OTP has been sent
        print("Fallback authentication triggered")
        
    case .SDK_READY:
        // Notify that SDK has been initialized successfully
        print("SDK has been initialized successfully, you may enable your continue button or proceed with user authentication.")
    case .FAILED:
        // Notify that the initialization has failed
        if response.statusCode == 5003 {
            print("SDK initialization failed, please try to initialize the SDK again")
        }
    }
}

Handle Initiate error response:

func handleInitiateError(_ response: OtplessResponse) {
    guard let responseDict = response.response as? [String: Any] else { return }
    
    let errorCode = responseDict["errorCode"] as? String
    let errorMessage = responseDict["errorMessage"] as? String

    switch errorCode {
    case "7101":
        print("OTPless Error: Invalid parameters values or missing parameters - \(errorMessage ?? "Unknown error")")
    case "7102":
        print("OTPless Error: Invalid phone number - \(errorMessage ?? "Unknown error")")
    case "7103":
        print("OTPless Error: Invalid phone number delivery channel - \(errorMessage ?? "Unknown error")")
    case "7104":
        print("OTPless Error: Invalid email - \(errorMessage ?? "Unknown error")")
    case "7105":
        print("OTPless Error: Invalid email channel - \(errorMessage ?? "Unknown error")")
    case "7106":
        print("OTPless Error: Invalid phone number or email - \(errorMessage ?? "Unknown error")")
    case "7113":
        print("OTPless Error: Invalid expiry - \(errorMessage ?? "Unknown error")")
    case "7116":
        print("OTPless Error: OTP Length is invalid (4 or 6 only allowed) - \(errorMessage ?? "Unknown error")")
    case "7121":
        print("OTPless Error: Invalid app hash - \(errorMessage ?? "Unknown error")")
    case "4000":
        print("OTPless Error: Invalid request values - \(errorMessage ?? "Unknown error")")
    case "4001":
        print("OTPless Error: Unsupported 2FA request - \(errorMessage ?? "Unknown error")")
    case "4003":
        print("OTPless Error: Incorrect request channel - \(errorMessage ?? "Unknown error")")
    case "401", "7025":
        print("OTPless Error: Unauthorized request or country not enabled - \(errorMessage ?? "Unknown error")")
    case "7020", "7022", "7023", "7024":
        print("OTPless Error: Rate limiting error (Too many requests) - \(errorMessage ?? "Unknown error")")

    // Feature not supported on older iOS versions
    case "5900":
        print("OTPless Error: The feature is not supported because it requires a newer iOS version - \(errorMessage ?? "The requested feature is only available on newer iOS versions.")")

    // Internet-related errors
    case "9100":
        print("OTPless Error: Request Timeout - \(errorMessage ?? "The request took too long to complete and was aborted.")")
    case "9101":
        print("OTPless Error: Network Connection Was Lost - \(errorMessage ?? "The connection was interrupted before the request could complete.")")
    case "9102":
        print("OTPless Error: DNS Lookup Failed - \(errorMessage ?? "The domain name could not be resolved, possibly due to network issues.")")
    case "9103":
        print("OTPless Error: Cannot Connect to Server - \(errorMessage ?? "The server is unreachable, possibly due to downtime or incorrect configurations.")")
    case "9104":
        print("OTPless Error: No Internet Connection - \(errorMessage ?? "The device is not connected to the internet.")")
    case "9105":
        print("OTPless Error: Secure Connection Failed - \(errorMessage ?? "A secure connection could not be established due to SSL/TLS issues.")")
    case "9110":
        print("OTPless Error: Otpless Authentication Request Cancelled - \(errorMessage ?? "The authentication request was manually canceled by the user or the system.")")

    default:
        print("OTPless Error: \(errorMessage ?? "Unknown error")")
    }
}

Handle Verify error response:

func handleVerifyError(response: OtplessResponse) {
    guard let responseDict = response.response as? [String: Any] else { return }
    
    let errorCode = responseDict["errorCode"] as? String
    let errorMessage = responseDict["errorMessage"] as? String

    switch errorCode {
    case "7112":
        // Handle request error: Empty OTP
        print("OTPless Error: \(errorMessage ?? "Unknown error")")
    case "7115":
        // Handle request error: OTP is already verified
        print("OTPless Error: \(errorMessage ?? "Unknown error")")
    case "7118":
        // Handle request error: Incorrect OTP
        print("OTPless Error: \(errorMessage ?? "Unknown error")")
    case "7303":
        // Handle request error: OTP expired
        print("OTPless Error: \(errorMessage ?? "Unknown error")")
    case "4000":
        // Handle invalid request
        print("OTPless Error: \(errorMessage ?? "Unknown error")")

    // Internet-related errors
    case "9100":
        print("OTPless Error: Request Timeout - \(errorMessage ?? "The request took too long to complete and was aborted.")")
    case "9101":
        print("OTPless Error: Network Connection Was Lost - \(errorMessage ?? "The connection was interrupted before the request could complete.")")
    case "9102":
        print("OTPless Error: DNS Lookup Failed - \(errorMessage ?? "The domain name could not be resolved, possibly due to network issues.")")
    case "9103":
        print("OTPless Error: Cannot Connect to Server - \(errorMessage ?? "The server is unreachable, possibly due to downtime or incorrect configurations.")")
    case "9104":
        print("OTPless Error: No Internet Connection - \(errorMessage ?? "The device is not connected to the internet.")")
    case "9105":
        print("OTPless Error: Secure Connection Failed - \(errorMessage ?? "A secure connection could not be established due to SSL/TLS issues.")")
    case "9110":
        print("OTPless Error: Otpless Authentication Request Cancelled - \(errorMessage ?? "The authentication request was manually canceled by the user or the system.")")

    default:
        print("OTPless Error: \(errorMessage ?? "Unknown error")")
    }
}

Step 6: Create a Task:

Create a task to initiate the authentication process based on the user’s selected method.

var otplessTask: Task<Void, Never>? // Store the ongoing task reference
  • Store the tasks created during Otpless authentication in the otplessTask variable.
  • Cancel the ongoing task before creating a new task to prevent duplication of requests or sending same request twice.

Step 7: Initiate Authentication

Initiate the authentication process based on the user’s selected method by using the initiate method of the SDK.

Phone Authentication 📱
Phone authentication allows users to verify their identity using their phone number. Merchants can choose from various authentication methods:

  • Silent Authentication (SNA) – Automatically verifies the user without requiring OTP or MAGICLINK.
  • OTP on Desired Channel – Sends a one-time password (OTP) via SMS, WhatsApp, or another preferred channel.
  • Magic Link – Sends a link that users can click to authenticate.
  • SNA + OTP – Uses silent authentication first and falls back to OTP if needed.
  • OTP + Magic Link – Sends both an OTP and a magic link, allowing users to authenticate via either method.
otplessTask?.cancel() // Cancel any ongoing tasks to prevent request duplication
let request = OtplessRequest()
request.set(phoneNumber: "9899XXXXXX", withCountryCode: "COUNTRY_CODE")
otplessTask = Task(priority: .userInitiated) {
  await Otpless.shared.start(withRequest: request)
}

Verify OTP

To verify the OTP entered by the user, use the verify method with the necessary parameters. Verifying OTP is required only in case of OTP authentication. No need to verify OTP in case of MAGICLINK.

let request = OtplessRequest()
request.set(phoneNumber: "9899XXXXXX", withCountryCode: "COUNTRY_CODE")
request.set(otp: otp)
Task(priority: .userInitiated) {
    await Otpless.shared.start(withRequest: request)
}

🏁 Checkpoint

To ensure a smooth integration process:

  1. Deploy your app/website with the included OTPLESS SDK.
  2. Conduct tests to verify the sign-in flow functions correctly.
  3. Ensure that after a successful sign-in, the user is redirected back to your app/website and their information is correctly logged in the console.

User Information Response Structure

The structure of the user information returned upon successful sign-in is as follows:

{
  "status": "SUCCESS",
  "token": "unique_token_here",
  "userId": "unique_user_id_here",
  "timestamp": "ISO_timestamp_here",
  "identities": [
    {
       "identityType": "EMAIL",
      "identityValue": "user@example.com",
      "channel": "OAUTH",
      "methods": [
        "GOOGLE"
      ],
      "name": "User Name",
      "verified": true,
      "verifiedAt": "ISO_timestamp_here",
      "isCompanyEmail": "true"
    }
  ],
  "idToken": "jwt_token",
  "network": {
    "ip": "127.0.0.1",
    "timezone": "Asia/Kolkata",
    "ipLocation": {}
  },
  "deviceInfo": {},
  "sessionInfo": {},
  "firebaseInfo": {},
}

You can check out a complete sample response here.

Next Steps