Skip to main content

Nafsi iOS SDK

Nafsi iOS SDK for identity verification powered by Nafsi AI. Easily integrate secure identity verification into your iOS app.

Before You Start

Ensure your project meets these exact requirements:

RequirementMinimum VersionRecommended
iOS15.016.0+
Xcode15.015.2+
Swift5.95.9+

Checklist before integration:

  • Your project uses SwiftUI (not UIKit storyboards)
  • You have iOS 15.0+ deployment target
  • You have a valid JWT token from Nafsi

Installation

Step 1: Add the SDK via Swift Package Manager

  1. In Xcode, go to File → Add Package Dependencies...
  2. Enter the repository URL:
    https://github.com/AdeWang0629/nafsi-ios-sdk.git
  3. Select version 1.0.0 or later
  4. Click Add Package

Or add to your Package.swift:

dependencies: [
.package(url: "https://github.com/AdeWang0629/nafsi-ios-sdk.git", from: "1.0.0")
]
Using CocoaPods? Click here

Add to your Podfile:

pod 'NafsiSDK', '~> 1.0'

Then run:

pod install

Step 2: Add Camera Permission

Open your Info.plist and add the camera usage description:

<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity verification</string>

Or in Xcode:

  1. Select your project in the navigator
  2. Select your target → Info tab
  3. Add a new key: Privacy - Camera Usage Description
  4. Set value: Camera access is required for identity verification

Step 3: Build and Run

  1. Build your project: Product → Build (or Cmd+B)
  2. If the build succeeds, proceed to Step 4

If you get errors, see Troubleshooting.


Step 4 & 5: Backend Authentication Setup

Before your iOS app can use the Nafsi SDK, you need to set up backend authentication. This is a two-part process:

┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION FLOW OVERVIEW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ STEP 4 (One-Time Setup) STEP 5 (Runtime) │
│ ───────────────────────── ───────────────── │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Your Server │ ──POST──► │ iOS App │───►│ Your Backend │ │
│ └──────────────┘ │ └──────────────┘ └──────────────┘ │
│ │ │ ▲ │ │
│ │ ▼ │ │ │
│ │ ┌────────────┐ │ ▼ │
│ │ │ Nafsi API │ │ ┌────────────┐ │
│ │ └────────────┘ │ │ Generate │ │
│ │ │ │ │ JWT Token │ │
│ │ │ │ │ (includes │ │
│ │ ▼ │ │ refresh_ │ │
│ │ ┌────────────────┐ │ │ token) │ │
│ │ │ Returns JWT │ │ └────────────┘ │
│ │ │ with refresh_ │ │ │ │
│ │ │ token inside │ └───────────────────┘ │
│ │ └────────────────┘ Return JWT │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Save refresh_token to .env │ │
│ │ (Use in Step 5) │ │
│ └────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Step 4: Generate Refresh Token (One-Time Setup)

First, obtain a refresh token from the Nafsi API. You only need to do this once during initial setup.

API Request

Make a POST request to generate your access and refresh tokens:

curl -X POST https://apisv2.windeal.co.ke/postdata \
-H "Content-Type: application/json" \
-d '{
"method": "generateAccessToken",
"menu": "auth_config",
"client_secret": "YOUR_CLIENT_SECRET",
"client_id": "YOUR_CLIENT_ID",
"rpcQueue": "nafsi"
}'

Request Body

FieldTypeRequiredDescription
methodStringYesMust be "generateAccessToken"
menuStringYesMust be "auth_config"
client_secretStringYesYour client secret from Nafsi Dashboard
client_idStringYesYour client ID from Nafsi Dashboard
rpcQueueStringYesMust be "nafsi"

Response

The API returns a JWT token in the result field:

