Autenticação
Visão Geral
A API Avista utiliza autenticação baseada em OAuth 2.0 com certificados X.509 (mTLS). Este modelo de segurança em camadas garante que apenas clientes autorizados com certificados válidos possam acessar a API.
Por que mTLS?
O mTLS (mutual TLS) oferece segurança superior comparado a tokens simples:
- Autenticação mútua: Tanto o cliente quanto o servidor se autenticam
- Não-repúdio: Certificados vinculados à conta garantem rastreabilidade
- Proteção contra roubo de credenciais: Mesmo com clientId/clientSecret vazados, o atacante precisaria do certificado
Pré-requisitos
Antes de iniciar, você precisará:
Obtenha seu certificado cliente através do portal Avista. O certificado deve estar no formato PEM e será vinculado à sua conta.
Solicite suas credenciais (clientId e clientSecret) no painel administrativo.
Configure seu ambiente para enviar o certificado no header X-SSL-Client-Cert.
O certificado X.509 deve estar vinculado à sua conta antes de ser utilizado. Certificados não vinculados serão rejeitados mesmo que válidos.
Endpoint de Autenticação
POST /api/auth/token
Gera um token JWT de acesso válido por 30 minutos (1800 segundos).
O certificado X.509 deve ser enviado URL-encoded no header X-SSL-Client-Cert. O sistema valida o fingerprint SHA256 do certificado contra os registros vinculados à conta.
Request
curl -X POST https://api.avista.global/api/auth/token \
-H "Content-Type: application/json" \
-H "X-SSL-Client-Cert: -----BEGIN%20CERTIFICATE-----%0AMIIB..." \
-d '{
"clientId": "account-93-550e8400",
"clientSecret": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}'Response (201 Created)
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 1800
}Exemplo Prático: Node.js
Instalação
npm install axiosCódigo Completo
const axios = require('axios');
const fs = require('fs');
// Carregar certificado X.509
const certificate = fs.readFileSync('./client-cert.pem', 'utf8');
const encodedCert = encodeURIComponent(certificate);
// Configuração da requisição
const config = {
method: 'post',
url: 'https://api.avista.global/api/auth/token',
headers: {
'Content-Type': 'application/json',
'X-SSL-Client-Cert': encodedCert
},
data: {
clientId: process.env.AVISTA_CLIENT_ID,
clientSecret: process.env.AVISTA_CLIENT_SECRET
}
};
// Fazer requisição
async function getToken() {
try {
const response = await axios(config);
console.log('Token obtido com sucesso!');
console.log('Expira em:', response.data.expires_in, 'segundos');
return response.data.access_token;
} catch (error) {
console.error('Erro ao obter token:', error.response?.data || error.message);
throw error;
}
}
getToken();Exemplo Prático: Python
Instalação
pip install requestsCódigo Completo
import os
import requests
import urllib.parse
# Carregar e codificar certificado
with open('client-cert.pem', 'r') as f:
certificate = f.read()
encoded_cert = urllib.parse.quote(certificate)
# Configuração da requisição
url = 'https://api.avista.global/api/auth/token'
headers = {
'Content-Type': 'application/json',
'X-SSL-Client-Cert': encoded_cert
}
payload = {
'clientId': os.environ.get('AVISTA_CLIENT_ID'),
'clientSecret': os.environ.get('AVISTA_CLIENT_SECRET')
}
# Fazer requisição
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
print('Token obtido com sucesso!')
print(f"Expira em: {data['expires_in']} segundos")
except requests.exceptions.RequestException as e:
print(f'Erro ao obter token: {e}')
if hasattr(e.response, 'text'):
print(f'Resposta: {e.response.text}')Utilizando o Token
Após obter o token, inclua-o no header Authorization de todas as requisições:
curl -X GET https://api.avista.global/api/balance \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Renovação de Token
Tokens expiram após 30 minutos. Implemente lógica de renovação automática em sua aplicação para evitar interrupções.
Estratégia Recomendada
class TokenManager {
constructor(clientId, clientSecret, certificatePath) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.certificatePath = certificatePath;
this.token = null;
this.expiresAt = null;
}
async getValidToken() {
// Verifica se o token ainda é válido (com margem de 30 segundos)
if (this.token && this.expiresAt && Date.now() < this.expiresAt - 30000) {
return this.token;
}
// Renova o token
return await this.refreshToken();
}
async refreshToken() {
const response = await this.requestNewToken();
this.token = response.access_token;
this.expiresAt = Date.now() + (response.expires_in * 1000);
return this.token;
}
async requestNewToken() {
const fs = require('fs');
const axios = require('axios');
const certificate = fs.readFileSync(this.certificatePath, 'utf8');
const encodedCert = encodeURIComponent(certificate);
const response = await axios.post('https://api.avista.global/api/auth/token', {
clientId: this.clientId,
clientSecret: this.clientSecret
}, {
headers: {
'Content-Type': 'application/json',
'X-SSL-Client-Cert': encodedCert
}
});
return response.data;
}
}
// Uso
const tokenManager = new TokenManager(
process.env.AVISTA_CLIENT_ID,
process.env.AVISTA_CLIENT_SECRET,
'./client-cert.pem'
);
// Em qualquer requisição
const token = await tokenManager.getValidToken();Validação do Certificado
O sistema realiza as seguintes validações no certificado:
- Formato PEM válido: O certificado deve estar no formato PEM e URL-encoded
- Vinculação à conta: O fingerprint SHA256 do certificado deve estar registrado e vinculado à sua conta
- Correspondência de credenciais: O certificado deve pertencer à mesma conta das credenciais OAuth
Certificados não vinculados ou vinculados a outra conta serão rejeitados, mesmo que tecnicamente válidos.
Respostas de erro
Todos os erros retornados por POST /api/auth/token seguem o mesmo formato:
{
"statusCode": 400,
"timestamp": "2026-04-24T16:59:25.577Z",
"path": "/api/auth/token",
"method": "POST",
"code": "PUB_CERT_MALFORMED_PEM",
"message": "Certificate could not be parsed",
"userMessage": "The provided certificate is malformed.",
"details": {
"hint": "The PEM could not be parsed. Common cause: '+' characters in the base64 body must be URL-encoded as '%2B', not '%20'."
},
"errorId": "81421e7a5afbd9cf25212f51dac08da1"
}code: identificador estável legível por máquina — faça o branch com base neste campo.message: descrição técnica resumida (em inglês, voltada para logs).userMessage: descrição voltada ao usuário final (em inglês).details.hint: orientação prática para desenvolvedores integrando a API.errorId: identificador único da requisição — informe-o ao contatar o suporte.
Referência de códigos de erro
PUB_CERT_HEADER_MISSING (400)
O cabeçalho X-SSL-Client-Cert não foi enviado na requisição.
Como resolver: inclua o cabeçalho com o certificado PEM codificado em URL. Se você estiver atrás de um gateway que faz terminação TLS (NGINX/ALB), confirme que ele está configurado para encaminhar $ssl_client_escaped_cert como X-SSL-Client-Cert.
PUB_CERT_MALFORMED_PEM (400)
O certificado não pôde ser analisado no gateway ou no serviço upstream.
Como resolver: a causa mais comum é codificação de URL incorreta — os caracteres + que aparecem naturalmente no corpo base64 devem ser codificados como %2B, não %20. Alguns encoders (por exemplo, url.QueryEscape do Go seguido de uma substituição ingênua + → %20) corrompem o base64 dessa forma. Use url.PathEscape no Go, ou verifique se seu encoder produz %2B para + literal.
PUB_REQUEST_BODY_INVALID (400)
O corpo JSON não satisfez o esquema (campo ausente, tipo errado, ou clientId não é um UUID v4). Verifique details.violations para identificar o(s) campo(s) com problema.
PUB_CERT_NOT_YET_VALID (401)
A data notBefore do certificado está no futuro.
Como resolver: sincronize o relógio do sistema via NTP. Se a data realmente estiver no futuro, aguarde ou solicite um novo certificado.
PUB_CERT_EXPIRED (401)
A data notAfter do certificado expirou.
Como resolver: solicite um novo certificado ao suporte e substitua-o na sua integração.
PUB_CERT_NOT_REGISTERED (401)
A impressão digital SHA-256 do certificado não está no registro — ele nunca foi registrado ou foi revogado.
Como resolver: calcule a impressão digital local com openssl x509 -noout -fingerprint -sha256 -in cert.pem e compare com a impressão digital recebida no onboarding. Se coincidirem, contate o suporte — o certificado pode ter sido revogado. Se diferirem, contate o suporte para registrar o novo certificado.
Nota: certificados revogados atualmente são reportados como
PUB_CERT_NOT_REGISTERED. Um código dedicadoPUB_CERT_REVOKEDpode ser introduzido em uma release futura; até lá, este código cobre ambos os casos e a equipe de suporte distingue.
PUB_CERT_NOT_AUTHORIZED_FOR_ACCOUNT (403)
O certificado é válido e registrado, mas não está vinculado à conta que possui o clientId informado.
Como resolver: verifique se você está usando o par certificado/clientId correto para este ambiente.
PUB_INVALID_CREDENTIALS (401)
O clientId não é reconhecido, ou o clientSecret não corresponde. A plataforma deliberadamente não diferencia esses casos (resistência a enumeração).
Como resolver: verifique tanto o clientId quanto o clientSecret. Se o problema persistir, contate o suporte com o errorId presente na resposta.
PUB_AUTH_UPSTREAM_UNAVAILABLE (503)
O serviço de autenticação upstream está temporariamente inacessível.
Como resolver: tente novamente com backoff exponencial (por exemplo, 1s, 2s, 4s). Se o problema persistir por mais de um minuto, contate o suporte.
PUB_AUTH_UPSTREAM_ERROR (502)
O upstream retornou um erro não reconhecido.
Como resolver: contate o suporte com o errorId — esse código indica um problema no servidor que investigaremos.
Notas de migração (para integrações existentes)
Se sua integração verifica diretamente os códigos antigos PUB_INVALID_CERTIFICATE ou PUB_TOKEN_GENERATION_FAILED, eles foram renomeados:
| Código antigo | Novo(s) código(s) |
|---|---|
PUB_INVALID_CERTIFICATE | PUB_CERT_MALFORMED_PEM (apenas falhas de análise PEM — outros problemas de certificado agora têm códigos dedicados) |
PUB_TOKEN_GENERATION_FAILED | PUB_AUTH_UPSTREAM_UNAVAILABLE (503) para indisponibilidade transitória, PUB_AUTH_UPSTREAM_ERROR (502) para erros upstream não reconhecidos |
PUB_INVALID_CREDENTIALS é mantido, mas com significado mais estrito — agora dispara apenas quando o clientId ou clientSecret está errado, nunca como coringa para qualquer 401 do serviço de autenticação.
Resolução de problemas
"Meu certificado funciona com openssl mas a API retorna PUB_CERT_MALFORMED_PEM"
O arquivo PEM em disco está correto — o problema está no transporte. Na maioria dos casos, é o bug de codificação de URL + → %20. Imprima o valor exato do cabeçalho enviado pelo seu cliente, decodifique-o e compare byte a byte com o PEM original. O base64 deve preservar cada + literal como %2B.
"Recebo ocasionalmente PUB_CERT_NOT_YET_VALID"
O relógio do sistema derivou além da data notBefore do seu certificado. Configure o NTP e garanta que o relógio do host esteja com poucos segundos de diferença do UTC. Contêineres podem herdar a deriva de relógio do host — verifique com date -u dentro do contêiner.
"Recebo PUB_CERT_NOT_REGISTERED após rotacionar o certificado"
Calcule a impressão digital do novo certificado e confirme que ele foi registrado: openssl x509 -noout -fingerprint -sha256 -in new-cert.pem. Se a impressão digital diferir do que temos registrado, solicite o registro ao suporte antes de realizar a transição.