Skip to content

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


Última atualização: 13 de fevereiro de 2026