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:
- Use the official Sigil Auth app — users install once, your backend talks to their device via the Sigil service
- Embed the Sigil SDK in your app — users get authentication inside your app, no separate install
- 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.jsoninapp/ - Register FCM token with Sigil relay
- Handle
MESSAGING_EVENTintent inSigilMessagingService
Debug:
# Check relay registered your token
curl https://relay.sigilauth.com/devices/{fingerprint}
# Expected: {"push_token": "...", "platform": "ios"}
2. Deep Link Handling
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 withsigil://scheme - Handle
Intent.ACTION_VIEWinonCreateoronNewIntent
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/andapp/android/.../ui/
For Path 3 (cli-device):
- Read cli-device documentation
- Set up CI integration with smoke tests
- Use for rapid backend iteration
Architecture deep-dive:
- MPA Setup Guide — multi-party authorization flows
- Self-Hosting Guide — run your own Sigil service
- SDK Reference (Node) and SDK Reference (Go)
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