Segurança

Segurança em APIs REST do Protheus: Autenticação, Validação e Boas Práticas

APIs são o novo vetor de ataque. No Protheus, expor dados sem segurança é convidar problema. Veja como proteger suas APIs REST.

Cada API REST exposta é uma porta aberta. Se essa porta não tem cadeado, trancou ou câmera, alguém vai entrar. No contexto Protheus, uma API insegura pode vazar dados financeiros, estoque ou dados sensíveis de clientes.

Autenticação: quem está acessando

A primeira camada de segurança é saber quem está fazendo a requisição. Existem três níveis de autenticação:

Basic Auth (não use em produção)

Basic Auth — Apenas para desenvolvimento
// O problema: credenciais em Base64 (não é criptografia!)
// Header: Authorization: Basic dXNlcjpwYXNzd29yZA==

// ADVPL — Validação Basic Auth (NÃO recomendado)
// Dentro do WsMethod: If !ValBasicAuth(::GetHeader("Authorization")) ; Return .F. ; EndIf

Static Function ValBasicAuth(cAuth)
  Local cUser, cPass

  If Empty(cAuth) .Or. Upper(Left(cAuth, 6)) != "BASIC "
    SetRestFault(401, "Autenticação necessária")
    Return .F.
  EndIf

  cAuth := Decode64(SubStr(cAuth, 7))  // Decodifica Base64
  cUser := Left(cAuth, At(":", cAuth) - 1)
  cPass := SubStr(cAuth, At(":", cAuth) + 1)

  // ⚠ Credenciais em Base64 na rede!
  // ⚔ Usar apenas em desenvolvimento local, sempre sobre HTTPS
  Return ValidaUsuario(cUser, cPass)

API Keys (uso interno)

API Key — Para integrações internas
// API Key é uma string fixa gerada no servidor
// Header: X-API-Key: ak_live_1234567890abcdef

// ADVPL — Validação de API Key
Static Function ValApiKey(cKey)
  Local cKeyValida := SuperGetMv("ES_APIKEY", .F., "")

  // Regra: customização usa prefixo ES_, nunca MV_ (reservado ao padrão TOTVS)

  If Empty(cKey) .Or. cKey != cKeyValida
    SetRestFault(401, "API Key inválida")
    Return .F.
  EndIf

  Return .T.

OAuth2 / TOTVS Auth (recomendado)

Bearer Token — Padrão recomendado
// O cliente obtém um token via /auth/token com credenciais
// Header: Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// ADVPL — Validação Bearer Token
Static Function ValBearerToken(cAuth)
  Local cToken  := ""
  Local cSecret := SuperGetMv("ES_JWT_SECRET", .F., "")

  If Empty(cAuth) .Or. Upper(Left(cAuth, 7)) != "BEARER "
    SetRestFault(401, "Token não fornecido")
    Return .F.
  EndIf

  cToken := SubStr(cAuth, 8)

  // JWT exige lib própria (HMAC + validação de exp/iss) —
  // NÃO existe JwtVerify() nem DateUnix() nativos do ADVPL.
  If !JwtValidarLib(cToken, cSecret)  // wrapper da sua lib JWT
    SetRestFault(401, "Token inválido ou expirado")
    Return .F.
  EndIf

  // Identidade do usuário: propague por retorno/parâmetro —
  // NUNCA em variável global (AppServer é multi-thread:
  // um usuário herdaria a identidade de outro)
  Return .T.
Recomendação

Para APIs internas (microserviços Protheus entre si), API Key é suficiente. Para APIs expostas para apps externos ou parceiros, use OAuth2/TOTVS Auth com tokens JWT.

HTTPS obrigatório

Transmitir dados sem HTTPS é como mandar uma carta aberta pelo correio — qualquer um lê no caminho. TLS não é opcional.

Onde o TLS/CORS realmente terminam

O REST do Protheus roda no HTTP embutido do AppServer — o Apache não é o servidor da API. O TLS é configurado no AppServer (seções [HTTPVHOST]/SSL do AppServer.ini) ou no proxy reverso. O Apache abaixo entra apenas se houver proxy na frente do AppServer — configurar o servidor errado é erro clássico.

Apache (proxy reverso) — Redirect HTTP → HTTPS
# httpd.conf ou .htaccess
# Forçar HTTPS em todas as rotas
<VirtualHost *:80>
  ServerName api.elptecnologia.com.br
  Redirect permanent / https://api.elptecnologia.com.br/
