Autenticación
Descripción General
La API de Avista utiliza autenticación basada en OAuth 2.0 con certificados X.509 (mTLS). Este modelo de seguridad en capas garantiza que solo los clientes autorizados con certificados válidos puedan acceder a la API.
¿Por qué mTLS?
mTLS (mutual TLS) ofrece una seguridad superior en comparación con tokens simples:
- Autenticación mutua: Tanto el cliente como el servidor se autentican entre sí
- No repudio: Los certificados vinculados a la cuenta garantizan la trazabilidad
- Protección contra robo de credenciales: Incluso con clientId/clientSecret filtrados, el atacante necesitaría el certificado
Requisitos Previos
Antes de comenzar, necesitará:
Obtenga su certificado de cliente a través del portal de Avista. El certificado debe estar en formato PEM y será vinculado a su cuenta.
Solicite sus credenciales (clientId y clientSecret) en el panel de administración.
Configure su entorno para enviar el certificado en el encabezado X-SSL-Client-Cert.
El certificado X.509 debe estar vinculado a su cuenta antes de usarse. Los certificados no vinculados serán rechazados aunque sean técnicamente válidos.
Endpoint de Autenticación
POST /api/auth/token
Genera un token de acceso JWT válido por 30 minutos (1800 segundos).
El certificado X.509 debe enviarse codificado en URL en el encabezado X-SSL-Client-Cert. El sistema valida la huella digital SHA256 del certificado contra los registros vinculados a la cuenta.
Solicitud
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"
}'Respuesta (201 Created)
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 1800
}Ejemplo Práctico: Node.js
Instalación
npm install axiosCódigo Completo
const axios = require('axios');
const fs = require('fs');
// Load X.509 certificate
const certificate = fs.readFileSync('./client-cert.pem', 'utf8');
const encodedCert = encodeURIComponent(certificate);
// Request configuration
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
}
};
// Make request
async function getToken() {
try {
const response = await axios(config);
console.log('Token obtained successfully!');
console.log('Expires in:', response.data.expires_in, 'seconds');
return response.data.access_token;
} catch (error) {
console.error('Error obtaining token:', error.response?.data || error.message);
throw error;
}
}
getToken();Ejemplo Práctico: Python
Instalación
pip install requestsCódigo Completo
import os
import requests
import urllib.parse
# Load and encode certificate
with open('client-cert.pem', 'r') as f:
certificate = f.read()
encoded_cert = urllib.parse.quote(certificate)
# Request configuration
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')
}
# Make request
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
print('Token obtained successfully!')
print(f"Expires in: {data['expires_in']} seconds")
except requests.exceptions.RequestException as e:
print(f'Error obtaining token: {e}')
if hasattr(e.response, 'text'):
print(f'Response: {e.response.text}')Uso del Token
Después de obtener el token, inclúyalo en el encabezado Authorization de todas las solicitudes:
curl -X GET https://api.avista.global/api/balance \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Renovación del Token
Los tokens expiran después de 30 minutos. Implemente lógica de renovación automática en su aplicación para evitar interrupciones.
Estrategia Recomendada
class TokenManager {
constructor(clientId, clientSecret, certificatePath) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.certificatePath = certificatePath;
this.token = null;
this.expiresAt = null;
}
async getValidToken() {
// Check if the token is still valid (with a 30-second margin)
if (this.token && this.expiresAt && Date.now() < this.expiresAt - 30000) {
return this.token;
}
// Renew the 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;
}
}
// Usage
const tokenManager = new TokenManager(
process.env.AVISTA_CLIENT_ID,
process.env.AVISTA_CLIENT_SECRET,
'./client-cert.pem'
);
// In any request
const token = await tokenManager.getValidToken();Validación del Certificado
El sistema realiza las siguientes validaciones sobre el certificado:
- Formato PEM válido: El certificado debe estar en formato PEM y codificado en URL
- Vinculación a la cuenta: La huella digital SHA256 del certificado debe estar registrada y vinculada a su cuenta
- Coincidencia de credenciales: El certificado debe pertenecer a la misma cuenta que las credenciales OAuth
Los certificados que no estén vinculados o estén vinculados a otra cuenta serán rechazados, aunque sean técnicamente válidos.
Errores Comunes
400 Bad Request
Causa: Certificado ausente o malformado
{
"statusCode": 400,
"message": "Certificado ausente no header X-SSL-Client-Cert"
}Solución: Verifique que:
- El certificado esté en formato PEM
- El certificado esté codificado en URL (use
encodeURIComponent()) - El encabezado
X-SSL-Client-Certesté presente en la solicitud
401 Unauthorized
Causa: Credenciales inválidas o certificado no autorizado
{
"statusCode": 401,
"message": "Credenciais inválidas ou certificado inválido"
}Solución: Verifique que:
- El
clientIdyclientSecretsean correctos - El certificado esté vinculado a su cuenta en el portal de Avista
- El certificado coincida con las credenciales OAuth utilizadas
403 Forbidden
Causa: Certificado no vinculado a la cuenta
{
"statusCode": 403,
"message": "Certificado não vinculado à conta"
}Solución: Contacte al soporte de Avista para vincular el certificado a su cuenta.