{
"method": "generateAccessToken",
"menu": "auth_config",
"client_id": "your-client-id",
"result": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Extract the Refresh Token

Decode the JWT token from the result field to extract the refresh token. The decoded payload contains:

{
"data": {
"expires_at": "2026-03-17 07:57:03.000000",
"expires_in": 3600,
"token_type": "Bearer",
"access_token": "83049a96882286...",
"refresh_token": "8d85fa5e89818f4dcd62593f01924068..."
},
"status": "success",
"iat": 1773730623,
"exp": 1773734223
}

Save the refresh_token value - you'll use this in Step 5 for backend token generation.

Decode JWT in Different Languages

Swift
import Foundation

func decodeJWT(_ jwt: String) -> [String: Any]? {
let parts = jwt.components(separatedBy: ".")
guard parts.count == 3 else { return nil }

var payload = parts[1]
// Add padding if needed
while payload.count % 4 != 0 {
payload += "="
}

guard let data = Data(base64Encoded: payload),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return nil
}

return json
}

// Usage
let result = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // From API response
if let decoded = decodeJWT(result),
let data = decoded["data"] as? [String: Any],
let refreshToken = data["refresh_token"] as? String {
print("Refresh Token: \(refreshToken)")
// Save this to your environment variables
}
Node.js
const jwt = require('jsonwebtoken');

const result = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From API response
const decoded = jwt.decode(result);
const refreshToken = decoded.data.refresh_token;

console.log("Refresh Token:", refreshToken);
// Save this to your environment variables
Python
import jwt

result = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." # From API response
decoded = jwt.decode(result, options={"verify_signature": False})
refresh_token = decoded["data"]["refresh_token"]

print(f"Refresh Token: {refresh_token}")
# Save this to your environment variables
Java
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

import java.util.Base64;

public class JwtDecoder {
public static void main(String[] args) {
String result = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From API response

// Decode JWT without verification
DecodedJWT jwt = JWT.decode(result);

// Get payload and decode from Base64
String payload = new String(Base64.getUrlDecoder().decode(jwt.getPayload()));

// Parse JSON to extract refresh_token
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(payload, JsonObject.class);
String refreshToken = jsonObject
.getAsJsonObject("data")
.get("refresh_token")
.getAsString();

System.out.println("Refresh Token: " + refreshToken);
// Save this to your environment variables
}
}

Maven dependency:

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
Spring Boot
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;

import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Service
public class NafsiTokenService {

private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();

public String generateRefreshToken(String clientId, String clientSecret) {
String url = "https://apisv2.windeal.co.ke/postdata";

// Build request body
Map<String, String> requestBody = new HashMap<>();
requestBody.put("method", "generateAccessToken");
requestBody.put("menu", "auth_config");
requestBody.put("client_secret", clientSecret);
requestBody.put("client_id", clientId);
requestBody.put("rpcQueue", "nafsi");

// Set headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<Map<String, String>> entity = new HttpEntity<>(requestBody, headers);

// Make POST request
ResponseEntity<Map> response = restTemplate.postForEntity(url, entity, Map.class);

// Extract JWT from result field
String jwtResult = (String) response.getBody().get("result");

// Decode JWT to get refresh token
DecodedJWT jwt = JWT.decode(jwtResult);
String payload = new String(Base64.getUrlDecoder().decode(jwt.getPayload()));

JsonNode jsonNode = objectMapper.readTree(payload);
String refreshToken = jsonNode.get("data").get("refresh_token").asText();

System.out.println("Refresh Token: " + refreshToken);
// Save this to your application.properties or environment variables

return refreshToken;
}
}

Maven dependencies:

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
PHP
<?php

// Using firebase/php-jwt library
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$result = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From API response

// Decode JWT without verification (just to read payload)
$parts = explode('.', $result);
$payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);

$refreshToken = $payload['data']['refresh_token'];

echo "Refresh Token: " . $refreshToken . "\n";
// Save this to your .env file

Composer dependency:

composer require firebase/php-jwt

Full PHP example with API call:

<?php

function generateRefreshToken($clientId, $clientSecret) {
$url = "https://apisv2.windeal.co.ke/postdata";

$data = [
"method" => "generateAccessToken",
"menu" => "auth_config",
"client_secret" => $clientSecret,
"client_id" => $clientId,
"rpcQueue" => "nafsi"
];

$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode($data)
]
];

