Logo

Autenticação e Segurança

Guia completo sobre autenticação, autorização e segurança na API Sinapse

A API Sinapse utiliza um sistema robusto de autenticação baseado em JWT (JSON Web Tokens) com suporte a refresh tokens e controle granular de permissões através de RBAC (Role-Based Access Control).

Visão Geral

Fluxo de Autenticação

Carregando diagrama...

Tipos de Autenticação

1. JWT Bearer Token (Recomendado)

O método principal de autenticação usa tokens JWT:

# Login
curl -X POST https://api.sinapse.org.br/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "senha": "suaSenhaSegura"
  }'

Resposta:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "user": {
    "id": 123,
    "nome": "João Silva",
    "email": "[email protected]",
    "grupos": ["analista", "gestor"],
    "permissoes": ["agravos.listar", "agravos.criar", "usuarios.listar"]
  }
}

2. OAuth 2.0 [Em breve]

Suporte OAuth 2.0 estará disponível em breve para integrações com terceiros.

Gerenciamento de Tokens

Refresh Token

Quando o access token expira, use o refresh token para obter um novo:

async function renovarToken(refreshToken) {
  const response = await fetch(`${API_URL}/auth/refresh`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      refresh_token: refreshToken
    })
  });
  
  if (!response.ok) {
    // Refresh token inválido, fazer login novamente
    return redirectToLogin();
  }
  
  const data = await response.json();
  // Armazenar novo access token
  localStorage.setItem('access_token', data.access_token);
  return data.access_token;
}

Implementação com Interceptors

import axios from 'axios';

// Configurar interceptor para adicionar token
axios.interceptors.request.use(
  config => {
    const token = localStorage.getItem('access_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

// Interceptor para renovar token automaticamente
axios.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        const refreshToken = localStorage.getItem('refresh_token');
        const response = await axios.post('/auth/refresh', {
          refresh_token: refreshToken
        });
        
        const { access_token } = response.data;
        localStorage.setItem('access_token', access_token);
        
        // Repetir requisição original com novo token
        originalRequest.headers.Authorization = `Bearer ${access_token}`;
        return axios(originalRequest);
      } catch (refreshError) {
        // Refresh falhou, redirecionar para login
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);
class ApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }
  
  async request(endpoint, options = {}) {
    const token = localStorage.getItem('access_token');
    
    const config = {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
        ...(token && { Authorization: `Bearer ${token}` })
      }
    };
    
    let response = await fetch(`${this.baseURL}${endpoint}`, config);
    
    // Se token expirado, tentar renovar
    if (response.status === 401) {
      const newToken = await this.refreshToken();
      if (newToken) {
        config.headers.Authorization = `Bearer ${newToken}`;
        response = await fetch(`${this.baseURL}${endpoint}`, config);
      } else {
        window.location.href = '/login';
      }
    }
    
    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }
    
    return response.json();
  }
  
  async refreshToken() {
    const refreshToken = localStorage.getItem('refresh_token');
    if (!refreshToken) return null;
    
    try {
      const response = await fetch(`${this.baseURL}/auth/refresh`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refresh_token: refreshToken })
      });
      
      if (!response.ok) return null;
      
      const data = await response.json();
      localStorage.setItem('access_token', data.access_token);
      return data.access_token;
    } catch {
      return null;
    }
  }
}

// Uso
const api = new ApiClient('https://api.sinapse.org.br/v1');
const casos = await api.request('/agravos/casos');
import requests
from datetime import datetime, timedelta

class SinapseAPI:
    def __init__(self, base_url, email=None, senha=None):
        self.base_url = base_url
        self.session = requests.Session()
        self.token_expires_at = None
        
        if email and senha:
            self.login(email, senha)
    
    def login(self, email, senha):
        response = self.session.post(
            f"{self.base_url}/auth/login",
            json={"email": email, "senha": senha}
        )
        response.raise_for_status()
        
        data = response.json()
        self.session.headers.update({
            "Authorization": f"Bearer {data['access_token']}"
        })
        self.refresh_token = data['refresh_token']
        self.token_expires_at = datetime.now() + timedelta(
            seconds=data['expires_in']
        )
    
    def _ensure_authenticated(self):
        if self.token_expires_at and datetime.now() >= self.token_expires_at:
            self._refresh_token()
    
    def _refresh_token(self):
        response = self.session.post(
            f"{self.base_url}/auth/refresh",
            json={"refresh_token": self.refresh_token}
        )
        response.raise_for_status()
        
        data = response.json()
        self.session.headers.update({
            "Authorization": f"Bearer {data['access_token']}"
        })
        self.token_expires_at = datetime.now() + timedelta(
            seconds=data['expires_in']
        )
    
    def request(self, method, endpoint, **kwargs):
        self._ensure_authenticated()
        
        response = self.session.request(
            method,
            f"{self.base_url}{endpoint}",
            **kwargs
        )
        
        # Tentar renovar token se 401
        if response.status_code == 401:
            self._refresh_token()
            response = self.session.request(
                method,
                f"{self.base_url}{endpoint}",
                **kwargs
            )
        
        response.raise_for_status()
        return response.json()

# Uso
api = SinapseAPI(
    "https://api.sinapse.org.br/v1",
    email="[email protected]",
    senha="senhaSegura"
)

casos = api.request("GET", "/agravos/casos")

Sistema de Permissões (RBAC)

Estrutura de Permissões

O Sinapse usa um sistema hierárquico de permissões:

Carregando diagrama...

Grupos Padrão

