Logo

Autenticação - Endpoints

Referência completa dos endpoints de autenticação da API Sinapse

Endpoints de Autenticação

O módulo de autenticação gerencia login, logout, tokens e sessões de usuários.

Login

Autentica um usuário e retorna tokens de acesso.

POST /api/v1/auth/login
Content-Type: application/json

Body Parameters:

CampoTipoObrigatórioDescrição
usernamestringSimEmail do usuário
passwordstringSimSenha do usuário
mfa_codestringNãoCódigo MFA [Em breve]
remember_mebooleanNãoEstender duração do token [Em breve]
{
  "username": "[email protected]",
  "password": "senhaSegura123!"
}

Success Response (200):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}

Error Responses:

  • 401 - Credenciais Inválidas
{
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Email ou senha incorretos"
  }
}
  • 428 - MFA Necessário [Em breve]
{
  "error": {
    "code": "MFA_REQUIRED",
    "message": "Código de autenticação multifator necessário"
  }
}
  • 423 - Conta Bloqueada
{
  "error": {
    "code": "ACCOUNT_LOCKED",
    "message": "Conta bloqueada após múltiplas tentativas falhas",
    "locked_until": "2025-07-29T11:00:00Z"
  }
}

cURL:

curl -X POST https://api.sinapse.org.br/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "[email protected]",
    "password": "senhaSegura123!"
  }'

JavaScript:

const response = await fetch('https://api.sinapse.org.br/v1/auth/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    username: '[email protected]',
    password: 'senhaSegura123!'
  })
});

const data = await response.json();
localStorage.setItem('access_token', data.access_token);

Python:

import requests

response = requests.post(
    'https://api.sinapse.org.br/v1/auth/login',
    json={
        'username': '[email protected]',
        'password': 'senhaSegura123!'
    }
)

if response.status_code == 200:
    tokens = response.json()
    access_token = tokens['access_token']

Refresh Token

Renova o token de acesso usando o refresh token.

POST /api/v1/auth/refresh
Content-Type: application/json

Body Parameters:

CampoTipoObrigatórioDescrição
refresh_tokenstringSimRefresh token válido
{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Success Response (200):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}

Error Response (401):

{
  "error": {
    "code": "INVALID_REFRESH_TOKEN",
    "message": "Refresh token inválido ou expirado"
  }
}

JavaScript com Auto-Refresh:

class TokenManager {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
    this.refreshTimer = null;
  }
  
  async login(username, password) {
    const response = await fetch('/api/v1/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password })
    });
    
    const data = await response.json();
    this.setTokens(data);
    return data;
  }
  
  setTokens(data) {
    this.accessToken = data.access_token;
    this.refreshToken = data.refresh_token;
    
    // Agendar refresh automático 5 minutos antes de expirar
    const refreshIn = (data.expires_in - 300) * 1000;
    this.scheduleRefresh(refreshIn);
  }
  
  scheduleRefresh(delay) {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }
    
    this.refreshTimer = setTimeout(() => {
      this.refresh();
    }, delay);
  }
  
  async refresh() {
    try {
      const response = await fetch('/api/v1/auth/refresh', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          refresh_token: this.refreshToken
        })
      });
      
      if (response.ok) {
        const data = await response.json();
        this.accessToken = data.access_token;
        this.scheduleRefresh((data.expires_in - 300) * 1000);
      } else {
        // Refresh falhou, fazer logout
        this.logout();
      }
    } catch (error) {
      console.error('Erro ao renovar token:', error);
      this.logout();
    }
  }
  
  logout() {
    this.accessToken = null;
    this.refreshToken = null;
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }
    // Redirecionar para login
    window.location.href = '/login';
  }
}

Logout

Invalida os tokens e encerra a sessão do usuário.

POST /api/v1/auth/logout
Authorization: Bearer {access_token}
Content-Type: application/json

Body Parameters (Opcional - em breve):

CampoTipoObrigatórioDescrição
refresh_tokenstringNãoRefresh token para invalidar
everywherebooleanNãoLogout de todos os dispositivos
{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "everywhere": true // **[Em breve]**
}

Success Response (200):

{
  "message": "Logout realizado com sucesso"
}

