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
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:
Grupos Padrão
| Grupo | Descrição | Permissões Principais |
|---|---|---|
| admin | Administrador total | Todas as permissões |
| gestor | Gestor de saúde | Gestão de usuários e dados |
| analista | Analista de dados | Leitura e análise |
| usuario | Usuário padrão | Acesso básico |
| visualizador | Apenas leitura | Somente 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
Referência da API
Explore todos os endpoints disponíveis
Gestão de Usuários
Configure usuários e permissões
Exemplos Práticos
Código pronto para suas integrações
Suporte de Segurança: Para questões de segurança ou vulnerabilidades, entre em contato através de [email protected]