</VirtualHost>

<VirtualHost *:443>
  ServerName api.elptecnologia.com.br

  SSLEngine on
  SSLCertificateFile /etc/ssl/certs/api.pem
  SSLCertificateKeyFile /etc/ssl/private/api.key

  # Headers de segurança
  Header always set Strict-Transport-Security "max-age=63072000"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "DENY"
</VirtualHost>

Validação de entrada

Nunca confie no que vem do cliente. Tudo que o frontend envia deve ser validado no backend — sem exceção.

ADVPL — Validação de entrada
Static Function ValidarEntrada(cBody)
  Local oBody := JsonObject():New()

  // 1. Parse do JSON — body chega via ::GetContent() no WsMethod
  If !oBody:FromJson(cBody)
    SetRestFault(400, "JSON inválido")
    Return NIL
  EndIf

  // 2. Campos obrigatórios
  If Empty(oBody["codigo"]) .Or. Empty(oBody["descricao"])
    SetRestFault(400, "Campos 'codigo' e 'descricao' são obrigatórios")
    Return NIL
  EndIf

  // 3. Tipos e tamanhos — validação real, não blacklist
  If Len(AllTrim(oBody["codigo"])) > 15
    SetRestFault(400, "Campo 'codigo' máximo 15 caracteres")
    Return NIL
  EndIf

  If ValType(oBody["preco"]) != "N" .Or. oBody["preco"] < 0
    SetRestFault(400, "Campo 'preco' deve ser numérico positivo")
    Return NIL
  EndIf

  // 4. SQL Injection: proteção estrutural, não busca de palavras.
  // Blacklist ("DROP/DELETE/INSERT" na descrição) é anti-padrão:
  // bypass com comentários/minúsculas e falso-positivo em nome próprio.
  // Nunca concatene entrada em query — query parametrizada via
  // FWExecStatement/ChangeQuery + validação de tipo/tamanho/whitelist.

  Return oBody

No WsMethod, o body chega via ::GetContent() e a função acima é chamada assim: oBody := ValidarEntrada(::GetContent()). Erros de validação respondem com SetRestFault(400, ...) + Return .F. no método.

Rate Limiting

Limitar requisições previne ataques de força bruta e abuso de recursos. Implemente um contador por IP com janela real de minuto e lock entre threads — o AppServer é multi-thread e contador global sem lock sofre corrida (dois requests incrementam ao mesmo tempo e o contador vaza):

ADVPL — Rate Limiting com janela + lock
Static Function CheckRateLimit(cIp)
  Local cKey    := "RATE_" + cIp + "_" + Left(Time(), 5)  // janela: minuto
  Local nCount  := 0
  Local nLimite := 100  // 100 req/minuto

  // Incrementa com lock — GetGlbValue/PutGlbValue com GlbLock/GlbUnlock
  GlbLock()
    nCount := Val(GetGlbValue(cKey)) + 1
    PutGlbValue(cKey, cValToChar(nCount))
  GlbUnlock()

  If nCount > nLimite
    SetRestFault(429, "Limite de requisições excedido (retry em 60s)")
    Return .F.
  EndIf

  Return .T.
Nota de engenharia

Globais (GetGlbValue/PutGlbValue) não expiram — a chave com o minuto cria a janela, mas a memória cresce. Para produção séria, prefira rate limiting no proxy/API gateway ou cache com expiração real. E o IP do cliente chega ao AppServer do proxy — use o header X-Forwarded-For validado quando houver proxy.

CORS: quem pode acessar sua API

Cross-Origin Resource Sharing controla quais domínios podem fazer requisições à sua API. Configure com cuidado — e no lugar certo: o CORS nativo do Protheus fica no AppServer.ini ([HTTPURI] CORSEnable=1 + AllowOrigin=...); o bloco Apache abaixo vale somente quando há proxy reverso:

Apache (proxy reverso) — Configuração CORS
# Permitir apenas domínios específicos
<Location /api/>
  Header always set Access-Control-Allow-Origin "https://app.elptecnologia.com.br"
  Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
  Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key"
  Header always set Access-Control-Max-Age "86400"

  # Preflight OPTIONS
  RewriteEngine On
  RewriteCond %{REQUEST_METHOD} OPTIONS
  RewriteRule ^(.*)$ $1 [R=200,L]
</Location>