Error Response (401):

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Token inválido ou expirado"
  }
}

cURL:

curl -X POST https://api.sinapse.org.br/v1/auth/logout \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

JavaScript:

async function logout(logoutEverywhere = false) {
  const token = localStorage.getItem('access_token');
  const refreshToken = localStorage.getItem('refresh_token');
  
  try {
    await fetch('/api/v1/auth/logout', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        refresh_token: refreshToken,
        // everywhere: logoutEverywhere // **[Em breve]**
      })
    });
  } finally {
    // Limpar tokens locais independente do resultado
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
    window.location.href = '/login';
  }
}

Informações do Usuário

Retorna informações do usuário autenticado.

GET /api/v1/usuarios/me
Authorization: Bearer {access_token}

Query Parameters:

ParâmetroTipoDescrição
includestringIncluir relações: grupos,permissoes,sessoes

Success Response (200):

{
  "id": 123,
  "nome": "João Silva",
  "email": "[email protected]",
  "cpf": "123.456.789-00",
  "telefone": "(11) 98765-4321",
  "avatar_url": "https://api.sinapse.org.br/avatars/123.jpg",
  "cargo": "Analista de Sistemas",
  "departamento": "TI",
  "ativo": true,
  "email_verificado": true,
  "mfa_habilitado": false,
  "criado_em": "2025-01-15T10:00:00Z",
  "atualizado_em": "2025-07-29T09:30:00Z",
  "ultimo_acesso": "2025-07-29T10:00:00Z",
  "grupos": [
    {
      "id": 2,
      "nome": "analista",
      "descricao": "Analista de dados"
    }
  ],
  "permissoes": [
    "agravos.listar",
    "agravos.criar",
    "noticias.listar"
  ],
  "sessoes_ativas": 1
}

JavaScript:

