intermediate mobileiosandroidintegrationsdk

Mobile-First Integration Guide

Add Sigil Auth to your iOS/Android app — three integration paths

Mobile-First Integration Guide

You’re building an iOS or Android app and want to replace passwords with hardware-backed authentication. This guide shows three integration paths, from easiest to most integrated.

Choose your path:

  1. Use the official Sigil Auth app — users install once, your backend talks to their device via the Sigil service
  2. Embed the Sigil SDK in your app — users get authentication inside your app, no separate install
  3. Test with cli-device first — validate your backend integration before mobile work begins

Path 1: Official Sigil Auth App (Easiest)

Story: Your users install the Sigil Auth mobile app from TestFlight (iOS) or Play Store (Android). Your app triggers authentication via deep links or QR codes. The Sigil app handles key management, biometric prompts, and challenge signing.

How It Works

┌─────────────────┐
│  Your Mobile    │
│  App            │──┐
└─────────────────┘  │
                     │ 1. User taps "Login"

┌─────────────────────────────┐
│  Your Backend               │
│  (Node/Go SDK)              │──┐
└─────────────────────────────┘  │
                                 │ 2. Create challenge
                                 │ 3. Send push via Sigil service

                          ┌──────────────────┐
                          │  Sigil Auth App  │
                          │  (user's device) │
                          └──────────────────┘
                                 │ 4. Biometric prompt
                                 │ 5. Sign challenge

┌─────────────────────────────┐
│  Your Backend               │ 6. Verify signature
│  Authenticate user          │ 7. Return session token
└─────────────────────────────┘

Backend Integration

1. Install SDK

# Node
npm install @sigilauth/sdk

# Go
go get github.com/sigilauth/server/sdk-go

2. Initialize Client

// Node
import { SigilClient } from '@sigilauth/sdk';

const sigil = new SigilClient({
  serverUrl: 'https://sigil.yourcompany.com',
  apiKey: process.env.SIGIL_API_KEY
});
// Go
import "github.com/sigilauth/server/sdk-go"

client := sigil.NewClient(sigil.Config{
  ServerURL: "https://sigil.yourcompany.com",
  APIKey:    os.Getenv("SIGIL_API_KEY"),
})

3. Register Device (First-Time)

User scans QR code or enters 8-digit pairing code:

// Generate pairing code
const { pairingCode, deviceId } = await sigil.createPairingCode(userId);

// Display QR code to user
const qrData = `sigil://register?code=${pairingCode}&server=${serverUrl}`;
// Or show 8-digit code: pairingCode

4. Authenticate User

// Create challenge
const challenge = await sigil.createChallenge({
  deviceId: user.sigilDeviceId,
  action: 'login',
  metadata: { ip: req.ip, userAgent: req.headers['user-agent'] }
});

// Challenge automatically pushed to device via APNs/FCM
// Wait for signature...

// Poll or use webhook
const result = await sigil.waitForResponse(challenge.id, { timeout: 30000 });

if (result.verified) {
  // User authenticated
  const session = createSession(user);
  return { token: session.token };
} else {
  // Denied or timeout
  throw new AuthError('Authentication failed');
}

Mobile App Integration

Your app needs minimal changes:

iOS (SwiftUI):

import UIKit

Button("Login with Sigil") {
    // Backend returns pairing code or deep link
    let url = URL(string: "sigil://challenge?id=\(challengeId)&server=\(serverUrl)")!
    UIApplication.shared.open(url)
}

Android (Compose):

Button(onClick = {
    // Backend returns pairing code or deep link
    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse("sigil://challenge?id=$challengeId&server=$serverUrl")
    }
    context.startActivity(intent)
}) {
    Text("Login with Sigil")
}

Pros & Cons

✓ Pros:

  • Zero crypto code in your app
  • Users authenticate across multiple apps with one device
  • Automatic updates to auth app (security fixes, new features)
  • Smallest integration surface