# ⚠ NUNCA use: Access-Control-Allow-Origin: *
# Isso permite qualquer site acessar sua API

Logging e auditoria

Registre quem acessou, o quê, quando e de onde. Mas nunca registre dados sensíveis. E a tabela de auditoria precisa de índice, ordem e política de retenção — auditoria sem purga cresce infinito e vira scan.

ADVPL — Auditoria de APIs
Static Function LogAcesso(cEndpoint, cMetodo, cUsuario, cIp)
  // Área + ordem corretas antes de gravar (índice: ZA1_FILIAL+ZA1_DATA+ZA1_HORA)
  dbSelectArea("ZA1")
  ZA1->(DbSetOrder(1))

  RecLock("ZA1", .T.)
    ZA1->ZA1_DATA   := Date()
    ZA1->ZA1_HORA   := Time()
    ZA1->ZA1_USR    := cUsuario
    ZA1->ZA1_IP     := cIp
    ZA1->ZA1_ENDPNT := cEndpoint
    ZA1->ZA1_METODO := cMetodo
  MsUnLock()

  // ⚠ NUNCA registre: senhas, tokens, dados bancários
  // ✅ Registre: quem, quando, de onde, qual endpoint
  // Complemente com FWLogMsg (CA1004) e defina retenção/purge
  // da tabela (ex.: purga mensal de ZA1 > 12 meses)
  Return Nil
Atenção

A identidade do usuário (cUsuario) chega como parâmetro — nunca como variável global não declarada: em AppServer multi-thread, estado de request em global faz um usuário herdar a identidade de outro. E confira no SX3 o tamanho de ZA1_ENDPNT (campos de 10 caracteres truncam endpoints reais — dimensione o campo).

OWASP Top 10 no contexto Protheus

O OWASP Top 10 é a lista dos riscos de segurança mais críticos em aplicações web. Veja como cada um se aplica ao Protheus:

OWASPRisco no ProtheusPrevenção
InjectionSQL Injection via parâmetros não sanitizadosParâmetros compostos, validação de entrada
Broken AuthTokens sem expiração, senhas fracasJWT com expiração, MFA quando possível
Sensitive DataDados bancários ou CPF em logsMascaramento em logs, criptografia em trânsito
XXEParsing de XML malicioso (menos comum em REST)Usar JSON, desabilitar parsing de XML externo
Broken AccessAcessar dados de outra empresa/filialValidação de filial em cada query, RBAC
Security MisconfigDebug ligado em produção, portas abertasHardening do Apache, desabilitar debug
Logging FailuresSem audit trail, logs insuficientesAuditoria obrigatória, retenção de logs

Checklist de segurança

01
HTTPS em todas as APIs

Sem exceção. HTTP é aceitável apenas em ambiente de desenvolvimento local.

02
Autenticação obrigatória

Toda API que retorna dados ou altera estado deve exigir autenticação.

03
Tokens com expiração

Nunca use tokens infinitos. Defina expiração curta conforme o risco, com refresh e revogação documentados (OWASP não fixa "24 horas" — meça o risco da sua API).

04
Validação de entrada

Tipo, tamanho, formato, whitelist. Nunca confie no que vem do cliente.

05
Rate limiting

Limite de requisições por IP/token. Previne força bruta e abuso.

06
CORS restritivo

Apenas domínios conhecidos. Nunca use wildcard (*) em produção.

07
Auditoria completa

Log de quem, quando, o quê e de onde. Nunca registre senhas ou tokens.

08
Headers de segurança

HSTS, X-Content-Type-Options, X-Frame-Options — no proxy reverso se houver, ou no AppServer onde o TLS termina.

09
Erros genéricos

Stack trace e detalhes internos vão só para o log interno (ver artigo de debugging) — nunca na resposta HTTP.

10
Revisão periódica

Auditoria trimestral. APIs que foram seguras há 6 meses podem não ser hoje.

Conclusão

Segurança em APIs REST do Protheus não é opcional — é responsabilidade. Cada API insegura é um risco para a empresa, seus dados e seus clientes. A boa notícia é que as práticas são conhecidas e testadas: HTTPS, autenticação, validação, rate limiting e auditoria.

Comece pelo básico, automatize o que puder, e revise periodicamente. A segurança não é um projeto com fim — é um processo contínuo.

Precisa revisar a segurança das suas APIs Protheus?

Falar com a ELP