Pular para o conteúdo
MeWeb

Desenvolvedores

Autenticação do cliente

A conta do cliente final (perfil, endereços, pedidos) usa uma sessão própria em cookies httpOnly: nenhum token transita pelo seu JavaScript. Não existe chave de API: rotas públicas não exigem nada, rotas de conta exigem a sessão do próprio cliente.

Sessão em dois cookies

  • customer_session: JWT de acesso, expira em ~15 minutos.
  • customer_refresh: refresh rotativo, ~30 dias; cada uso emite um novo par.
login
curl -c cookies.txt -X POST https://SUA-LOJA.meweb.com.br/api/v1/storefront/auth/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "cliente@example.com", "password": "..." }'
# Set-Cookie: customer_session=... (JWT, ~15 min)
# Set-Cookie: customer_refresh=... (rotativo, ~30 dias)
renovação
# Em 401 CUSTOMER_UNAUTHENTICATED: renove UMA vez e repita a requisição original
curl -b cookies.txt -c cookies.txt -X POST \
  https://SUA-LOJA.meweb.com.br/api/v1/storefront/auth/refresh
# → { "data": { "ok": true } }  (ok: false = refresh expirado → tela de login)

Padrão do tema oficial: em 401, chamar refresh uma única vez (deduplicando chamadas concorrentes) e repetir a requisição. A sessão é presa à loja: o token de uma loja não vale em outra.

Por que um proxy BFF

Os cookies são sameSite: lax e pertencem ao domínio da API; um front em outro domínio não consegue enviá-los em chamadas de browser (fetch cross-site). Servindo a API pelo seu próprio domínio (rota /api/* que repassa para a loja), os cookies ficam same-origin e tudo (carrinho, sessão, conta) funciona como no tema oficial:

proxy BFF (essência)
// app/api/[...path]/route.ts (Next.js): proxy BFF resumido
export async function POST(req: Request) {
  const upstream = await fetch(apiUrl(req), {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      cookie: req.headers.get('cookie') ?? '',
      'x-forwarded-host': process.env.MEWEB_STORE_HOST!,
    },
    body: await req.text(),
  });
  const res = new Response(await upstream.text(), { status: upstream.status });
  // Repasse CADA Set-Cookie separadamente (nunca junte com vírgula)
  for (const c of upstream.headers.getSetCookie()) res.headers.append('set-cookie', c);
  return res;
}
  • Injete X-Forwarded-Host com o host da loja: é assim que a plataforma resolve o tenant.
  • Repasse os Set-Cookie individualmente (getSetCookie()); login emite múltiplos cookies de uma vez.
  • Não repasse content-encoding/content-length do upstream.

O tema starter implementa esse proxy completo; use-o como referência mesmo que seu front não seja Next.js.

Limites

login e register têm limite de 10 req/min por IP. Solicitação de redefinição de senha responde sempre ok (sem revelar se o e-mail existe).

Desafio progressivo no login

O limite por IP não segura uma botnet que rotaciona endereços contra a senha de um cliente. Por isso, depois de 5 falhas do mesmo e-mail em 15 minutos, a conta entra em modo desafio por 15 minutos (um login correto zera o contador). No fluxo normal nada é exigido: implemente o desafio sob demanda, disparado pelo error.code:

  • 422 CAPTCHA_REQUIRED significa que a loja está sendo servida em um subdomínio meweb.com.br: renderize o widget do Cloudflare Turnstile e repita o login com turnstileToken. O token é de uso único: peça um novo a cada falha, senão a resposta vira 422 CAPTCHA_FAILED para sempre.
  • 429 RATE_LIMITED: em domínio próprio do lojista (onde a allowlist do Turnstile não alcança e o widget não renderiza), o desafio é um limite de 1 tentativa a cada 30 s por e-mail; respeite o Retry-After e ofereça o link de redefinição de senha.
modo desafio
# Tentativa de login em modo desafio, sem token (subdomínio da plataforma)
# → 422 { "error": { "code": "CAPTCHA_REQUIRED", "message": "Por segurança, confirme que você não é um robô." } }

# Renderize o widget do Cloudflare Turnstile e reenvie COM o token:
curl -c cookies.txt -X POST https://SUA-LOJA.meweb.com.br/api/v1/storefront/auth/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "cliente@example.com", "password": "...", "turnstileToken": "TOKEN_DO_WIDGET" }'
# 422 CAPTCHA_FAILED = token recusado (uso único!) → peça um token NOVO ao widget

# Em domínio próprio, onde o widget do Turnstile não renderiza, o desafio é outro:
# → 429 { "error": { "code": "RATE_LIMITED", ... } } + Retry-After: 30

Os dois códigos saem iguais para e-mail inexistente: o desafio não revela se a conta existe. Não trate nenhum deles como "conta bloqueada".