Pular para o conteúdo principal
ℹ️ Conteúdo em atualização

Estamos revisando estes procedimentos. Pessoas técnicas podem encontrar ajustes em códigos de erro ou mapeamentos de payload; valide com os logs e o OpenAPI mais recente. Pessoas não técnicas podem continuar usando o passo a passo e acionar o time de suporte caso algo pareça diferente do descrito.

📲 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

Passo 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

Parâmetros da Requisição

Importante: Envie as informações do novo dispositivo no parâmetro infosAdicionais.

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

Exemplo em JavaScript

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
})
})
}
);

const data = await response.json();

// Se o dispositivo não é reconhecido, será retornado um token de sessão
// temporário que exige validação adicional
return data;
}

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.


Se configurado, você pode solicitar validação biométrica como método alternativo ou adicional.

Endpoint

POST {baseUrl}/identificacao/biometria
Authorization: Bearer {token-sessao-cliente}
Content-Type: application/json

Request Body

{
"consumidor": "CONSUMIDOR_ID",
"loja": 123456
}

Exemplo de Código

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();

// Abra o link para captura biométrica
return link;
}
📸 Biometria

A validação biométrica é opcional e depende da configuração do aplicativo. Pode ser usada como método único ou em conjunto com SMS.


Passo 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}

Exemplo de Código

async function enviarSMSTrocaDispositivo(sessionToken) {
const response = await fetch(`${baseUrl}/identificacao/token-sms/envio`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`
}
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Erro ao enviar SMS');
}

const data = await response.json();

console.log('SMS enviado com sucesso');
return data;
}
📱 Código SMS
  • Código de 6 dígitos
  • Válido por 5 minutos
  • Enviado para o telefone cadastrado
  • Uso único

Passo 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

Exemplo de Código

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: 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) {
const error = await response.json();
throw new Error(error.message || 'Código SMS inválido');
}

const data = await response.json();

// Após validação bem-sucedida, retorna tokens de acesso completos
return data;
}

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.


🔄 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 Relacionados


Última atualização: 03 de Dezembro de 2025