$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
$responseData = json_decode($response, true);

// Decode the JWT result
$jwtResult = $responseData['result'];
$parts = explode('.', $jwtResult);
$payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);

$refreshToken = $payload['data']['refresh_token'];

echo "Refresh Token: " . $refreshToken . "\n";
// Save to your .env file: NAFSI_REFRESH_TOKEN=...

return $refreshToken;
}

// Usage
$refreshToken = generateRefreshToken('your-client-id', 'your-client-secret');
Online Tool

You can also use jwt.io to decode the token:

  1. Go to https://jwt.io
  2. Paste the result JWT token in the "Encoded" field
  3. The decoded payload will show in the right panel
  4. Copy the refresh_token from data.refresh_token

Important: Store the refresh token securely as an environment variable on your server. Never expose it in client-side code. You'll use this refresh token in Step 5 below.


Step 5: Create Backend Token Endpoint

Now that you have the refresh_token from Step 4, create a backend endpoint that generates JWT tokens for the mobile SDK.

Why a backend endpoint? The Nafsi SDK requires a JWT token containing sensitive credentials. These must never be stored in mobile apps - always generate tokens server-side.

┌──────────────┐    1. Request Token     ┌────────────────┐
│ iOS App │ ──────────────────────► │ Your Backend │
│ (Nafsi SDK) │ │ Server │
└──────────────┘ └────────────────┘
▲ │
│ Uses refresh_token
│ from Step 4
│ │
│ ▼
│ ┌────────────────┐
│ 2. Return JWT Token │ Generate JWT │
└──────────────────────────────│ with claims │
└────────────────┘

Node.js / Express Example

const express = require("express");
const jwt = require("jsonwebtoken");

const app = express();

// Protect this endpoint with your authentication middleware
app.get("/generate-nafsi-token", authMiddleware, (req, res) => {
const userId = req.user.id; // From your auth middleware

const payload = {
workflowId: process.env.NAFSI_WORKFLOW_ID, // From Nafsi Dashboard
clientId: process.env.NAFSI_CLIENT_ID, // From Nafsi Dashboard
config: "ke_national_id", // Document type
apiUrl: process.env.NAFSI_API_URL, // Nafsi API URL
organisationId: process.env.NAFSI_ORG_ID, // Your organization ID
refresh_token: process.env.NAFSI_REFRESH_TOKEN, // From Step 4 above
sub: userId // Optional: Include user context for audit trails
};

const token = jwt.sign(payload, process.env.NAFSI_JWT_SECRET, {
expiresIn: "24h"
});

res.json({ token });
});

app.listen(3000);
Python / Flask Example
from flask import Flask, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
import jwt
import os
from datetime import datetime, timedelta

app = Flask(__name__)

@app.route("/generate-nafsi-token", methods=["GET"])
@jwt_required() # Your auth decorator
def generate_nafsi_token():
user_id = get_jwt_identity()

payload = {
"workflowId": os.environ["NAFSI_WORKFLOW_ID"],
"clientId": os.environ["NAFSI_CLIENT_ID"],
"config": "ke_national_id",
"apiUrl": os.environ["NAFSI_API_URL"],
"organisationId": os.environ["NAFSI_ORG_ID"],
"refresh_token": os.environ["NAFSI_REFRESH_TOKEN"],
"sub": user_id,
"exp": datetime.utcnow() + timedelta(hours=24)
}

token = jwt.encode(payload, os.environ["NAFSI_JWT_SECRET"], algorithm="HS256")
return jsonify({"token": token})
PHP / Laravel Example
<?php

use Firebase\JWT\JWT;
use Illuminate\Http\Request;

