iOS
Integração nativa iOS para o Surt Guardian SDK, distribuída via Swift Package Manager. O SDK coleta sinais do dispositivo, executa verificações de integridade do dispositivo e retorna a decisão de risco do backend.
O SDK nunca guarda uma chave de API da Surt. Seu backend guarda a chave sp_live_* (somente no lado do servidor) e a troca por um JWT de curta duração que o app passa para verify().
Este guia cobre o SDK v0.3.0, que também corrige a detecção de VPN.
Requisitos
- iOS 14.0+
- Xcode 14+
- Swift 5.7+
1. Autenticação
Adicione seu token de acesso ao ~/.netrc para que o Xcode possa clonar o repositório privado:
machine github.com
login surt-customer
password <YOUR_TOKEN>
Substitua <YOUR_TOKEN> pelo token fornecido pela Surt.
Se o ~/.netrc não existir, crie-o. Certifique-se de que ele tenha permissões restritas:
chmod 600 ~/.netrc
2. Adicionar o Pacote
- No Xcode, vá para File > Add Package Dependencies
- Insira a URL do repositório:
https://github.com/surtTech/surt-guardian-sdk - Defina a regra de versão como "Up to Next Minor" a partir de 0.3.0
- Clique em Add Package
- Selecione a biblioteca SurtGuardianSDK e adicione-a ao seu target
3. Configurar as Permissões do Info.plist
Se você habilitar a coleta de localização, adicione esta chave ao seu Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location helps verify your identity and protect your account.</string>
Se você habilitar a coleta de informações da câmera, adicione esta chave:
<key>NSCameraUsageDescription</key>
<string>Camera access helps verify device authenticity.</string>
O SDK solicita automaticamente a permissão relevante durante a verificação quando a opção de coleta correspondente está habilitada. A caixa de diálogo do sistema aparece automaticamente - nenhum código adicional é necessário.
4. Inicializar o SDK
Chame uma vez na inicialização do app, antes de qualquer outro método do SDK. Nenhuma chave de API é necessária no SDK - sua chave de API do lado do servidor nunca chega ao dispositivo.
import SurtGuardianSDK
// No seu AppDelegate ou no init do App:
GuardianSDK.initialize(
options: GuardianOptions(
environment: .production,
failurePolicy: .fail,
logLevel: .warn,
collectLocation: true // Habilita a coleta de GPS (opcional)
)
)
Ambientes
| Ambiente | URL Base |
|---|---|
.production | https://api.surt.com |
.sandbox | https://sandbox-api.surt.com |
Opções de Coleta de Dados
| Opção | Padrão | Descrição |
|---|---|---|
collectLocation | false | Coordenadas GPS - requer NSLocationWhenInUseUsageDescription no Info.plist |
collectWifiInfo | false | Detalhes de rede WiFi |
collectSimCardInfo | false | Informações de SIM/operadora |
collectCameraInfo | false | Contagem/informações da câmera - requer NSCameraUsageDescription no Info.plist |
Quando collectLocation está habilitado, o SDK solicita automaticamente ao usuário a permissão de localização na primeira vez que verify() é chamado. Se o usuário negar, o SDK continua sem dados de GPS - o backend pontua a transação com menos confiança.
5. Gerar um JWT a Partir do Seu Backend
Antes de cada chamada verify(), seu app deve obter um GeolocationJwt de curta duração do seu próprio backend. Seu backend chama POST /geolocation/preflight com sua chave de API do lado do servidor e o contexto da transação, e então retorna o JWT para o app.
Exemplo de chamada do backend (seu servidor, não o 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"
}
A resposta do preflight retorna o token que seu app precisa:
{
"data": {
"token": "<jwt>"
}
}
Seu app obtém o JWT a partir do endpoint do seu próprio backend:
// No seu app - chame o SEU PRÓPRIO backend, não a Surt diretamente
func fetchVerifyJwt() async throws -> String {
let response = try await URLSession.shared.data(
for: URLRequest(url: URL(string: "https://your-api.com/geolocation-jwt")!)
)
// Faça o parse e retorne a string do JWT da resposta do seu backend
let body = try JSONDecoder().decode(YourJwtResponse.self, from: response.0)
return body.jwt
}
Sempre obtenha um JWT novo imediatamente antes de chamar verify(). Cada JWT é de uso único; reutilizar um JWT é rejeitado pelo backend.
6. Verificar uma Transação
Chame em momentos sensíveis à segurança (login, pagamento, etc.). Obtenha um JWT do seu backend imediatamente antes de cada chamada.
let jwt = try await fetchVerifyJwt()
let result = try await GuardianSDK.shared.verify(jwt: jwt)
if result.allowed {
// Prosseguir com a transação
} else {
// Tratar transação negada (result.riskLevel tem os detalhes)
}
Ou com um 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 { /* prosseguir */ }
case .failure(let error):
// Tratar SurtError
}
}
Substituição de localização por chamada:
// Forçar GPS ligado para esta chamada, independentemente do padrão de inicialização
let result = try await GuardianSDK.shared.verify(
jwt: jwt,
collectLocation: true
)
7. Collect (Servidor para Servidor, Opcional)
collect() retorna um payload criptografado para o seu backend encaminhar à Surt. Nenhuma chamada direta do SDK para a Surt acontece durante o collect.
// Sem JWT - nenhuma chamada de rede para a Surt, sem IP no payload
let result = try await GuardianSDK.shared.collect()
// Com JWT - resolve o IP público do dispositivo (GET /geolocation/client-ip),
// e o embute no payload (melhor esforço)
let jwtForCollect = try await fetchCollectJwt()
let result = try await GuardianSDK.shared.collect(jwt: jwtForCollect)
// Envie result.payload para o seu backend
O JWT para collect() é opcional. Passe-o apenas para embutir o IP público do dispositivo. Diferente de verify(), um JWT para collect() pode ser reutilizado ou omitido - collect() não exige um JWT por chamada e não faz nenhuma chamada de rede à Surt quando nenhum JWT é fornecido.
Resultado da Verificação
struct VerificationResult {
let allowed: Bool // Decisão do backend - true = prosseguir
let riskLevel: RiskLevel // .low / .medium / .high / .blocked / .unknown
let sessionId: String // ID da transação para referência de suporte
let errors: [String]? // Mensagens de erro do backend, se houver
let timestamp: TimeInterval // Timestamp da resposta
}
Tipos de Transação
Estes valores são definidos pelo seu backend no campo transaction_type do preflight. Eles não são passados para verify() no app.
| Tipo | Caso de uso |
|---|---|
.login | Login do usuário |
.signUp | Criação de nova conta |
.deposit | Adição de fundos |
.withdrawal | Saque de fundos |
Solução de Problemas
| Erro | Causa | Solução |
|---|---|---|
| "Package resolution failed" | Token não configurado ou expirado | Verifique se o ~/.netrc tem o token correto |
.notInitialized | verify() chamado antes de initialize() | Chame initialize() na inicialização do app |
.invalidJwt | JWT ausente, malformado ou rejeitado | Garanta que seu backend gere um JWT novo a cada chamada |
.attestationFailed | Falha na verificação de integridade do dispositivo - geralmente um JWT reutilizado | Nunca reutilize um JWT entre chamadas verify() |