async function getProfile() {
  const response = await fetch('/api/v1/usuarios/me?include=grupos,permissoes', {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  if (response.ok) {
    const user = await response.json();
    console.log('Usuário:', user.nome);
    console.log('Grupos:', user.grupos.map(g => g.nome).join(', '));
    console.log('Permissões:', user.permissoes.length);
  }
}

Python:

def get_user_profile(token):
    headers = {'Authorization': f'Bearer {token}'}
    response = requests.get(
        'https://api.sinapse.org.br/v1/usuarios/me',
        headers=headers,
        params={'include': 'grupos,permissoes'}
    )
    
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception('Erro ao buscar perfil')

Alterar Senha [Em breve]

Permite ao usuário alterar sua própria senha.

Esta funcionalidade [Em breve]. Por enquanto, use o endpoint de reset de senha.

POST /api/v1/auth/change-password
Authorization: Bearer {access_token}
Content-Type: application/json

Body Parameters:

CampoTipoObrigatórioDescrição
senha_atualstringSimSenha atual do usuário
senha_novastringSimNova senha (min 8 caracteres)
confirmar_senhastringSimConfirmação da nova senha
{
  "senha_atual": "senhaAntiga123!",
  "senha_nova": "senhaNova456!",
  "confirmar_senha": "senhaNova456!"
}

Requisitos da Senha:

  • Mínimo 8 caracteres
  • Pelo menos 1 letra maiúscula
  • Pelo menos 1 letra minúscula
  • Pelo menos 1 número
  • Pelo menos 1 caractere especial

Success Response (200):

{
  "message": "Senha alterada com sucesso",
  "sessions_invalidated": true
}

Error Responses:

  • 400 - Senha Fraca
{
  "error": {
    "code": "WEAK_PASSWORD",
    "message": "A senha não atende aos requisitos mínimos",
    "details": [
      "Deve conter pelo menos 1 caractere especial",
      "Deve conter pelo menos 1 número"
    ]
  }
}
  • 401 - Senha Atual Incorreta
{
  "error": {
    "code": "INVALID_CURRENT_PASSWORD",
    "message": "Senha atual incorreta"
  }
}

React Component:

function ChangePasswordForm() {
  const [form, setForm] = useState({
    senha_atual: '',
    senha_nova: '',
    confirmar_senha: ''
  });
  const [errors, setErrors] = useState([]);
  
  const validatePassword = (password) => {
    const requirements = [];
    if (password.length < 8) {
      requirements.push('Mínimo 8 caracteres');
    }
    if (!/[A-Z]/.test(password)) {
      requirements.push('Pelo menos 1 letra maiúscula');
    }
    if (!/[a-z]/.test(password)) {
      requirements.push('Pelo menos 1 letra minúscula');
    }
    if (!/[0-9]/.test(password)) {
      requirements.push('Pelo menos 1 número');
    }
    if (!/[^A-Za-z0-9]/.test(password)) {
      requirements.push('Pelo menos 1 caractere especial');
    }
    return requirements;
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    // Validar localmente primeiro
    const validationErrors = validatePassword(form.senha_nova);
    if (validationErrors.length > 0) {
      setErrors(validationErrors);
      return;
    }
    
    if (form.senha_nova !== form.confirmar_senha) {
      setErrors(['As senhas não coincidem']);
      return;
    }
    
    try {
      const response = await fetch('/api/v1/auth/change-password', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${accessToken}`
        },
        body: JSON.stringify(form)
      });
      
      if (response.ok) {
        alert('Senha alterada com sucesso!');
        // Redirecionar para login
        window.location.href = '/login';
      } else {
        const error = await response.json();
        setErrors([error.error.message]);
      }
    } catch (error) {
      setErrors(['Erro ao alterar senha']);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* Campos do formulário */}
    </form>
  );
}

Esqueci Minha Senha

Inicia o processo de recuperação de senha.

POST /api/v1/auth/forgot-password
Content-Type: application/json

Body Parameters:

CampoTipoObrigatórioDescrição
emailstringSimEmail do usuário
{
  "email": "[email protected]"
}

Success Response (200):

{
  "message": "Se o email existir, instruções de recuperação foram enviadas",
  "expires_in": 3600
}

Por segurança, a resposta é sempre a mesma, independente do email existir ou não.

JavaScript:

async function forgotPassword(email) {
  const response = await fetch('/api/v1/auth/forgot-password', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email })
  });
  
  if (response.ok) {
    alert('Verifique seu email para instruções de recuperação');
  }
}

Resetar Senha

Completa o processo de recuperação de senha usando o token recebido por email.

POST /api/v1/auth/reset-password
Content-Type: application/json

Body Parameters:

CampoTipoObrigatórioDescrição
tokenstringSimToken de recuperação
senha_novastringSimNova senha
confirmar_senhastringSimConfirmação da senha
{
  "token": "abc123def456ghi789",
  "senha_nova": "novaSenhaSegura123!",
  "confirmar_senha": "novaSenhaSegura123!"
}

Success Response (200):

{
  "message": "Senha resetada com sucesso"
}

Error Responses:

  • 400 - Token Inválido
{
  "error": {
    "code": "INVALID_RESET_TOKEN",
    "message": "Token de recuperação inválido ou expirado"
  }
}

React Page Component:

function ResetPasswordPage() {
  const [searchParams] = useSearchParams();
  const token = searchParams.get('token');
  const [form, setForm] = useState({
    senha_nova: '',
    confirmar_senha: ''
  });
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    if (form.senha_nova !== form.confirmar_senha) {
      alert('As senhas não coincidem');
      return;
    }
    
    const response = await fetch('/api/v1/auth/reset-password', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        token,
        senha_nova: form.senha_nova,
        confirmar_senha: form.confirmar_senha
      })
    });
    
    if (response.ok) {
      alert('Senha alterada com sucesso!');
      window.location.href = '/login';
    } else {
      alert('Erro ao resetar senha');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* Campos do formulário */}
    </form>
  );
}

Multi-Factor Authentication (MFA) [Em breve]

Habilitar MFA [Em breve]

POST /api/v1/auth/mfa/enable
Authorization: Bearer {access_token}

Success Response (200):

{
  "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANS...",
  "secret": "JBSWY3DPEHPK3PXP",
  "backup_codes": [
    "12345678",
    "87654321",
    "11223344",
    "55667788",
    "99001122"
  ],
  "instructions": "Escaneie o QR code com seu app autenticador"
}

React Component:

function EnableMFA() {
  const [mfaData, setMfaData] = useState(null);
  const [verificationCode, setVerificationCode] = useState('');
  
  const enableMFA = async () => {
    const response = await fetch('/api/v1/auth/mfa/enable', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    });
    
    if (response.ok) {
      const data = await response.json();
      setMfaData(data);
    }
  };
  
  const verifyMFA = async () => {
    const response = await fetch('/api/v1/auth/mfa/verify', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        code: verificationCode
      })
    });
    
    if (response.ok) {
      alert('MFA habilitado com sucesso!');
    }
  };
  
  return (
    <div>
      {!mfaData ? (
        <button onClick={enableMFA}>Habilitar MFA</button>
      ) : (
        <>
          <img src={mfaData.qr_code} alt="QR Code" />
          <p>Secret: {mfaData.secret}</p>
          <input
            type="text"
            value={verificationCode}
            onChange={(e) => setVerificationCode(e.target.value)}
            placeholder="Código de verificação"
          />
          <button onClick={verifyMFA}>Verificar</button>
          
          <h3>Códigos de Backup (guarde em local seguro):</h3>
          <ul>
            {mfaData.backup_codes.map(code => (
              <li key={code}>{code}</li>
            ))}
          </ul>
        </>
      )}
    </div>
  );
}

Desabilitar MFA [Em breve]

POST /api/v1/auth/mfa/disable
Authorization: Bearer {access_token}
Content-Type: application/json

Body Parameters:

CampoTipoObrigatórioDescrição
senhastringSimSenha atual do usuário
codestringSimCódigo MFA atual
{
  "senha": "senhaAtual123!",
  "code": "123456"
}

Success Response (200):

{
  "message": "MFA desabilitado com sucesso"
}

Listar Sessões [Em breve]

Lista todas as sessões ativas do usuário.

GET /api/v1/auth/sessions
Authorization: Bearer {access_token}

Success Response (200):

{
  "total": 3,
  "items": [
    {
      "id": "sess_123abc",
      "device": "Chrome 91.0 - Windows 10",
      "ip_address": "192.168.1.100",
      "location": "São Paulo, BR",
      "created_at": "2025-07-29T08:00:00Z",
      "last_activity": "2025-07-29T10:30:00Z",
      "current": true
    },
    {
      "id": "sess_456def",
      "device": "Mobile App - iOS 14.6",
      "ip_address": "189.40.123.45",
      "location": "Rio de Janeiro, BR",
      "created_at": "2025-07-28T14:00:00Z",
      "last_activity": "2025-07-28T18:45:00Z",
      "current": false
    }
  ]
}

Revogar Sessão [Em breve]

Revoga uma sessão específica.

DELETE /api/v1/auth/sessions/{session_id}
Authorization: Bearer {access_token}

Success Response (204):

Sem conteúdo de resposta

Error Response (404):

{
  "error": {
    "code": "SESSION_NOT_FOUND",
    "message": "Sessão não encontrada"
  }
}

Métricas de Segurança [Em breve]

Retorna métricas de segurança da conta do usuário.

GET /api/v1/auth/security-metrics
Authorization: Bearer {access_token}

Success Response (200):

{
  "login_attempts": {
    "successful": 45,
    "failed": 3,
    "last_failed": "2025-07-15T14:30:00Z"
  },
  "password_strength": "strong",
  "password_age_days": 45,
  "mfa_enabled": true,
  "active_sessions": 2,
  "suspicious_activities": [],
  "security_score": 95,
  "recommendations": [
    "Considere usar uma senha mais longa"
  ]
}

Melhores Práticas

Segurança de Tokens

  1. Armazenamento Seguro

    • Use HTTPS sempre
    • Armazene tokens em local seguro (não em localStorage para apps sensíveis)
    • Implemente rotação automática de tokens
  2. Validação

    • Valide tokens em cada requisição
    • Implemente timeout de sessão
    • Use refresh tokens com prazo maior
  3. Monitoramento

    • Registre todas as tentativas de login
    • Detecte padrões suspeitos
    • Notifique usuários sobre acessos incomuns

Próximos Passos

On this page