Route::middleware('auth:sanctum')->get('/generate-nafsi-token', function (Request $request) {
$payload = [
'workflowId' => env('NAFSI_WORKFLOW_ID'),
'clientId' => env('NAFSI_CLIENT_ID'),
'config' => 'ke_national_id',
'apiUrl' => env('NAFSI_API_URL'),
'organisationId' => env('NAFSI_ORG_ID'),
'refresh_token' => env('NAFSI_REFRESH_TOKEN'),
'sub' => $request->user()->id,
'exp' => time() + (24 * 60 * 60) // 24 hours
];

$token = JWT::encode($payload, env('NAFSI_JWT_SECRET'), 'HS256');

return response()->json(['token' => $token]);
});
Java / Spring Boot Example
package com.yourcompany.service;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@RestController
public class NafsiTokenController {

@Value("${nafsi.workflow-id}")
private String workflowId;

@Value("${nafsi.client-id}")
private String clientId;

@Value("${nafsi.api-url}")
private String apiUrl;

@Value("${nafsi.org-id}")
private String organisationId;

@Value("${nafsi.refresh-token}")
private String refreshToken;

@Value("${nafsi.jwt-secret}")
private String jwtSecret;

@GetMapping("/generate-nafsi-token")
public ResponseEntity<Map<String, String>> generateNafsiToken(
@AuthenticationPrincipal UserDetails userDetails) {

Algorithm algorithm = Algorithm.HMAC256(jwtSecret);

String token = JWT.create()
.withClaim("workflowId", workflowId)
.withClaim("clientId", clientId)
.withClaim("config", "ke_national_id")
.withClaim("apiUrl", apiUrl)
.withClaim("organisationId", organisationId)
.withClaim("refresh_token", refreshToken)
.withSubject(userDetails.getUsername()) // Optional: user context
.withExpiresAt(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) // 24 hours
.sign(algorithm);

Map<String, String> response = new HashMap<>();
response.put("token", token);

return ResponseEntity.ok(response);
}
}

application.properties:

# Nafsi Configuration (use environment variables in production)
nafsi.workflow-id=${NAFSI_WORKFLOW_ID}
nafsi.client-id=${NAFSI_CLIENT_ID}
nafsi.api-url=${NAFSI_API_URL}
nafsi.org-id=${NAFSI_ORG_ID}
nafsi.refresh-token=${NAFSI_REFRESH_TOKEN}
nafsi.jwt-secret=${NAFSI_JWT_SECRET}

Maven dependencies (pom.xml):

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>
Java / Servlet Example (Non-Spring)
package com.yourcompany.servlet;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.google.gson.Gson;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@WebServlet("/generate-nafsi-token")
public class NafsiTokenServlet extends HttpServlet {

private static final String WORKFLOW_ID = System.getenv("NAFSI_WORKFLOW_ID");
private static final String CLIENT_ID = System.getenv("NAFSI_CLIENT_ID");
private static final String API_URL = System.getenv("NAFSI_API_URL");
private static final String ORG_ID = System.getenv("NAFSI_ORG_ID");
private static final String REFRESH_TOKEN = System.getenv("NAFSI_REFRESH_TOKEN");
private static final String JWT_SECRET = System.getenv("NAFSI_JWT_SECRET");

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {

// Get authenticated user (implement your auth logic)
String userId = (String) request.getAttribute("userId");

Algorithm algorithm = Algorithm.HMAC256(JWT_SECRET);

String token = JWT.create()
.withClaim("workflowId", WORKFLOW_ID)
.withClaim("clientId", CLIENT_ID)
.withClaim("config", "ke_national_id")
.withClaim("apiUrl", API_URL)
.withClaim("organisationId", ORG_ID)
.withClaim("refresh_token", REFRESH_TOKEN)
.withSubject(userId)
.withExpiresAt(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000))
.sign(algorithm);

Map<String, String> result = new HashMap<>();
result.put("token", token);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new Gson().toJson(result));
}
}

Maven dependencies:

<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
PHP (Plain / Non-Laravel)
<?php

require_once 'vendor/autoload.php';

use Firebase\JWT\JWT;

// Load environment variables (use dotenv or similar in production)
$workflowId = getenv('NAFSI_WORKFLOW_ID');
$clientId = getenv('NAFSI_CLIENT_ID');
$apiUrl = getenv('NAFSI_API_URL');
$orgId = getenv('NAFSI_ORG_ID');
$refreshToken = getenv('NAFSI_REFRESH_TOKEN');
$jwtSecret = getenv('NAFSI_JWT_SECRET');