✗ Cons:

  • Users must install separate app
  • Deep link handoff (slight UX friction)
  • Requires Sigil Auth app to be running/backgrounded

Path 2: Embedded SDK (Most Integrated)

Story: You embed Sigil Auth directly in your app. Users’ keys live in your app’s Secure Enclave (iOS) or StrongBox (Android). Authentication happens in-app with biometric prompts — no separate app needed.

How It Works

┌──────────────────────────────┐
│  Your Mobile App             │
│  + SigilAuthCore SDK         │
│                              │
│  ┌────────────────────────┐  │
│  │ User taps "Login"      │  │
│  └────────────────────────┘  │
│           ↓                  │
│  ┌────────────────────────┐  │
│  │ Biometric prompt       │  │
│  │ (Face ID / Fingerprint)│  │
│  └────────────────────────┘  │
│           ↓                  │
│  ┌────────────────────────┐  │
│  │ Sign challenge         │  │
│  │ (Secure Enclave)       │  │
│  └────────────────────────┘  │
│           ↓                  │
│  Send signature to backend   │
└──────────────────────────────┘

iOS Integration

1. Add SigilAuthCore to your Xcode project

Via Swift Package Manager:

// Package.swift
dependencies: [
    .package(url: "https://github.com/sigilauth/app", from: "0.1.0")
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "SigilAuthCore", package: "app")
        ]
    )
]

2. Initialize on first launch

import SigilAuthCore

class AuthManager: ObservableObject {
    private let crypto = CryptoService()
    private let keychain = KeychainService()
    
    func setupDevice() async throws {
        // Generate keypair in Secure Enclave
        let keypair = try await crypto.generateKeypair()
        let fingerprint = crypto.deriveFingerprint(publicKey: keypair.publicKey)
        
        // Register with your backend
        let response = try await registerDevice(
            publicKey: keypair.publicKey,
            fingerprint: fingerprint
        )
        
        // Save device ID
        try keychain.saveDeviceId(response.deviceId)
    }
}

3. Authenticate

func authenticate(challenge: String) async throws -> String {
    // Prompt for biometric
    let context = LAContext()
    guard try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, 
                                           localizedReason: "Authenticate to login") else {
        throw AuthError.biometricFailed
    }
    
    // Sign challenge
    let signature = try await crypto.sign(challenge: challenge)
    
    // Send to backend
    let response = try await submitSignature(signature)
    return response.sessionToken
}

Android Integration

1. Add SigilAuthCore to your app

// build.gradle.kts
dependencies {
    implementation("com.sigilauth:sigilauth-android:0.1.0")
}

2. Initialize on first launch

import com.sigilauth.core.CryptoManager
import com.sigilauth.core.KeystoreManager

class AuthRepository(context: Context) {
    private val keystore = KeystoreManager(context)
    private val crypto = CryptoManager(keystore)
    
    suspend fun setupDevice() {
        // Generate keypair in Android Keystore/StrongBox
        val keypair = keystore.generateKeypair()
        val fingerprint = crypto.deriveFingerprint(keypair.public)
        
        // Register with backend
        val response = api.registerDevice(
            publicKey = keypair.public.encoded.toBase64(),
            fingerprint = fingerprint
        )
        
        // Save device ID
        prefs.deviceId = response.deviceId
    }
}

3. Authenticate

suspend fun authenticate(challenge: String): String {
    // Prompt for biometric
    val biometricPrompt = BiometricPrompt(
        activity,
        executor,
        object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                // Sign challenge
                val signature = crypto.sign(challenge)
                
                // Send to backend
                val response = api.submitSignature(signature)
                sessionToken = response.token
            }
        }
    )
    
    val promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Authenticate")
        .setSubtitle("Confirm your identity")
        .setNegativeButtonText("Cancel")
        .build()
    
    biometricPrompt.authenticate(promptInfo)
}

Backend Integration

Same as Path 1, but you call sigil.verifySignature() directly instead of waiting for push:

