📲 Fluxo de Troca de Dispositivo
Este guia apresenta o fluxo para autorizar o acesso de um usuário em um novo dispositivo.
📋 Visão Geral
Quando um usuário precisa acessar sua conta em um novo dispositivo, é necessário validar a segurança da operação através de verificações adicionais.
Este fluxo é ativado automaticamente quando o sistema detecta:
- Login de um dispositivo não reconhecido
- Primeiro acesso após troca/reset de dispositivo
- Dispositivo diferente do último login registrado
URLs Base:
Autenticação (Passo 1 — Login):
- Homologação:
https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect - Produção:
https://ssoprd.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect
Identificação (Passos 2+):
- Homologação:
https://apihml.credsystem.com.br/api/v1 - Produção:
https://api.credsystem.com.br/api/v1
- 🔧 Guia de Troubleshooting - Solução de problemas e erros comuns
- 📖 Especificação OpenAPI — Referência técnica completa (schemas, tipos, exemplos)
1️⃣ Fazer Login no Novo Dispositivo
Realize o login normalmente com as credenciais do usuário.
Endpoint
POST https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
Exemplos de Código
- cURL
- JavaScript
- Python
- Java
- C#
- PHP
- Go
curl -X POST 'https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=YOUR_CLIENT_ID' \
-d 'client_secret=YOUR_CLIENT_SECRET' \
-d 'username=12345678909' \
-d 'password=SenhaDoUsuario' \
-d 'grant_type=password' \
-d 'infosAdicionais={"dispositivoId":"novo-device-456","modelo":"iPhone 14 Pro","idSistemaOperacional":"1","versaoSistemaOperacional":"17.2","versaoApp":"2.1.0"}'
async function loginNovoDispositivo(cpf, senha, novoDispositivo) {
const response = await fetch(
'https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
username: cpf,
password: senha,
grant_type: 'password',
infosAdicionais: JSON.stringify({
dispositivoId: novoDispositivo.id,
modelo: novoDispositivo.modelo,
idSistemaOperacional: novoDispositivo.idSO,
versaoSistemaOperacional: novoDispositivo.versaoSO,
versaoApp: novoDispositivo.versaoApp
})
})
}
);
return await response.json();
}
import requests, json
def login_novo_dispositivo(cpf, senha, novo_dispositivo):
url = 'https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token'
data = {
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET',
'username': cpf,
'password': senha,
'grant_type': 'password',
'infosAdicionais': json.dumps({
'dispositivoId': novo_dispositivo['id'],
'modelo': novo_dispositivo['modelo'],
'idSistemaOperacional': novo_dispositivo['idSO'],
'versaoSistemaOperacional': novo_dispositivo['versaoSO'],
'versaoApp': novo_dispositivo['versaoApp']
})
}
response = requests.post(url, data=data)
response.raise_for_status()
return response.json()
import java.net.http.*;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
Map<String, String> params = Map.of(
"client_id", "YOUR_CLIENT_ID",
"client_secret", "YOUR_CLIENT_SECRET",
"username", cpf, "password", senha,
"grant_type", "password",
"infosAdicionais", "{\"dispositivoId\":\"novo-device-456\"}"
);
String form = params.entrySet().stream()
.map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8)
+ "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.reduce("", (a, b) -> a.isEmpty() ? b : a + "&" + b);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
using System.Net.Http;
var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string> {
["client_id"] = "YOUR_CLIENT_ID",
["client_secret"] = "YOUR_CLIENT_SECRET",
["username"] = cpf,
["password"] = senha,
["grant_type"] = "password",
["infosAdicionais"] = "{\"dispositivoId\":\"novo-device-456\"}"
});
var response = await client.PostAsync(
"https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token",
content
);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
$data = http_build_query([
'client_id' => 'YOUR_CLIENT_ID',
'client_secret' => 'YOUR_CLIENT_SECRET',
'username' => $cpf,
'password' => $senha,
'grant_type' => 'password',
'infosAdicionais' => json_encode(['dispositivoId' => 'novo-device-456'])
]);
$ch = curl_init('https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
import (
"encoding/json"
"net/http"
"net/url"
"strings"
)
infos, _ := json.Marshal(map[string]string{"dispositivoId": "novo-device-456", "modelo": modelo})
data := url.Values{
"client_id": {"YOUR_CLIENT_ID"},
"client_secret": {"YOUR_CLIENT_SECRET"},
"username": {cpf},
"password": {senha},
"grant_type": {"password"},
"infosAdicionais": {string(infos)},
}
resp, _ := http.PostForm(
"https://ssohml.credsystem.com.br/auth/realms/{realm}/protocol/openid-connect/token",
data,
)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
Resposta - Dispositivo Não Reconhecido
Quando o sistema detecta um novo dispositivo, retorna um token de sessão temporário:
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 900,
"requires_device_validation": true,
"validation_methods": ["SMS", "BIOMETRIA"]
}
O flag requires_device_validation: true indica que validações adicionais são necessárias antes do acesso completo.
Consulte o 🔧 Troubleshooting - Obter Token para detalhes de erros e soluções.
2️⃣ Solicitar Link de Biometria
Solicite o link de captura biométrica que será aberto no WebView do aplicativo para validação facial do usuário.
O link retornado por este endpoint deve ser integrado via WebView usando o Motor de Biometria Credsystem. Consulte a documentação por plataforma:
Acesse a Visão Geral do Motor de Biometria para entender o fluxo completo de captura.
Endpoint
POST {baseUrl}/identificacao/biometria
Authorization: Bearer {token-sessao-cliente}
Content-Type: application/json
Request Body
{
"consumidor": "CONSUMIDOR_ID",
"loja": 123456
}
Exemplos de Código
- cURL
- JavaScript
- Python
- Java
- C#
- PHP
- Go
curl -X POST "{baseUrl}/identificacao/biometria" \
-H "Authorization: Bearer {token-sessao-cliente}" \
-H "Content-Type: application/json" \
-d '{"consumidor": "CONSUMIDOR_ID", "loja": 123456}'
async function solicitarBiometriaNovoDispositivo(sessionToken, consumidorId, lojaId) {
const response = await fetch(`${baseUrl}/identificacao/biometria`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ consumidor: consumidorId, loja: lojaId })
});
if (!response.ok) throw new Error('Erro ao solicitar biometria');
const { link } = await response.json();
return link;
}
import requests
def solicitar_biometria_novo_dispositivo(base_url, session_token, consumidor_id, loja_id):
url = f"{base_url}/identificacao/biometria"
headers = {"Authorization": f"Bearer {session_token}", "Content-Type": "application/json"}
response = requests.post(url, json={"consumidor": consumidor_id, "loja": loja_id}, headers=headers)
response.raise_for_status()
return response.json()["link"]
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.http.*;
import java.net.URI;
import java.util.Map;
String body = new ObjectMapper()
.writeValueAsString(Map.of("consumidor", consumidorId, "loja", lojaId));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/identificacao/biometria"))
.header("Authorization", "Bearer " + sessionToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
using System.Net.Http.Json;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", sessionToken);
var response = await client.PostAsJsonAsync(
$"{baseUrl}/identificacao/biometria",
new { consumidor = consumidorId, loja = lojaId }
);
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadFromJsonAsync<JsonElement>();
var link = data.GetProperty("link").GetString();
$ch = curl_init("{$baseUrl}/identificacao/biometria");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$sessionToken}",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'consumidor' => $consumidorId, 'loja' => $lojaId
]));
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
$link = $data['link'];
import (
"bytes"
"encoding/json"
"net/http"
)
payload := map[string]interface{}{"consumidor": consumidorId, "loja": lojaId}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/identificacao/biometria", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+sessionToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := (&http.Client{}).Do(req)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
link := result["link"].(string)
Abra o link retornado em um WebView e aguarde o callback de conclusão da captura biométrica antes de avançar para o próximo passo do fluxo.
Consulte o 🔧 Troubleshooting - Solicitar Biometria para detalhes de erros e soluções.
3️⃣ Enviar Token SMS
Solicite o envio de um código de verificação por SMS para o telefone cadastrado.
Endpoint
POST {baseUrl}/identificacao/token-sms/envio
Authorization: Bearer {token-sessao-cliente}
Exemplos de Código
- cURL
- JavaScript
- Python
- Java
- C#
- PHP
- Go
curl -X POST "{baseUrl}/identificacao/token-sms/envio" \
-H "Authorization: Bearer {token-sessao-cliente}"
async function enviarSMSTrocaDispositivo(sessionToken) {
const response = await fetch(`${baseUrl}/identificacao/token-sms/envio`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${sessionToken}` }
});
if (!response.ok) throw new Error('Erro ao enviar SMS');
return await response.json();
}
import requests
def enviar_sms_troca_dispositivo(base_url, session_token):
url = f"{base_url}/identificacao/token-sms/envio"
headers = {"Authorization": f"Bearer {session_token}"}
response = requests.post(url, headers=headers)
response.raise_for_status()
return response.json()
import java.net.http.*;
import java.net.URI;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/identificacao/token-sms/envio"))
.header("Authorization", "Bearer " + sessionToken)
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", sessionToken);
var response = await client.PostAsync(
$"{baseUrl}/identificacao/token-sms/envio", null
);
response.EnsureSuccessStatusCode();
$ch = curl_init("{$baseUrl}/identificacao/token-sms/envio");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$sessionToken}"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '');
$response = curl_exec($ch);
curl_close($ch);
import "net/http"
req, _ := http.NewRequest("POST", baseURL+"/identificacao/token-sms/envio", nil)
req.Header.Set("Authorization", "Bearer "+sessionToken)
resp, _ := (&http.Client{}).Do(req)
defer resp.Body.Close()
- Código de 6 dígitos
- Válido por 5 minutos
- Enviado para o telefone cadastrado
- Uso único
Consulte o 🔧 Troubleshooting - Enviar SMS para detalhes de erros e soluções.
4️⃣ Validar Token SMS
Valide o código SMS recebido pelo usuário.
Endpoint
POST {baseUrl}/identificacao/token-sms
Authorization: Bearer {token-sessao-cliente}
Content-Type: application/json
Request Body
{
"pinCode": "123456",
"analise": {
"userID": "12345678909",
"origem": "APP_NAME",
"deviceID": "novo-device-456",
"integrationName": "app-login-pl",
"dadosMaquina": {
"ip": "192.168.0.1",
"fingerPrint": "{fingerprint}"
}
}
}
Parâmetros
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
pinCode | string | ✅ | Código de 6 dígitos recebido por SMS |
analise.userID | string | ✅ | CPF do usuário |
analise.origem | string | ✅ | Nome do aplicativo |
analise.deviceID | string | ✅ | ID do novo dispositivo |
analise.integrationName | string | ✅ | Nome da integração |
analise.dadosMaquina.ip | string | ✅ | Endereço IP do novo dispositivo |
analise.dadosMaquina.fingerPrint | string | ✅ | Fingerprint do novo dispositivo |
Exemplos de Código
- cURL
- JavaScript
- Python
- Java
- C#
- PHP
- Go
curl -X POST "{baseUrl}/identificacao/token-sms" \
-H "Authorization: Bearer {token-sessao-cliente}" \
-H "Content-Type: application/json" \
-d '{
"pinCode": "123456",
"analise": {
"userID": "12345678909",
"origem": "APP_NAME",
"deviceID": "novo-device-456",
"integrationName": "app-login-pl",
"dadosMaquina": {"ip": "192.168.0.1", "fingerPrint": "{fingerprint}"}
}
}'
async function validarSMSNovoDispositivo(sessionToken, pinCode, novoDispositivo) {
const response = await fetch(`${baseUrl}/identificacao/token-sms`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
pinCode,
analise: {
userID: novoDispositivo.cpf,
origem: 'meu-app-private-label',
deviceID: novoDispositivo.id,
integrationName: 'app-login-pl',
dadosMaquina: { ip: novoDispositivo.ip, fingerPrint: novoDispositivo.fingerprint }
}
})
});
if (!response.ok) throw new Error('Código SMS inválido');
return await response.json();
}
import requests
def validar_sms_novo_dispositivo(base_url, session_token, pin_code, novo_dispositivo):
url = f"{base_url}/identificacao/token-sms"
headers = {
"Authorization": f"Bearer {session_token}",
"Content-Type": "application/json"
}
payload = {
"pinCode": pin_code,
"analise": {
"userID": novo_dispositivo["cpf"],
"origem": "meu-app-private-label",
"deviceID": novo_dispositivo["id"],
"integrationName": "app-login-pl",
"dadosMaquina": {
"ip": novo_dispositivo["ip"],
"fingerPrint": novo_dispositivo["fingerprint"]
}
}
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.http.*;
import java.net.URI;
import java.util.Map;
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> body = Map.of(
"pinCode", pinCode,
"analise", Map.of(
"userID", cpf, "origem", "meu-app-private-label",
"deviceID", novoDispositivoId, "integrationName", "app-login-pl",
"dadosMaquina", Map.of("ip", ip, "fingerPrint", fingerprint)
)
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/identificacao/token-sms"))
.header("Authorization", "Bearer " + sessionToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body)))
.build();
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
using System.Net.Http.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", sessionToken);
var payload = new {
pinCode,
analise = new {
userID = cpf,
origem = "meu-app-private-label",
deviceID = novoDispositivoId,
integrationName = "app-login-pl",
dadosMaquina = new { ip, fingerPrint = fingerprint }
}
};
var response = await client.PostAsJsonAsync($"{baseUrl}/identificacao/token-sms", payload);
response.EnsureSuccessStatusCode();
$payload = [
'pinCode' => $pinCode,
'analise' => [
'userID' => $cpf,
'origem' => 'meu-app-private-label',
'deviceID' => $novoDispositivoId,
'integrationName' => 'app-login-pl',
'dadosMaquina' => ['ip' => $ip, 'fingerPrint' => $fingerprint]
]
];
$ch = curl_init("{$baseUrl}/identificacao/token-sms");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$sessionToken}",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
curl_close($ch);
import (
"bytes"
"encoding/json"
"net/http"
)
payload := map[string]interface{}{
"pinCode": pinCode,
"analise": map[string]interface{}{
"userID": cpf, "origem": "meu-app-private-label",
"deviceID": novoDispositivoId, "integrationName": "app-login-pl",
"dadosMaquina": map[string]string{"ip": ip, "fingerPrint": fingerprint},
},
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/identificacao/token-sms", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+sessionToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := (&http.Client{}).Do(req)
defer resp.Body.Close()
Resposta de Sucesso
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldU...",
"expires_in": 300,
"refresh_expires_in": 1800,
"token_type": "Bearer",
"device_validated": true
}
Após a validação bem-sucedida, o novo dispositivo estará autorizado e o usuário poderá acessar o app normalmente com os tokens retornados.
Consulte o 🔧 Troubleshooting - Validar SMS para detalhes de erros e soluções.
🔄 Fluxo Completo - Exemplo
async function fluxoTrocaDispositivoCompleto(credenciais, novoDispositivo) {
try {
// Passo 1: Login no novo dispositivo
console.log('🔑 Fazendo login no novo dispositivo...');
const loginResponse = await loginNovoDispositivo(
credenciais.cpf,
credenciais.senha,
novoDispositivo
);
// Verificar se precisa validação
if (!loginResponse.requires_device_validation) {
console.log('✅ Dispositivo já autorizado');
return loginResponse;
}
console.log('🔐 Dispositivo novo detectado. Validação necessária.');
const sessionToken = loginResponse.access_token;
// Passo 2: Biometria (opcional)
if (loginResponse.validation_methods.includes('BIOMETRIA')) {
const usarBiometria = await perguntarUsuario('Deseja usar biometria?');
if (usarBiometria) {
const linkBiometria = await solicitarBiometriaNovoDispositivo(
sessionToken,
credenciais.consumidorId,
credenciais.lojaId
);
await abrirBiometria(linkBiometria);
// Se biometria for aprovada, pode pular SMS
return;
}
}
// Passo 3: Enviar SMS
console.log('📱 Enviando código SMS...');
await enviarSMSTrocaDispositivo(sessionToken);
exibirMensagem('Código SMS enviado para seu telefone cadastrado');
// Aguardar usuário digitar código
const pinCode = await solicitarCodigoSMS();
// Passo 4: Validar SMS
console.log('✔️ Validando código SMS...');
const tokensFinais = await validarSMSNovoDispositivo(
sessionToken,
pinCode,
novoDispositivo
);
// Salvar tokens e informações do novo dispositivo
await salvarTokens(tokensFinais);
await salvarDispositivoAutorizado(novoDispositivo);
console.log('✅ Novo dispositivo autorizado com sucesso!');
// Navegar para tela principal
navegarParaHome();
return tokensFinais;
} catch (error) {
console.error('❌ Erro na troca de dispositivo:', error);
if (error.message.includes('Código SMS inválido')) {
exibirErro('Código incorreto. Tente novamente ou solicite um novo código.');
} else if (error.message.includes('Token expirado')) {
exibirErro('Tempo expirado. Por favor, faça login novamente.');
navegarParaLogin();
} else {
exibirErro('Erro ao validar dispositivo. Tente novamente mais tarde.');
}
throw error;
}
}
🔒 Segurança e Boas Práticas
1. Identificação Única do Dispositivo
// Gerar ID único e persistente do dispositivo
async function obterDispositivoId() {
// Android
if (Platform.OS === 'android') {
return await DeviceInfo.getUniqueId();
}
// iOS - Use UUID armazenado no Keychain
if (Platform.OS === 'ios') {
let uuid = await Keychain.getGenericPassword({ service: 'deviceId' });
if (!uuid) {
uuid = UUID.v4();
await Keychain.setGenericPassword('deviceId', uuid, { service: 'deviceId' });
}
return uuid;
}
}
2. Fingerprint do Dispositivo
// Coletar informações para fingerprint
async function gerarFingerprint() {
const info = {
brand: await DeviceInfo.getBrand(),
model: await DeviceInfo.getModel(),
systemName: await DeviceInfo.getSystemName(),
systemVersion: await DeviceInfo.getSystemVersion(),
deviceId: await DeviceInfo.getUniqueId(),
appVersion: await DeviceInfo.getVersion(),
buildNumber: await DeviceInfo.getBuildNumber(),
carrier: await DeviceInfo.getCarrier(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
};
// Criar hash do fingerprint
return btoa(JSON.stringify(info));
}
3. Gerenciamento de Dispositivos Múltiplos
// Armazenar lista de dispositivos autorizados
async function salvarDispositivoAutorizado(dispositivo) {
const dispositivos = await obterDispositivosAutorizados();
// Adicionar novo dispositivo
dispositivos.push({
id: dispositivo.id,
modelo: dispositivo.modelo,
dataAutorizacao: new Date().toISOString(),
ultimoAcesso: new Date().toISOString()
});
// Limitar a 3 dispositivos mais recentes
if (dispositivos.length > 3) {
dispositivos.sort((a, b) =>
new Date(b.ultimoAcesso) - new Date(a.ultimoAcesso)
);
dispositivos.splice(3);
}
await AsyncStorage.setItem('dispositivos_autorizados', JSON.stringify(dispositivos));
}
// Verificar se dispositivo já está autorizado
async function dispositivoJaAutorizado(dispositivoId) {
const dispositivos = await obterDispositivosAutorizados();
return dispositivos.some(d => d.id === dispositivoId);
}
4. Limite de Tentativas
// Controlar tentativas de validação SMS
class ValidadorSMS {
constructor() {
this.tentativas = 0;
this.maxTentativas = 3;
this.bloqueadoAte = null;
}
async validar(codigo) {
// Verificar se está bloqueado
if (this.bloqueadoAte && new Date() < this.bloqueadoAte) {
const minutosRestantes = Math.ceil(
(this.bloqueadoAte - new Date()) / 60000
);
throw new Error(
`Muitas tentativas. Aguarde ${minutosRestantes} minutos.`
);
}
try {
const resultado = await validarSMSNovoDispositivo(codigo);
this.tentativas = 0;
return resultado;
} catch (error) {
this.tentativas++;
if (this.tentativas >= this.maxTentativas) {
// Bloquear por 15 minutos
this.bloqueadoAte = new Date(Date.now() + 15 * 60 * 1000);
throw new Error(
'Limite de tentativas excedido. Aguarde 15 minutos.'
);
}
throw error;
}
}
async reenviarSMS() {
if (this.bloqueadoAte && new Date() < this.bloqueadoAte) {
throw new Error('Aguarde antes de solicitar novo código');
}
// Resetar tentativas ao reenviar
this.tentativas = 0;
return await enviarSMSTrocaDispositivo();
}
}
⚠️ Tratamento de Erros
| Código | Descrição | Ação Recomendada |
|---|---|---|
| 401 | Credenciais inválidas | Verificar CPF e senha |
| 403 | Dispositivo bloqueado | Contatar suporte |
| 422 | Código SMS inválido | Permitir nova tentativa (máx 3) |
| 429 | Muitas tentativas | Bloquear temporariamente (15 min) |
| 500 | Erro no servidor | Tentar novamente mais tarde |
Exemplo de Tratamento
function tratarErroTrocaDispositivo(error) {
switch (error.status) {
case 401:
return 'CPF ou senha incorretos';
case 403:
return 'Dispositivo bloqueado. Entre em contato com o suporte.';
case 422:
return 'Código SMS inválido. Tente novamente.';
case 429:
return 'Muitas tentativas. Aguarde 15 minutos.';
default:
return 'Erro ao validar dispositivo. Tente novamente mais tarde.';
}
}
💡 Dicas de UX
1. Mensagens Claras
// Informar usuário sobre novo dispositivo
function exibirMensagemNovoDispositivo() {
return (
<Alert>
<AlertIcon name="shield" />
<AlertTitle>Novo dispositivo detectado</AlertTitle>
<AlertDescription>
Por segurança, precisamos validar este dispositivo.
Você receberá um código por SMS no número cadastrado.
</AlertDescription>
</Alert>
);
}
2. Timer Visual para Reenvio
function ComponenteCodigoSMS() {
const [tempoRestante, setTempoRestante] = useState(60);
const [podeReenviar, setPodeReenviar] = useState(false);
useEffect(() => {
if (tempoRestante > 0) {
const timer = setTimeout(() => {
setTempoRestante(tempoRestante - 1);
}, 1000);
return () => clearTimeout(timer);
} else {
setPodeReenviar(true);
}
}, [tempoRestante]);
return (
<View>
<Text>Digite o código recebido por SMS</Text>
<Input onChangeText={setCodigo} maxLength={6} keyboardType="numeric" />
{podeReenviar ? (
<Button onPress={reenviarCodigo}>Reenviar código</Button>
) : (
<Text>Reenviar em {tempoRestante}s</Text>
)}
</View>
);
}
3. Lista de Dispositivos Autorizados
// Mostrar dispositivos autorizados ao usuário
function TelaDispositivos() {
const [dispositivos, setDispositivos] = useState([]);
useEffect(() => {
carregarDispositivos();
}, []);
return (
<View>
<Text>Dispositivos Autorizados</Text>
{dispositivos.map(dispositivo => (
<Card key={dispositivo.id}>
<CardTitle>{dispositivo.modelo}</CardTitle>
<CardDescription>
Autorizado em {formatarData(dispositivo.dataAutorizacao)}
</CardDescription>
<CardDescription>
Último acesso: {formatarData(dispositivo.ultimoAcesso)}
</CardDescription>
{dispositivo.id === dispositivoAtual && (
<Badge>Este dispositivo</Badge>
)}
<Button onPress={() => revogarDispositivo(dispositivo.id)}>
Remover
</Button>
</Card>
))}
</View>
);
}
🎓 Recursos Adicionais
- 🔧 Guia de Troubleshooting — Solução de problemas e erros comuns
- 📄 Referência OpenAPI — Especificação técnica completa
📚 Recursos Relacionados
- Login - Autenticação de usuários
- Primeiro Acesso - Cadastro de novos usuários
- Recuperar Senha - Redefinir senha esquecida