// Your authentication logic here
session_start();
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}

$userId = $_SESSION['user_id'];

$payload = [
'workflowId' => $workflowId,
'clientId' => $clientId,
'config' => 'ke_national_id',
'apiUrl' => $apiUrl,
'organisationId' => $orgId,
'refresh_token' => $refreshToken,
'sub' => $userId,
'exp' => time() + (24 * 60 * 60) // 24 hours
];

$token = JWT::encode($payload, $jwtSecret, 'HS256');

header('Content-Type: application/json');
echo json_encode(['token' => $token]);

Composer dependency:

composer require firebase/php-jwt vlucas/phpdotenv

Environment Variables

Store your Nafsi credentials as environment variables on your server. The NAFSI_REFRESH_TOKEN is the value you obtained in Step 4.

# .env (Server-side only - NEVER commit to git)
NAFSI_WORKFLOW_ID=your-workflow-id # From Nafsi Dashboard
NAFSI_CLIENT_ID=your-client-id # From Nafsi Dashboard
NAFSI_API_URL=https://api.nafsi.ai # Nafsi API URL
NAFSI_ORG_ID=your-org-id # From Nafsi Dashboard
NAFSI_REFRESH_TOKEN=8d85fa5e89818f4d... # From Step 4 above
NAFSI_JWT_SECRET=your-jwt-secret # Your own secret for signing JWTs

JWT Payload Reference

FieldTypeRequiredDescription
workflowIdStringYesYour workflow ID from Nafsi Dashboard
clientIdStringYesYour client ID from Nafsi Dashboard
configStringYesDocument type (e.g., ke_national_id, ke_passport)
apiUrlStringYesNafsi API URL
organisationIdStringYesYour organization ID
refresh_tokenStringYesFrom Step 4 - the token you extracted
subStringNoUser identifier for audit trails
expNumberNoExpiration time (Unix timestamp)

Security Best Practices

  • Never hardcode secrets in mobile apps - Always fetch tokens from your backend
  • Authenticate users first - Only generate tokens for authenticated users
  • Use short expiration times - 24 hours or less is recommended
  • Use HTTPS - Always use HTTPS for token requests
  • Rate limiting - Implement rate limiting on your token endpoint

Quick Start

Minimal Integration

First, create a service to fetch the token from your backend:

import Foundation

class NafsiTokenService {
// For iOS Simulator: use localhost or your machine's IP
// For physical device: use your machine's local IP (e.g., 192.168.x.x)
// For production: use your actual server URL
private let baseURL = "http://localhost:3000"

func fetchToken() async throws -> String {
guard let url = URL(string: "\(baseURL)/generate-nafsi-token") else {
throw URLError(.badURL)
}

let (data, _) = try await URLSession.shared.data(from: url)
let response = try JSONDecoder().decode(TokenResponse.self, from: data)
return response.token
}
}

struct TokenResponse: Codable {
let token: String
}

Then add the verification view to your SwiftUI app:

import SwiftUI
import NafsiSDK

struct VerificationScreen: View {
let jwtToken: String

var body: some View {
NafsiVerificationView(
config: NafsiConfig(token: jwtToken),
onSuccess: { result in
print("Success: \(result.verificationId ?? "")")
},
onError: { error in
print("Error: \(error.localizedDescription)")
},
onCancel: {
print("User cancelled")
}
)
}
}

Complete Working Example

Copy this entire file as your ContentView.swift:

import SwiftUI
import NafsiSDK

