iOS
Integración nativa de iOS para el SDK de Surt Guardian, distribuido mediante Swift Package Manager. El SDK recopila señales del dispositivo, ejecuta verificaciones de integridad del dispositivo y devuelve la decisión de riesgo del backend.
El SDK nunca maneja una clave de API de Surt. Tu backend maneja la clave sp_live_* (solo del lado del servidor) y la intercambia por un JWT de corta duración que la app pasa a verify().
Esta guía cubre el SDK v0.3.0, que también corrige la detección de VPN.
Requisitos
- iOS 14.0+
- Xcode 14+
- Swift 5.7+
1. Autenticación
Agrega tu token de acceso a ~/.netrc para que Xcode pueda clonar el repositorio privado:
machine github.com
login surt-customer
password <YOUR_TOKEN>
Reemplaza <YOUR_TOKEN> con el token proporcionado por Surt.
Si ~/.netrc no existe, créalo. Asegúrate de que tenga permisos restringidos:
chmod 600 ~/.netrc
2. Agregar el paquete
- En Xcode, ve a File > Add Package Dependencies
- Ingresa la URL del repositorio:
https://github.com/surtTech/surt-guardian-sdk - Establece la regla de versión en "Up to Next Minor" desde 0.3.0
- Haz clic en Add Package
- Selecciona la librería SurtGuardianSDK y agrégala a tu target
3. Configurar permisos en Info.plist
Si habilitas la recopilación de ubicación, agrega esta clave a tu Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location helps verify your identity and protect your account.</string>
Si habilitas la recopilación de información de cámara, agrega esta clave:
<key>NSCameraUsageDescription</key>
<string>Camera access helps verify device authenticity.</string>
El SDK solicita automáticamente el permiso correspondiente durante la verificación cuando la opción de recopilación coincidente está habilitada. El cuadro de diálogo del sistema aparece automáticamente; no se necesita código adicional.
4. Inicializar el SDK
Llama una sola vez al arrancar la app, antes de cualquier otro método del SDK. No se requiere ninguna clave de API en el SDK; tu clave de API del lado del servidor nunca se entrega al dispositivo.
import SurtGuardianSDK
// In your AppDelegate or App init:
GuardianSDK.initialize(
options: GuardianOptions(
environment: .production,
failurePolicy: .fail,
logLevel: .warn,
collectLocation: true // Enable GPS collection (optional)
)
)
Entornos
| Entorno | URL base |
|---|---|
.production | https://api.surt.com |
.sandbox | https://sandbox-api.surt.com |
Opciones de recopilación de datos
| Opción | Predeterminado | Descripción |
|---|---|---|
collectLocation | false | Coordenadas GPS - requiere NSLocationWhenInUseUsageDescription en Info.plist |
collectWifiInfo | false | Detalles de red WiFi |
collectSimCardInfo | false | Información de SIM/operador |
collectCameraInfo | false | Cantidad/información de cámara - requiere NSCameraUsageDescription en Info.plist |
Cuando collectLocation está habilitado, el SDK solicita automáticamente al usuario el permiso de ubicación la primera vez que se llama a verify(). Si el usuario lo deniega, el SDK continúa sin datos GPS; el backend evalúa la transacción con menos confianza.
5. Generar un JWT desde tu backend
Antes de cada llamada a verify(), tu app debe obtener un GeolocationJwt de corta duración desde tu propio backend. Tu backend llama a POST /geolocation/preflight con su clave de API del lado del servidor y el contexto de la transacción, y luego devuelve el JWT a la app.
Ejemplo de llamada del backend (tu servidor, no la app):
POST /geolocation/preflight
Authorization: Bearer sp_live_xxx
Content-Type: application/json
{
"customer_id": "user_abc123",
"transaction_type": "login",
"transaction_name": "User Login",
"name": "John Doe",
"email": "john@example.com"
}
La respuesta del preflight devuelve el token que tu app necesita:
{
"data": {
"token": "<jwt>"
}
}
Tu app obtiene el JWT desde tu propio endpoint del backend:
// In your app - call your OWN backend, not Surt directly
func fetchVerifyJwt() async throws -> String {
let response = try await URLSession.shared.data(
for: URLRequest(url: URL(string: "https://your-api.com/geolocation-jwt")!)
)
// Parse and return the JWT string from your backend response
let body = try JSONDecoder().decode(YourJwtResponse.self, from: response.0)
return body.jwt
}
Obtén siempre un JWT fresco justo antes de llamar a verify(). Cada JWT es de un solo uso; reutilizar uno es rechazado por el backend.
6. Verificar una transacción
Llama en momentos sensibles de seguridad (inicio de sesión, pago, etc.). Obtén un JWT de tu backend justo antes de cada llamada.
let jwt = try await fetchVerifyJwt()
let result = try await GuardianSDK.shared.verify(jwt: jwt)
if result.allowed {
// Proceed with the transaction
} else {
// Handle denied transaction (result.riskLevel has details)
}
O con un completion handler (iOS 14+):
let jwt = try await fetchVerifyJwt()
GuardianSDK.shared.verify(jwt: jwt) { result in
switch result {
case .success(let verification):
if verification.allowed { /* proceed */ }
case .failure(let error):
// Handle SurtError
}
}
Anulación de ubicación por llamada:
// Force GPS on for this call, regardless of init default
let result = try await GuardianSDK.shared.verify(
jwt: jwt,
collectLocation: true
)
7. Collect (Servidor a servidor, opcional)
collect() devuelve un payload cifrado para que tu backend lo reenvíe a Surt. No ocurren llamadas directas del SDK a Surt durante collect.
// Without JWT - no Surt network calls, no IP in payload
let result = try await GuardianSDK.shared.collect()
// With JWT - resolves device public IP (GET /geolocation/client-ip),
// embeds it in payload (best-effort)
let jwtForCollect = try await fetchCollectJwt()
let result = try await GuardianSDK.shared.collect(jwt: jwtForCollect)
// Send result.payload to your backend
El JWT para collect() es opcional. Pásalo solo para incrustar la IP pública del dispositivo. A diferencia de verify(), un JWT para collect() puede reutilizarse u omitirse: collect() genera su propio nonce internamente y no realiza ninguna llamada de red a Surt cuando no se proporciona un JWT.
Resultado de la verificación
struct VerificationResult {
let allowed: Bool // Backend decision - true = proceed
let riskLevel: RiskLevel // .low / .medium / .high / .blocked / .unknown
let sessionId: String // Transaction ID for support reference
let errors: [String]? // Backend error messages, if any
let timestamp: TimeInterval // Response timestamp
}
Tipos de transacción
Estos valores los establece tu backend en el campo transaction_type del preflight. No se pasan a verify() en la app.
| Tipo | Caso de uso |
|---|---|
.login | Inicio de sesión del usuario |
.signUp | Creación de cuenta nueva |
.deposit | Agregar fondos |
.withdrawal | Retirar fondos |
Solución de problemas
| Error | Causa | Solución |
|---|---|---|
| "Package resolution failed" | Token no configurado o expirado | Verifica que ~/.netrc tenga el token correcto |
.notInitialized | verify() llamado antes de initialize() | Llama a initialize() al arrancar la app |
.invalidJwt | JWT faltante, malformado o rechazado | Asegúrate de que tu backend genere un JWT fresco por cada llamada |
.attestationFailed | Falló una verificación de integridad del dispositivo, normalmente un JWT reutilizado | Nunca reutilices un JWT entre llamadas a verify() |