Exemplos de Integração JWT - Frontend¶
📱 Como Enviar JWT Token para o Agente iFriend¶
Este documento contém exemplos práticos de como integrar o JWT token nas diferentes plataformas de messaging.
🌐 WebChat (Widget no Site)¶
Opção 1: JWT no Header HTTP (Recomendado)¶
// Frontend - Widget de chat
class IFriendChatWidget {
constructor(config) {
this.apiUrl = config.apiUrl || 'https://api.ifriend-bot.com';
this.jwtToken = config.jwtToken; // Token do usuário autenticado
}
async sendMessage(message) {
const response = await fetch(`${this.apiUrl}/webchat/message`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.jwtToken}`, // ✅ JWT aqui
},
body: JSON.stringify({
user_id: this.getUserId(),
session_id: this.getSessionId(),
message: message,
metadata: {
page_url: window.location.href,
user_agent: navigator.userAgent,
}
})
});
return await response.json();
}
getUserId() {
// Pode vir do token decodificado ou de outra fonte
return this.decodeToken(this.jwtToken).sub;
}
getSessionId() {
// Mantém sessão no localStorage
let sessionId = localStorage.getItem('ifriend_session_id');
if (!sessionId) {
sessionId = this.generateUUID();
localStorage.setItem('ifriend_session_id', sessionId);
}
return sessionId;
}
decodeToken(token) {
// Decodifica JWT (apenas para ler claims, não valida)
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
}
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
// Uso
const token = getUserJWTToken(); // Função que obtém token do sistema de auth do site
const chatWidget = new IFriendChatWidget({
apiUrl: 'https://api.ifriend-bot.com',
jwtToken: token
});
await chatWidget.sendMessage('Quero reservar uma experiência em Roma');
Opção 2: JWT no Body da Requisição¶
async function sendChatMessage(message, jwtToken) {
const response = await fetch('https://api.ifriend-bot.com/webchat/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: 'user123',
session_id: 'session456',
message: message,
jwt_token: jwtToken, // ✅ JWT no body
metadata: {
page_url: window.location.href,
}
})
});
return await response.json();
}
// Uso
const token = getUserJWTToken();
await sendChatMessage('Olá!', token);
Opção 3: JWT no Metadata¶
async function sendChatMessage(message, jwtToken) {
const response = await fetch('https://api.ifriend-bot.com/webchat/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: 'user123',
session_id: 'session456',
message: message,
metadata: {
jwt_token: jwtToken, // ✅ JWT no metadata
page_url: window.location.href,
}
})
});
return await response.json();
}
🔄 SSE (Server-Sent Events)¶
Exemplo com EventSource¶
class IFriendSSEChat {
constructor(apiUrl, jwtToken) {
this.apiUrl = apiUrl;
this.jwtToken = jwtToken;
this.streamId = null;
}
async sendMessage(message) {
// 1. Inicia stream com POST
const response = await fetch(`${this.apiUrl}/sse/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.jwtToken}`, // ✅ JWT aqui
},
body: JSON.stringify({
user_id: this.getUserId(),
session_id: this.getSessionId(),
message: message,
})
});
const data = await response.json();
this.streamId = data.stream_id;
// 2. Conecta ao stream SSE
return this.connectToStream(this.streamId);
}
connectToStream(streamId) {
return new Promise((resolve, reject) => {
const eventSource = new EventSource(
`${this.apiUrl}/sse/stream/${streamId}`
);
let fullResponse = '';
eventSource.addEventListener('text_chunk', (event) => {
const data = JSON.parse(event.data);
fullResponse += data.text;
this.onChunk(data.text); // Callback para UI
});
eventSource.addEventListener('tool_call', (event) => {
const data = JSON.parse(event.data);
this.onToolCall(data); // Feedback de progresso
});
eventSource.addEventListener('complete', (event) => {
const data = JSON.parse(event.data);
eventSource.close();
resolve(data);
});
eventSource.addEventListener('error', (event) => {
eventSource.close();
reject(new Error('Stream error'));
});
});
}
onChunk(text) {
// Atualiza UI com chunk de texto
console.log('Chunk:', text);
}
onToolCall(data) {
// Mostra feedback na UI (ex: "🔍 Buscando produtos...")
console.log('Tool:', data.tool_name, data.status);
}
getUserId() {
const decoded = this.decodeToken(this.jwtToken);
return decoded.sub;
}
getSessionId() {
let sessionId = sessionStorage.getItem('ifriend_session_id');
if (!sessionId) {
sessionId = crypto.randomUUID();
sessionStorage.setItem('ifriend_session_id', sessionId);
}
return sessionId;
}
decodeToken(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
}
}
// Uso
const token = getUserJWTToken();
const chat = new IFriendSSEChat('https://api.ifriend-bot.com', token);
await chat.sendMessage('Quais experiências você tem em Paris?');
📱 WhatsApp (Via Webhook)¶
Estratégia: Autenticação Prévia via Web¶
Como WhatsApp não permite enviar JWT diretamente nas mensagens, é necessário um passo de autenticação prévia:
1. Usuário Autentica no Site (One-time)¶
// Frontend - Página de autenticação do WhatsApp
async function linkWhatsAppAccount(phoneNumber, jwtToken) {
const response = await fetch('https://api.ifriend-bot.com/whatsapp/link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken}`,
},
body: JSON.stringify({
phone_number: phoneNumber, // Ex: "+5511999999999"
})
});
const data = await response.json();
if (data.success) {
alert('WhatsApp vinculado! Agora você pode conversar pelo WhatsApp.');
}
}
// Uso
const token = getUserJWTToken();
await linkWhatsAppAccount('+5511999999999', token);
2. Backend Armazena Mapeamento¶
# Backend - Endpoint para vincular WhatsApp
@app.post("/whatsapp/link")
async def link_whatsapp(request: Request):
# Extrai JWT do header
auth_header = request.headers.get("Authorization", "")
jwt_token = auth_header.replace("Bearer ", "")
# Valida JWT
user_context = await jwt_manager.get_user_context(jwt_token)
# Parse body
data = await request.json()
phone_number = data["phone_number"]
# Armazena mapeamento phone → JWT no Redis/DB
await redis_client.set(
f"whatsapp:jwt:{phone_number}",
jwt_token,
ex=86400 * 30 # 30 dias
)
return {
"success": True,
"message": "WhatsApp linked successfully"
}
3. WhatsApp Adapter Faz Lookup¶
# messaging/adapters/whatsapp_official_adapter.py
async def parse_webhook_request(self, request: Request) -> Optional[IncomingMessage]:
data = await request.json()
# Extrai número de telefone da mensagem WhatsApp
phone_number = self.extract_phone_number(data)
# Lookup JWT armazenado para este número
jwt_token = await self.lookup_user_jwt(phone_number)
metadata = {
"phone_number": phone_number,
}
if jwt_token:
metadata["jwt_token"] = jwt_token
return IncomingMessage(
platform="whatsapp",
user_id=phone_number,
channel_id=phone_number,
thread_id=self.extract_thread_id(data),
text=self.extract_message_text(data),
metadata=metadata
)
async def lookup_user_jwt(self, phone_number: str) -> Optional[str]:
"""Busca JWT armazenado para este número de telefone"""
jwt_token = await redis_client.get(f"whatsapp:jwt:{phone_number}")
return jwt_token.decode() if jwt_token else None
4. Usuário Conversa Normalmente no WhatsApp¶
Usuário (via WhatsApp): "Quero reservar uma tour em Roma"
↓
WhatsApp webhook → WhatsAppAdapter
↓
Adapter faz lookup JWT (phone → token)
↓
Adiciona JWT ao metadata
↓
Processa normalmente com autenticação do usuário
💬 Slack¶
Estratégia: Comando de Autenticação¶
Similar ao WhatsApp, Slack não envia JWT automaticamente.
1. Comando Slash para Autenticar¶
Usuário no Slack: /ifriend-auth eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
↓
Slack envia webhook para: /webhook/slack/commands
↓
Backend valida JWT e armazena mapeamento slack_user_id → JWT
↓
Responde: "✅ Autenticado! Agora você pode usar @ifriend-bot"
2. Backend - Handler do Comando¶
@app.post("/webhook/slack/commands")
async def slack_command(request: Request):
form_data = await request.form()
command = form_data.get("command") # /ifriend-auth
text = form_data.get("text") # JWT token
user_id = form_data.get("user_id") # Slack user ID
if command == "/ifriend-auth":
jwt_token = text.strip()
try:
# Valida JWT
user_context = await jwt_manager.get_user_context(jwt_token)
# Armazena mapeamento
await redis_client.set(
f"slack:jwt:{user_id}",
jwt_token,
ex=86400 * 30 # 30 dias
)
return {
"response_type": "ephemeral", # Só o usuário vê
"text": f"✅ Autenticado como {user_context.email}!"
}
except Exception as e:
return {
"response_type": "ephemeral",
"text": f"❌ Token inválido: {str(e)}"
}
3. Mensagens Subsequentes Usam JWT¶
Usuário no Slack: "@ifriend-bot quero reservar uma experiência"
↓
Slack webhook → SlackAdapter
↓
Adapter faz lookup JWT (slack_user_id → token)
↓
Processa com autenticação do usuário
🔐 Geração do JWT Token (Backend do Site)¶
Exemplo: Node.js + jsonwebtoken¶
const jwt = require('jsonwebtoken');
function generateUserJWT(user) {
const payload = {
sub: user.id, // User ID
email: user.email,
name: user.name,
roles: user.roles, // Ex: ["traveler", "premium"]
tenant_id: user.tenant_id, // Opcional: multi-tenancy
};
const secret = process.env.JWT_SECRET_KEY;
const token = jwt.sign(payload, secret, {
expiresIn: '24h',
issuer: 'https://theifriend.com',
audience: 'ifriend-api',
});
return token;
}
// Uso
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
// Autentica usuário
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Gera JWT
const token = generateUserJWT(user);
res.json({
token: token,
user: {
id: user.id,
email: user.email,
name: user.name,
}
});
});
Exemplo: Python + PyJWT¶
import jwt
from datetime import datetime, timedelta
def generate_user_jwt(user: dict) -> str:
payload = {
"sub": user["id"],
"email": user["email"],
"name": user["name"],
"roles": user.get("roles", []),
"tenant_id": user.get("tenant_id"),
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=24),
"iss": "https://theifriend.com",
"aud": "ifriend-api",
}
secret = os.getenv("JWT_SECRET_KEY")
token = jwt.encode(payload, secret, algorithm="HS256")
return token
# Uso
@app.post("/api/auth/login")
async def login(credentials: LoginRequest):
user = await authenticate_user(credentials.email, credentials.password)
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
token = generate_user_jwt(user)
return {
"token": token,
"user": {
"id": user["id"],
"email": user["email"],
"name": user["name"],
}
}
🧪 Testando JWT Integration¶
1. Teste Manual com cURL¶
# Gera um JWT de teste (use https://jwt.io ou seu backend)
JWT_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzc4OSIsImVtYWlsIjoidXN1YXJpb0BleGFtcGxlLmNvbSIsIm5hbWUiOiJKb8OjbyBTaWx2YSIsInJvbGVzIjpbInRyYXZlbGVyIl0sImlhdCI6MTcwNzg2OTQwMCwiZXhwIjoxNzA3OTU1ODAwLCJpc3MiOiJodHRwczovL3RoZWlmcmllbmQuY29tIiwiYXVkIjoiaWZyaWVuZC1hcGkifQ.SIGNATURE"
# Teste WebChat com JWT no header
curl -X POST https://api.ifriend-bot.com/webchat/message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT_TOKEN" \
-d '{
"user_id": "user_789",
"session_id": "test_session_1",
"message": "Olá! Quero reservar uma experiência em Roma"
}'
# Teste WebChat com JWT no body
curl -X POST https://api.ifriend-bot.com/webchat/message \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_789",
"session_id": "test_session_2",
"message": "Olá!",
"jwt_token": "'"$JWT_TOKEN"'"
}'
2. Verificar Logs do Backend¶
# Você deve ver logs como:
[2026-02-13 10:30:45] [INFO] [JWT] Usuário autenticado via JWT: usuario@example.com (id: user_789)
[2026-02-13 10:30:45] [INFO] [buscar_usuario_tool] Usando token JWT do contexto
[2026-02-13 10:30:45] [INFO] [buscar_usuario_tool] ✅ Usuário encontrado: 789 - João Silva
3. Teste Sem JWT (Fallback)¶
# Enviar mensagem sem JWT (deve usar auth default)
curl -X POST https://api.ifriend-bot.com/webchat/message \
-H "Content-Type: application/json" \
-d '{
"user_id": "anonymous_user",
"message": "Olá!"
}'
# Logs devem mostrar:
# [INFO] [AuthTokenManager] Usando token em cache (auth default)
📊 Monitoramento¶
Métricas Importantes¶
# Adicionar métricas para monitorar uso de JWT
from prometheus_client import Counter, Histogram
# Contador de mensagens com/sem JWT
messages_with_jwt = Counter(
'ifriend_messages_with_jwt_total',
'Total de mensagens processadas com JWT',
['platform'] # webchat, sse, whatsapp, slack
)
messages_without_jwt = Counter(
'ifriend_messages_without_jwt_total',
'Total de mensagens processadas sem JWT (auth default)',
['platform']
)
# No processor:
if jwt_context:
messages_with_jwt.labels(platform=message.platform).inc()
else:
messages_without_jwt.labels(platform=message.platform).inc()
🚨 Troubleshooting¶
Problema 1: JWT Expirado¶
Sintoma: Erro "Token expirado" nos logs
Solução: - Frontend deve renovar token antes de expirar - Implementar refresh token se necessário
// Verifica se token vai expirar em breve
function isTokenExpiringSoon(token, minutesThreshold = 5) {
const decoded = decodeToken(token);
const exp = decoded.exp * 1000; // exp em segundos, converter para ms
const now = Date.now();
const timeUntilExpiry = exp - now;
const thresholdMs = minutesThreshold * 60 * 1000;
return timeUntilExpiry < thresholdMs;
}
// Usar antes de enviar mensagem
if (isTokenExpiringSoon(currentToken)) {
currentToken = await refreshToken();
}
Problema 2: JWT Inválido¶
Sintoma: Erro "Invalid signature" ou "Token malformed"
Solução: - Verificar se JWT_SECRET_KEY é o mesmo no frontend e backend - Verificar se token está completo (3 partes separadas por ponto)
Problema 3: WhatsApp/Slack Não Encontra JWT¶
Sintoma: Logs mostram "JWT context not found" para WhatsApp/Slack
Solução:
- Verificar se usuário fez autenticação prévia (/whatsapp/link ou /ifriend-auth)
- Verificar se Redis tem o mapeamento: redis-cli get "whatsapp:jwt:+5511999999999"
📚 Recursos Adicionais¶
- JWT.io - Debugger - Decodificar e validar tokens
- JWT Best Practices
- OWASP JWT Cheat Sheet
Última atualização: 13 de fevereiro de 2026