GrupoDescriçãoPermissões Principais
adminAdministrador totalTodas as permissões
gestorGestor de saúdeGestão de usuários e dados
analistaAnalista de dadosLeitura e análise
usuarioUsuário padrãoAcesso básico
visualizadorApenas leituraSomente visualização

Verificando Permissões

# Obter informações do usuário logado (incluindo permissões)
curl -X GET https://api.sinapse.org.br/v1/usuarios/me \
  -H "Authorization: Bearer SEU_TOKEN"

Resposta:

{
  "id": 123,
  "email": "[email protected]",
  "nome": "João Silva",
  "grupos": [
    {
      "id": 2,
      "nome": "analista",
      "descricao": "Analista de dados"
    }
  ],
  "permissoes_acesso": [
    "agravos.listar",
    "agravos.criar",
    "noticias.listar",
    "relatorios.gerar",
    "exportar.excel"
  ]
}

Permissões Necessárias por Endpoint

Melhores Práticas de Segurança

1. Armazenamento de Tokens

NUNCA armazene tokens em:

  • Código fonte
  • Local Storage (para aplicações sensíveis)
  • Logs ou mensagens de erro
  • URLs ou query parameters

Recomendações:

// Para aplicações web, use cookies httpOnly
// Configuração no backend:
res.cookie('access_token', token, {
  httpOnly: true,
  secure: true, // HTTPS only
  sameSite: 'strict',
  maxAge: 3600000 // 1 hora
});

// Ou use sessionStorage para sessões temporárias
sessionStorage.setItem('access_token', token);

// Para aplicações menos sensíveis, localStorage com criptografia
import CryptoJS from 'crypto-js';

const encryptedToken = CryptoJS.AES.encrypt(
  token, 
  'chave-secreta'
).toString();
localStorage.setItem('token', encryptedToken);
// Android - Use Android Keystore
val keyAlias = "SinapseTokenKey"

// Armazenar
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)

val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(keyAlias))

val encryptedToken = cipher.doFinal(token.toByteArray())
sharedPrefs.edit()
  .putString("encrypted_token", Base64.encodeToString(encryptedToken, Base64.DEFAULT))
  .putString("iv", Base64.encodeToString(cipher.iv, Base64.DEFAULT))
  .apply()
// iOS - Use Keychain
let token = "seu_token_aqui"
let tokenData = token.data(using: .utf8)!

let query: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword,
  kSecAttrAccount as String: "SinapseToken",
  kSecValueData as String: tokenData
]

SecItemAdd(query as CFDictionary, nil)
# Use variáveis de ambiente
import os
from cryptography.fernet import Fernet

# Criptografar antes de armazenar
key = os.getenv('ENCRYPTION_KEY').encode()
cipher = Fernet(key)

encrypted_token = cipher.encrypt(token.encode())

# Armazenar em cache seguro (Redis com TLS)
redis_client.setex(
  f"token:{user_id}",
  3600,  # TTL de 1 hora
  encrypted_token
)

# Usar secrets manager em produção
# AWS Secrets Manager, Azure Key Vault, etc.

2. Validação de Entrada

Sempre valide dados antes de enviar à API:

import { z } from 'zod';

// Schema de validação
const LoginSchema = z.object({
  email: z.string().email('Email inválido'),
  senha: z.string()
    .min(8, 'Senha deve ter no mínimo 8 caracteres')
    .regex(/[A-Z]/, 'Senha deve conter letra maiúscula')
    .regex(/[a-z]/, 'Senha deve conter letra minúscula')
    .regex(/[0-9]/, 'Senha deve conter número')
    .regex(/[^A-Za-z0-9]/, 'Senha deve conter caractere especial')
});

// Validar antes de enviar
try {
  const validData = LoginSchema.parse(formData);
  const response = await api.login(validData);
} catch (error) {
  console.error('Dados inválidos:', error.errors);
}

3. Proteção contra Ataques

CSRF Protection

// Incluir CSRF token em requisições
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

fetch('/api/endpoint', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken
  },
  body: JSON.stringify(data)
});

Rate Limiting no Cliente

import pLimit from 'p-limit';

// Limitar requisições paralelas
const limit = pLimit(5); // Máximo 5 requisições simultâneas

const promises = urls.map(url => 
  limit(() => fetch(url))
);

const results = await Promise.all(promises);

4. Auditoria e Logs

Implemente logs de segurança:

import logging
from datetime import datetime

security_logger = logging.getLogger('security')

def log_auth_attempt(email, success, ip_address, user_agent):
    security_logger.info({
        'event': 'authentication',
        'timestamp': datetime.utcnow().isoformat(),
        'email': email,
        'success': success,
        'ip_address': ip_address,
        'user_agent': user_agent,
    })

def log_permission_denied(user_id, endpoint, required_permission):
    security_logger.warning({
        'event': 'permission_denied',
        'timestamp': datetime.utcnow().isoformat(),
        'user_id': user_id,
        'endpoint': endpoint,
        'required_permission': required_permission
    })

Troubleshooting

Problemas Comuns de Autenticação

Checklist de Segurança

Antes de ir para produção, verifique:

Tokens armazenados de forma segura HTTPS habilitado em todas as comunicações Refresh token implementado e funcionando Tratamento adequado de erros de autenticação Logs de segurança configurados Rate limiting implementado no cliente Validação de entrada em todos os formulários Timeout de sessão configurado Política de senhas fortes aplicada

Próximos Passos


Suporte de Segurança: Para questões de segurança ou vulnerabilidades, entre em contato através de [email protected]

On this page