Contexto de usuario en apps del Marketplace (Shared Secret)
Contexto de usuario en apps del Marketplace
La plataforma ofrece un mecanismo seguro para acceder a la información del usuario autenticado mediante tokens firmados. Esta guia explica como generar y usar una llave Shared Secret para acceder a ese contexto de forma segura — útil si tu app necesita saber, por ejemplo, que persona de tu equipo esta usando la integración en ese momento.
Configurar el Shared Secret
Generar una llave Shared Secret
Primero necesitas generar una llave Shared Secret para tu aplicación:
- Ve a la Configuración avanzada de tu aplicación
- Entra a la sección de Auth
- En Shared Secret, da clic en Generar para crear tu llave
Metodos de implementacion en el frontend
Hay dos formas de acceder a estos datos desde tu frontend, según donde corra tu código:
1. Implementacion con JavaScript personalizado
Si estas usando JavaScript personalizado inyectado en páginas de la plataforma, usa el metodo exposeSessionDetails:
async function getUserData() {
try {
// APP_ID es el identificador unico de tu aplicacion
const encryptedUserData = await window.exposeSessionDetails(APP_ID)
// Envia estos datos cifrados a tu backend para descifrarlos
const response = await fetch('your-backend-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ encryptedData: encryptedUserData })
})
const userData = await response.json()
return userData
} catch (error) {
console.error('Failed to fetch session details:', error)
throw error
}
}
2. Implementacion en páginas personalizadas (Custom Pages)
Si estas intentando obtener el contexto de usuario dentro de una página personalizada, usa el metodo postMessage para comunicarte con la ventana padre:
async function getUserData() {
try {
const encryptedUserData = await new Promise((resolve) => {
// Solicita los datos del usuario a la ventana padre
window.parent.postMessage({ message: 'REQUEST_USER_DATA' }, '*')
// Escucha la respuesta
const messageHandler = ({ data }) => {
if (data.message === 'REQUEST_USER_DATA_RESPONSE') {
window.removeEventListener('message', messageHandler)
resolve(data.payload)
}
}
window.addEventListener('message', messageHandler)
})
// Envia los datos cifrados a tu backend para descifrarlos
const response = await fetch('your-backend-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ encryptedData: encryptedUserData })
})
const userData = await response.json()
return userData
} catch (error) {
console.error('Failed to fetch user data:', error)
throw error
}
}
Implementacion en el backend
Sin importar que metodo de frontend uses, el proceso de descifrado en el backend es el mismo:
const CryptoJS = require('crypto-js')
function decryptUserData(encryptedUserData, sharedSecretKey) {
try {
const decrypted = CryptoJS.AES.decrypt(encryptedUserData, sharedSecretKey).toString(CryptoJS.enc.Utf8)
return JSON.parse(decrypted)
} catch (error) {
throw new Error('Failed to decrypt user data')
}
}
// Ejemplo de endpoint con Express
app.post('/decrypt-user-data', (req, res) => {
try {
const { encryptedData } = req.body
const userData = decryptUserData(encryptedData, process.env.GHL_APP_SHARED_SECRET)
res.json(userData)
} catch (error) {
res.status(400).json({ error: 'Failed to decrypt user data' })
}
})
Estructura de los datos descifrados
Tras descifrarlos, los datos se devuelven como un objeto JSON con la información del usuario. La estructura varia según si se accede desde el contexto de una Agencia o de una Sede.
Contexto de Agencia
{
"userId": "MKQJ7wOVVmNOMvrnKKKK", // Identificador unico del usuario
"companyId": "GNb7aIv4rQFVb9iwNl5K", // Identificador unico de la agencia/empresa
"role": "admin", // Rol del usuario en el sistema
"type": "agency", // Indica que es un usuario de agencia
"userName": "John Doe", // Nombre completo del usuario
"email": "johndoe@gmail.com" // Correo del usuario
}
Contexto de Sede
Al acceder desde el contexto de una sede, los datos descifrados incluyen además el campo activeLocation:
{
"userId": "MKQJ7wOVVmNOMvrnKKKK", // Identificador unico del usuario
"companyId": "GNb7aIv4rQFVb9iwNl5K", // Identificador unico de la agencia/empresa
"role": "admin", // Rol del usuario en el sistema
"type": "agency", // Indica que es un usuario de agencia
"activeLocation": "yLKVZpNppIdYpah4RjNE", // Identificador unico de la sede activa
"userName": "John Doe", // Nombre completo del usuario
"email": "johndoe@gmail.com" // Correo del usuario
}
Descripcion de los campos
| Campo | Tipo | Descripcion |
|---|---|---|
| userId | string | Identificador único del usuario |
| companyId | string | Identificador único de la agencia/empresa |
| role | string | Rol del usuario en el sistema (ej. 'admin', 'user') |
| type | string | Tipo de contexto ('agency' o 'location') |
| activeLocation | string | (Solo en contexto de sede) Identificador único de la sede activa |
| userName | string | Nombre completo del usuario |
| string | Correo del usuario |
Implementacion de referencia
Para un ejemplo completo, puedes consultar el repositorio de plantilla de apps del Marketplace:
Plantilla de app del Marketplace de GoHighLevel
La implementacion relevante esta en el endpoint /decrypt-sso de la plantilla.
Consideraciones de seguridad
- Nunca expongas tu llave Shared Secret en código del lado del cliente.
- Realiza siempre el descifrado en tu backend.
- Guarda tu llave Shared Secret de forma segura usando variables de entorno.
- Usa HTTPS para toda comunicación entre tu frontend y tu backend.
- Rota tus llaves Shared Secret periodicamente para mayor seguridad.