struct ContentView: View {
@State private var showVerification = false
@State private var jwtToken: String?
@State private var isLoading = true
@State private var errorMessage: String?
@State private var resultMessage: String?

private let tokenService = NafsiTokenService()

var body: some View {
Group {
if isLoading {
VStack(spacing: 16) {
ProgressView()
Text("Loading token...")
.foregroundColor(.secondary)
}
} else if let error = errorMessage {
VStack(spacing: 16) {
Text(error)
.foregroundColor(.red)
.multilineTextAlignment(.center)

Button("Retry") {
loadToken()
}
.buttonStyle(.borderedProminent)
}
.padding()
} else if showVerification, let token = jwtToken {
NafsiVerificationView(
config: NafsiConfig(
token: token,
customization: NafsiCustomization(
primaryColor: "#10b981",
secondaryColor: "#065f46",
title: "Identity Verification",
tagline: "Quick and secure verification"
)
),
onSuccess: { result in
resultMessage = "Success! ID: \(result.verificationId ?? "N/A")"
showVerification = false
},
onError: { error in
resultMessage = "Error: \(error.localizedDescription)"
showVerification = false
},
onCancel: {
resultMessage = "Cancelled"
showVerification = false
}
)
} else {
VStack(spacing: 24) {
Button("Start Verification") {
showVerification = true
}
.buttonStyle(.borderedProminent)
.controlSize(.large)

if let result = resultMessage {
Text(result)
.foregroundColor(.secondary)
}
}
.padding()
}
}
.task {
loadToken()
}
}

private func loadToken() {
isLoading = true
errorMessage = nil

Task {
do {
jwtToken = try await tokenService.fetchToken()
isLoading = false
} catch {
errorMessage = "Failed to fetch token: \(error.localizedDescription)"
isLoading = false
}
}
}
}

// Token Service
class NafsiTokenService {
private let baseURL = "http://localhost:3000"

func fetchToken() async throws -> String {
guard let url = URL(string: "\(baseURL)/generate-nafsi-token") else {
throw URLError(.badURL)
}

let (data, _) = try await URLSession.shared.data(from: url)
let response = try JSONDecoder().decode(TokenResponse.self, from: data)
return response.token
}
}

struct TokenResponse: Codable {
let token: String
}

#Preview {
ContentView()
}

Customization

Theme Colors

NafsiCustomization(
primaryColor: "#10b981", // Buttons, accents
secondaryColor: "#065f46", // Secondary elements
buttonTextColor: "#FFFFFF", // Button text
successColor: "#17D27C", // Success screen
errorColor: "#FF4842" // Error screen
)

Branding

NafsiCustomization(
logoUrl: "https://your-company.com/logo.png",
title: "Your Company Name",
tagline: "Your tagline here"
)

Note: Branding configured in your Nafsi dashboard takes precedence over local customization.


Verification Flow

  1. Landing Screen - Displays branding and "Get Started" button
  2. Document Selection - Choose document type (if configured)
  3. ID Front Capture - Camera capture for front of ID
  4. ID Back Capture - Camera capture for back of ID
  5. Selfie Capture - Face capture (if required by workflow)
  6. Review - User reviews captured images
  7. Processing - Images submitted for verification
  8. Result - onSuccess or onError callback triggered

Handling Results

Success

onSuccess: { result in
let verificationId = result.verificationId
let status = result.status

// Handle based on status
switch status {
case .success:
print("Verification successful!")
case .pending:
print("Verification pending review")
case .failed:
print("Verification failed")
}
}

Errors

onError: { error in
switch error {
case .invalidJWT(let message):
print("Invalid token: \(message)")
case .networkError(let message):
print("Network error: \(message)")
case .cameraPermissionDenied:
print("Camera permission denied")
case .verificationFailed(let message):
print("Verification failed: \(message)")
default:
print("Error: \(error.localizedDescription)")
}
}

Troubleshooting

Error: "Cannot find 'NafsiSDK' in scope"

Cause: Package not properly added.

Fix:

  1. Go to File → Add Package Dependencies...
  2. Remove the existing NafsiSDK package if present
  3. Re-add using the URL: https://github.com/AdeWang0629/nafsi-ios-sdk.git
  4. Build the project again

Error: "Camera permission denied"

Cause: Missing Info.plist entry.

Fix: Add to Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity verification</string>

Error: "Network request failed" on physical device

Cause: App Transport Security blocking HTTP.

Fix: For development, add to Info.plist:


<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

Note: Remove this in production and use HTTPS.


Support

License

Copyright 2026 Nafsi AI. All rights reserved.