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/jsonBody Parameters:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
username | string | Sim | Email do usuário |
password | string | Sim | Senha do usuário |
mfa_code | string | Não | Código MFA [Em breve] |
remember_me | boolean | Não | Estender 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/jsonBody Parameters:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
refresh_token | string | Sim | Refresh 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/jsonBody Parameters (Opcional - em breve):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
refresh_token | string | Não | Refresh token para invalidar |
everywhere | boolean | Não | Logout 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âmetro | Tipo | Descrição |
|---|---|---|
include | string | Incluir 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/jsonBody Parameters:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
senha_atual | string | Sim | Senha atual do usuário |
senha_nova | string | Sim | Nova senha (min 8 caracteres) |
confirmar_senha | string | Sim | Confirmaçã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/jsonBody Parameters:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
email | string | Sim | Email 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/jsonBody Parameters:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
token | string | Sim | Token de recuperação |
senha_nova | string | Sim | Nova senha |
confirmar_senha | string | Sim | Confirmaçã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/jsonBody Parameters:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
senha | string | Sim | Senha atual do usuário |
code | string | Sim | Có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
-
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
-
Validação
- Valide tokens em cada requisição
- Implemente timeout de sessão
- Use refresh tokens com prazo maior
-
Monitoramento
- Registre todas as tentativas de login
- Detecte padrões suspeitos
- Notifique usuários sobre acessos incomuns