// User submits signature from mobile app
const verified = await sigil.verifySignature({
  deviceId: req.body.deviceId,
  challenge: req.body.challenge,
  signature: req.body.signature
});

if (verified) {
  return { token: createSession(user).token };
}

Pros & Cons

✓ Pros:

  • Seamless in-app UX (no deep links)
  • No separate app install
  • Full control over biometric prompts and UI
  • Works offline (challenge/response can be queued)

✗ Cons:

  • More code to maintain in your app
  • Crypto implementation surface (audit SigilAuthCore)
  • Users have separate keys per app (not shared identity)
  • Must handle key lifecycle (backup, recovery, device migration)

Path 3: Test with cli-device First (Backend Validation)

Story: You’re a backend developer. Mobile app isn’t ready yet. You want to validate your Sigil integration without waiting for mobile builds.

cli-device is a command-line tool that acts like a mobile device. Use it to:

  • Test challenge/response flows
  • Verify signature validation
  • Simulate multi-party authorization (MPA)
  • Automate CI smoke tests

Installation

# Install from source (requires Go 1.23+)
go install github.com/sigilauth/cli-device/cmd/sigil-device@latest

# Or download pre-built binary
curl -LO https://github.com/sigilauth/cli-device/releases/download/v0.1.0/sigil-device_darwin_amd64.tar.gz
tar -xzf sigil-device_darwin_amd64.tar.gz
sudo mv sigil-device /usr/local/bin/

Quick Test Flow

1. Initialize device

sigil-device init
# Output: Fingerprint: a1b2c3d4... | Mnemonic saved to ~/.sigil-device/mnemonic.txt

2. Pair with your server

sigil-device pair https://sigil.yourcompany.com
# Output: Paired with server | Device ID: dev_abc123

3. Start listening for challenges

sigil-device listen
# Listening for authentication requests...

4. Trigger auth from your app/backend

# In another terminal, trigger login flow
curl -X POST https://yourapp.com/api/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{"userId": "test@example.com"}'

5. Watch cli-device auto-respond

✓ Challenge received: login attempt
  Server: sigil.yourcompany.com
  Action: login
  Metadata: {"ip": "192.168.1.100"}
  
✓ Signature sent
✓ Authentication successful

6. Verify backend received valid signature

# Check your app's response
# Expected: session token or success message

Use in CI/CD

GitHub Actions example:

name: Sigil Auth Integration Test

on: [push, pull_request]

jobs:
  test-auth:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install cli-device
        run: |
          curl -LO https://github.com/sigilauth/cli-device/releases/download/v0.1.0/sigil-device_linux_amd64.tar.gz
          tar -xzf sigil-device_linux_amd64.tar.gz
          sudo mv sigil-device /usr/local/bin/
      
      - name: Start Sigil service
        run: docker-compose up -d
      
      - name: Initialize device
        run: sigil-device init
      
      - name: Pair device
        run: sigil-device pair http://localhost:8443
      
      - name: Start listener (background)
        run: sigil-device listen &
      
      - name: Run integration tests
        run: npm test
      
      - name: Verify auth flow
        run: |
          curl -X POST http://localhost:3000/api/auth/challenge \
            -d '{"userId": "test@example.com"}' | jq .token

Pros & Cons

✓ Pros:

  • Test backend integration immediately (no mobile build required)
  • Automate smoke tests in CI
  • Simulate edge cases (network failures, timeouts, rejections)
  • Fast iteration (no app rebuild/reinstall)

✗ Cons:

  • Not a real mobile device (can’t test APNs/FCM push, biometric UX)
  • Manual pairing (not QR scan)
  • CLI-only (no visual confirmation of pictograms)

Common Pitfalls

1. Push Notification Setup

Problem: Challenges sent but device never receives them.

iOS Fix:

  • Enable Push Notifications capability in Xcode
  • Add APNs entitlement to provisioning profile
  • Register for remote notifications in app delegate
  • Send APNs device token to Sigil relay via POST /relay/register

