Saltar al contenido principal
Version: Guardian v0.1.0

Android

Integración nativa de Android para el SDK de Surt Guardian, distribuido mediante Maven (GitHub Packages). 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().

Version

Esta guía cubre el SDK v0.3.0, que también corrige la detección de VPN.

Requisitos​

  • Android SDK 24+ (Android 7.0+)
  • Kotlin 1.9+
  • Gradle 8+
  • Java 17

1. Autenticación​

Agrega tu token de acceso al gradle.properties de tu proyecto (o a ~/.gradle/gradle.properties para todos los proyectos):

gradle.properties
SURT_GITHUB_TOKEN=<YOUR_TOKEN>

Reemplaza <YOUR_TOKEN> con el token proporcionado por Surt.

aviso

No confirmes gradle.properties con tokens en el control de versiones. Agrégalo a .gitignore, o usa ~/.gradle/gradle.properties en su lugar.

2. Agregar el repositorio​

En el settings.gradle (o settings.gradle.kts) de tu proyecto, agrega el repositorio Maven de Surt:

settings.gradle
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/surtTech/surt-guardian-sdk")
credentials {
username = "surt-customer"
password = settings.ext.find("SURT_GITHUB_TOKEN")
?: System.getenv("SURT_GITHUB_TOKEN") ?: ""
}
}
}
}
settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/surtTech/surt-guardian-sdk")
credentials {
username = "surt-customer"
password = providers.gradleProperty("SURT_GITHUB_TOKEN").orNull
?: System.getenv("SURT_GITHUB_TOKEN")
}
}
}
}

3. Agregar la dependencia​

En el build.gradle (o build.gradle.kts) de tu app:

build.gradle
dependencies {
implementation 'com.surt.guardian:securitysdk:0.3.0'
}
build.gradle.kts
dependencies {
implementation("com.surt.guardian:securitysdk:0.3.0")
}

Core Library Desugaring​

Si tu app apunta a minSdk < 26, asegúrate de que el core library desugaring esté habilitado en el build.gradle de tu app:

build.gradle
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}

dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}

4. Permisos de AndroidManifest​

Si habilitas la recopilación de ubicación, agrega estos permisos a tu AndroidManifest.xml:

AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Si habilitas la recopilación de información de tarjeta SIM, agrega este permiso:

AndroidManifest.xml
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

El SDK solicita automáticamente el permiso de ubicación durante la verificación cuando collectLocation está habilitado. El cuadro de diálogo de permisos estándar de Android aparece automáticamente; no se necesita código adicional.

5. Inicializar el SDK​

Llama una sola vez al arrancar la app (normalmente en tu clase Application). No se requiere ninguna clave de API en el SDK; tu clave de API del lado del servidor nunca se entrega al dispositivo.

MyApplication.kt
import android.app.Application
import com.surt.guardian.GuardianSDK
import com.surt.guardian.core.Environment
import com.surt.guardian.core.FailurePolicy
import com.surt.guardian.core.GuardianOptions
import com.surt.guardian.utils.Logger

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

GuardianSDK.initialize(
context = this,
options = GuardianOptions(
environment = Environment.Production,
failurePolicy = FailurePolicy.Fail,
logLevel = Logger.Level.WARN,
collectLocation = true // Enable GPS collection (optional)
)
)
}
}

Entornos​

EntornoURL base
Environment.Productionhttps://api.surt.com
Environment.Sandboxhttps://sandbox-api.surt.com

Opciones de recopilación de datos​

OpciónPredeterminadoDescripción
collectLocationfalseCoordenadas GPS - requiere permisos de ubicación en el manifest
collectWifiInfofalseDetalles de red WiFi
collectSimCardInfofalseInformación de SIM/operador - requiere el permiso READ_PHONE_STATE
collectCameraInfofalseCantidad/información de cámara

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.

6. Referencia de Activity (para solicitudes de permisos automáticas)​

Para que el SDK muestre el cuadro de diálogo de permisos, necesita una referencia a la Activity actual. Llama a setActivity() en el ciclo de vida de tu Activity:

MainActivity.kt
override fun onResume() {
super.onResume()
GuardianSDK.getInstance().setActivity(this)
}

override fun onPause() {
super.onPause()
GuardianSDK.getInstance().setActivity(null)
}
nota

Si no llamas a setActivity(), el SDK sigue funcionando pero no puede solicitar permisos automáticamente. Tendrías que solicitar los permisos tú mismo antes de llamar a verify().

7. 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):

Backend preflight request
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:

Preflight response
{
"data": {
"token": "<jwt>"
}
}

Tu app obtiene el JWT desde tu propio endpoint del backend:

Fetch JWT from your backend
suspend fun fetchVerifyJwt(): String {
// Call your OWN backend endpoint, not Surt directly
val response = httpClient.post("https://your-api.com/geolocation-jwt")
return response.body<YourJwtResponse>().jwt
}
Always fetch a fresh JWT per call

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.

8. 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.

Coroutines (recommended)
val jwt = fetchVerifyJwt()

val result = GuardianSDK.getInstance().verifySuspend(jwt = jwt)

if (result.allowed) {
// Proceed with the transaction
} else {
// Handle denied transaction (result.riskLevel has details)
}

O con un callback:

Callback
val jwt = fetchVerifyJwt()

GuardianSDK.getInstance().verify(jwt = jwt) { result ->
result.onSuccess { verification ->
if (verification.allowed) { /* proceed */ }
}
result.onFailure { error ->
// Handle SurtError
}
}

Anulación de ubicación por llamada:

Per-call location override
// Force GPS on for this call, regardless of init default
val result = GuardianSDK.getInstance().verifySuspend(
jwt = jwt,
collectLocation = true
)

9. 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.

Collect payload
// Without JWT - no Surt network calls, no IP in payload
val result = GuardianSDK.getInstance().collectSuspend()

// With JWT - resolves device public IP (GET /geolocation/client-ip),
// embeds it in payload (best-effort)
val jwtForCollect = fetchCollectJwt()
val result = GuardianSDK.getInstance().collectSuspend(jwt = jwtForCollect)

// Send result.payload to your backend
JWT is optional for collect

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​

VerificationResult
data class VerificationResult(
val allowed: Boolean, // Backend decision - true = proceed
val riskLevel: RiskLevel, // LOW / MEDIUM / HIGH / BLOCKED / UNKNOWN
val sessionId: String, // Transaction ID for support reference
val errors: List<String>?, // Backend error messages, if any
val timestamp: Long // Response timestamp (ms)
)

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.

TipoCaso de uso
TransactionType.LOGINInicio de sesión del usuario
TransactionType.SIGN_UPCreación de cuenta nueva
TransactionType.DEPOSITAgregar fondos
TransactionType.WITHDRAWALRetirar fondos

Solución de problemas​

ErrorCausaSolución
"Could not resolve com.surt.guardian:securitysdk"Token no configurado o repositorio no agregadoVerifica gradle.properties y settings.gradle
"401 Unauthorized" desde maven.pkg.github.comToken expirado o inválidoObtén un nuevo token de Surt
.notInitializedverify() llamado antes de initialize()Llama a initialize() en Application.onCreate()
.invalidJwtJWT faltante, malformado o rechazadoAsegúrate de que tu backend genere un JWT fresco por cada llamada
.attestationFailedFalló una verificación de integridad del dispositivo, normalmente un JWT reutilizadoNunca reutilices un JWT entre llamadas a verify()