iOS Integration

iOS Integration

Complete guide to integrating Grantiva's iOS SDK into your application for secure device attestation.

Installation

Swift Package Manager (Recommended)

Add Grantiva to your project using Swift Package Manager:

  1. In Xcode, select File → Add Packages
  2. Enter the repository URL: https://github.com/grantiva/ios-sdk
  3. Select version 1.0.0 or later
  4. Click Add Package

Setup and Configuration

1. Enable App Attest Capability

In your Xcode project:

  1. Select your project in the navigator
  2. Select your app target
  3. Go to Signing & Capabilities
  4. Click + Capability
  5. Add "App Attest"

2. Import and Initialize

import Grantiva
import DeviceCheck

class SecurityManager {
    private let grantiva = Grantiva()
    
    init() {
        // No configuration needed - bundle ID is inferred automatically
    }
}

Basic Implementation

Simple Attestation

func attestDevice() async throws -> AttestationResult {
    do {
        let result = try await grantiva.validateAttestation()
        
        if result.isValid {
            // Store the JWT token for API requests
            UserDefaults.standard.set(result.token, forKey: "attestation_token")
            
            print("Device attested successfully")
            print("Risk Score: \(result.deviceIntelligence.riskScore)")
            print("Jailbreak Detected: \(result.deviceIntelligence.jailbreakDetected)")
            
            return result
        } else {
            throw AttestationError.validationFailed
        }
    } catch {
        print("Attestation failed: \(error)")
        throw error
    }
}

Advanced Implementation with Retry

class AttestationManager {
    private let grantiva = Grantiva()
    private let maxRetries = 3
    
    func attestDeviceWithRetry() async throws -> AttestationResult {
        var lastError: Error?
        
        for attempt in 1...maxRetries {
            do {
                let result = try await grantiva.validateAttestation()
                
                // Cache successful attestation
                await cacheAttestation(result)
                
                return result
            } catch {
                lastError = error
                
                if attempt < maxRetries {
                    // Exponential backoff
                    let delay = TimeInterval(attempt * 2)
                    try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
                }
            }
        }
        
        throw lastError ?? AttestationError.unknownError
    }
    
    private func cacheAttestation(_ result: AttestationResult) async {
        // Store attestation data
        let cache = AttestationCache(
            token: result.token,
            expiresAt: result.expiresAt,
            deviceId: result.deviceIntelligence.deviceId,
            riskScore: result.deviceIntelligence.riskScore
        )
        
        try? await cache.save()
    }
}

Handling Attestation Results

Understanding the Response

struct AttestationResult {
    let isValid: Bool
    let token: String               // JWT for backend authentication
    let expiresAt: Date            // Token expiration
    let deviceIntelligence: DeviceIntelligence
    let permissions: [String]       // Granted permissions
}

struct DeviceIntelligence {
    let deviceId: String
    let riskScore: Int             // 0-100 (lower is better)
    let deviceIntegrity: String    // "high", "medium", "low"
    let jailbreakDetected: Bool
    let attestationCount: Int
    let lastAttestationDate: Date?
}

Using the JWT Token

// Add token to API requests
func makeAuthenticatedRequest() async throws {
    guard let token = UserDefaults.standard.string(forKey: "attestation_token") else {
        throw APIError.notAuthenticated
    }
    
    var request = URLRequest(url: apiURL)
    request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    
    let (data, response) = try await URLSession.shared.data(for: request)
    // Process response...
}

Error Handling

enum AttestationError: LocalizedError {
    case deviceNotSupported
    case attestationNotAvailable
    case networkError
    case validationFailed
    case tokenExpired
    
    var errorDescription: String? {
        switch self {
        case .deviceNotSupported:
            return "This device doesn't support App Attest"
        case .attestationNotAvailable:
            return "App Attest is not available in this region"
        case .networkError:
            return "Network connection error"
        case .validationFailed:
            return "Device attestation validation failed"
        case .tokenExpired:
            return "Attestation token has expired"
        }
    }
}

// Check device support
if !DCAppAttestService.shared.isSupported {
    throw AttestationError.deviceNotSupported
}

Best Practices

  • Cache attestations: Don't re-attest on every app launch
  • Handle failures gracefully: Provide fallback behavior for unsupported devices
  • Refresh tokens before expiry: Check expiration and re-attest proactively
  • Monitor risk scores: Adjust app behavior based on device risk
  • Test on real devices: App Attest doesn't work in simulators

Testing

Important Testing Notes

  • App Attest only works on real devices (iOS 14.0+)
  • Not available in all regions (check Apple's documentation)
  • Requires active internet connection
  • TestFlight builds use Apple's test environment

Next Steps