Android Fix:

  • Add Firebase Cloud Messaging dependency
  • Include google-services.json in app/
  • Register FCM token with Sigil relay
  • Handle MESSAGING_EVENT intent in SigilMessagingService

Debug:

# Check relay registered your token
curl https://relay.sigilauth.com/devices/{fingerprint}
# Expected: {"push_token": "...", "platform": "ios"}

Problem: Tapping “Login with Sigil” does nothing or opens browser.

iOS Fix:

  • Add URL scheme to Info.plist: sigil://
  • Implement application(_:open:options:) in AppDelegate or SceneDelegate
  • Parse URL and extract challenge ID

Android Fix:

  • Add <intent-filter> to MainActivity with sigil:// scheme
  • Handle Intent.ACTION_VIEW in onCreate or onNewIntent

Test deep link:

# iOS (simulator)
xcrun simctl openurl booted "sigil://challenge?id=ch_abc123&server=https://sigil.yourcompany.com"

# Android (device/emulator)
adb shell am start -a android.intent.action.VIEW -d "sigil://challenge?id=ch_abc123&server=https://sigil.yourcompany.com"

3. Biometric Prompt Timing

Problem: Biometric prompt appears before user sees what they’re approving.

Fix:

  • Show challenge details FIRST (server name, action, metadata)
  • Display server’s pictogram for visual verification
  • THEN trigger biometric prompt
  • Never prompt immediately on challenge receipt

Good UX flow:

1. Challenge arrives
2. Show approval screen:
   ┌─────────────────────────┐
   │ Approve Login?          │
   │                         │
   │ Server: acme.com        │
   │ 🍎 🍌 ✈️ 🚗 🐕         │
   │                         │
   │ Action: Login           │
   │ IP: 192.168.1.100       │
   │                         │
   │ [Approve] [Deny]        │
   └─────────────────────────┘
3. User taps "Approve"
4. NOW trigger biometric
5. Sign challenge after biometric succeeds

4. Mnemonic Backup Reminder

Problem: User loses device, can’t recover keys, locked out.

Fix:

  • Prompt user to write down mnemonic on first launch
  • Show pictogram + fingerprint for verification
  • Offer “Verify backup” flow (user re-enters mnemonic)
  • Never store mnemonic in cloud backup (exclude from iCloud/Google Backup)

iOS:

// Exclude mnemonic file from backup
var url = mnemonicFileURL
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try url.setResourceValues(resourceValues)

Android:

// Use `no_backup` directory
val mnemonicFile = File(context.noBackupFilesDir, "mnemonic.txt")

5. Testing on Physical Devices

Problem: Secure Enclave (iOS) and StrongBox (Android) don’t work in simulators/emulators.

Fix:

  • Use real devices for crypto testing
  • Simulators can test UI/navigation but keys will use software fallback
  • CI: use cli-device for automated testing (no hardware required)

Emulator workaround (Android only):

// Detect emulator and use software keystore
val isEmulator = Build.FINGERPRINT.contains("generic")
val keystoreProvider = if (isEmulator) "AndroidKeyStore" else "AndroidKeyStore" // StrongBox auto-selected if available

Next Steps

For Path 1 (Official App):

  • Read Integrator Quickstart for backend SDK usage
  • Set up push relay with APNs/FCM credentials
  • Test deep link handling in your app

For Path 2 (Embedded SDK):

  • Clone app repo: git clone https://github.com/sigilauth/app
  • Review iOS SDK: app/ios/Sources/Core/
  • Review Android SDK: app/android/app/src/main/kotlin/com/sigilauth/core/
  • Check mobile UI implementation: app/ios/Sources/UI/ and app/android/.../ui/

For Path 3 (cli-device):

Architecture deep-dive:


Support

Issues: https://github.com/sigilauth/app/issues
Discussions: https://github.com/sigilauth/project/discussions
Email: support@sigilauth.com

License: AGPL-3.0 (copyleft) — API specifications Apache-2.0