Pular para o conteúdo principal

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

Quando usar

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
📚 Recursos Adicionais

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 -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"}'

Resposta - Dispositivo Não Reconhecido

Quando o sistema detecta um novo dispositivo, retorna um token de sessão temporário:

200 OK
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 900,
"requires_device_validation": true,
"validation_methods": ["SMS", "BIOMETRIA"]
}
🔐 Validação Necessária

O flag requires_device_validation: true indica que validações adicionais são necessárias antes do acesso completo.


Problemas com este endpoint?

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.

🔐 Motor de Biometria Credsystem

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 -X POST "{baseUrl}/identificacao/biometria" \
-H "Authorization: Bearer {token-sessao-cliente}" \
-H "Content-Type: application/json" \
-d '{"consumidor": "CONSUMIDOR_ID", "loja": 123456}'
📸 Próximo passo após receber o link

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.


Problemas com este endpoint?

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 -X POST "{baseUrl}/identificacao/token-sms/envio" \
-H "Authorization: Bearer {token-sessao-cliente}"
📱 Código SMS
  • Código de 6 dígitos
  • Válido por 5 minutos
  • Enviado para o telefone cadastrado
  • Uso único

Problemas com este endpoint?

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

CampoTipoObrigatórioDescrição
pinCodestringCódigo de 6 dígitos recebido por SMS
analise.userIDstringCPF do usuário
analise.origemstringNome do aplicativo
analise.deviceIDstringID do novo dispositivo
analise.integrationNamestringNome da integração
analise.dadosMaquina.ipstringEndereço IP do novo dispositivo
analise.dadosMaquina.fingerPrintstringFingerprint do novo dispositivo

Exemplos de Código

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}"}
}
}'

Resposta de Sucesso

200 OK
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldU...",
"expires_in": 300,
"refresh_expires_in": 1800,
"token_type": "Bearer",
"device_validated": true
}
✅ Dispositivo Autorizado

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.


Problemas com este endpoint?

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ódigoDescriçãoAção Recomendada
401Credenciais inválidasVerificar CPF e senha
403Dispositivo bloqueadoContatar suporte
422Código SMS inválidoPermitir nova tentativa (máx 3)
429Muitas tentativasBloquear temporariamente (15 min)
500Erro no servidorTentar 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

📚 Recursos Relacionados