Acesso Comercial / Docs / Storefront / API
Ctrl K
GUIA

Modos de Integração

Existem três formas de integrar um storefront com a Storefront API. Cada uma tem diferentes níveis de segurança e complexidade.

Comparação Rápida

Modo Autenticação Segurança Complexidade
1. Hospedagem na plataforma Cookies httpOnly (automático) Máxima Nenhuma configuração extra
2. Frontend + Backend próprio Cookies httpOnly (via proxy reverso) Máxima Requer proxy no servidor
3. Apenas frontend próprio Bearer Token (JWT no JavaScript) Moderada Simples, sem servidor
Recomendação de segurança: Os modos 1 e 2 mantêm os tokens de autenticação completamente no lado do servidor, protegidos de ataques XSS. O modo 3 expõe tokens ao JavaScript do frontend, e deve ser usado apenas quando não há alternativa server-side (ex: plataformas sem backend como Lovable, ou protótipos em desenvolvimento).

Modo 1: Hospedagem na Plataforma (Recomendado)

A loja roda na infraestrutura da plataforma com domínio customizado (ex: www.minhaloja.com). O storefront e a API ficam no mesmo servidor, tudo same-origin.

Como funciona

  • O frontend (storefront) e a API estão no mesmo domínio
  • Os cookies httpOnly são definidos automaticamente pelo servidor
  • O navegador envia os cookies em cada request sem intervenção do JavaScript
  • Tokens nunca são acessíveis pelo JavaScript do frontend

Por que é o mais seguro

Tudo é same-origin. Cookies httpOnly, Secure, SameSite=Lax funcionam nativamente. Nenhuma configuração especial é necessária. O JavaScript do frontend nunca vê nenhum token de autenticação.

Zero configuração: Basta apontar o domínio customizado para a plataforma. A autenticação, carrinho e Google login funcionam automaticamente. Veja o Guia de Autenticação para detalhes.

Modo 2: Frontend + Backend Próprio (Proxy Reverso)

O lojista hospeda seu próprio frontend e backend, e usa um proxy reverso para encaminhar requests à Storefront API. O resultado é a mesma segurança do Modo 1.

Como funciona

O servidor do lojista encaminha as requests GraphQL para a API da plataforma. Do ponto de vista do navegador, tudo é same-origin:

  • O frontend chama www.minhaloja.com/api/graphql (mesmo domínio)
  • O proxy reverso encaminha para api.acessocomercial.com/graphql
  • Os cookies httpOnly são definidos no domínio www.minhaloja.com
  • O navegador envia os cookies automaticamente em cada request
  • Tokens nunca são acessíveis pelo JavaScript do frontend

Configuração por plataforma

Vercel (next.config.js rewrites)

JavaScript
module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/graphql',
        destination:
          'https://storefront.acessocomercial.com/graphql'
      }
    ];
  }
};

Cloudflare Worker

JavaScript
export default {
  async fetch(request) {
    const url = new URL(request.url);

    if (url.pathname.startsWith('/api/graphql')) {
      const target =
        'https://storefront.acessocomercial.com/graphql';

      return fetch(target, {
        method: request.method,
        headers: request.headers,
        body: request.body
      });
    }
  }
};

Nginx

Nginx
location /api/graphql {
    proxy_pass https://storefront.acessocomercial.com/graphql;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_pass_header Set-Cookie;
}

Node.js / Express

JavaScript
import { createProxyMiddleware } from 'http-proxy-middleware';

app.use('/api/graphql', createProxyMiddleware({
  target: 'https://storefront.acessocomercial.com',
  changeOrigin: true,
  pathRewrite: {
    '^/api/graphql': '/graphql'
  },
  cookieDomainRewrite: {
    'api.acessocomercial.com': ''
  }
}));

Como o frontend faz requests

O código do frontend é idêntico ao Modo 1. Basta chamar o endpoint local com credentials: 'include':

JavaScript
const response = await fetch('/api/graphql', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: `mutation {
      clientLogin(input: {
        email: "user@example.com"
        password: "secret"
        hostname: "minha-loja"
      }) {
        auth
      }
    }`
  })
});
Mesma segurança do Modo 1: O proxy reverso transforma qualquer setup cross-domain em same-origin. Não é necessário X-Auth-Mode, Authorization ou gerenciamento manual de tokens. O frontend se comporta exatamente como se estivesse hospedado na plataforma.

Modo 3: Apenas Frontend Próprio (Bearer Token)

O lojista hospeda apenas o frontend, sem servidor próprio. O frontend chama a API diretamente de um domínio diferente (ex: lovable.app, vercel.app). Cookies não funcionam neste cenário, então a API retorna tokens no corpo da resposta.

Segurança reduzida: Neste modo, os tokens JWT ficam acessíveis ao JavaScript do frontend. Se o site for vulnerável a XSS, um atacante pode roubar o token. Prefira os Modos 1 ou 2 para lojas em produção que processam pagamentos reais. Use este modo para desenvolvimento, protótipos ou plataformas sem backend (Lovable, etc.).

Como funciona

O modo Bearer é ativado enviando o header X-Auth-Mode: bearer nas requests GraphQL. Quando ativo:

  • As mutations de login/signup retornam jwtToken e refreshToken no corpo da resposta
  • O frontend armazena esses tokens em sessionStorage (ou em memória)
  • Requests autenticadas enviam o header Authorization: Bearer <token>

Exemplo completo: API Client

Crie um client reutilizável que gerencia tokens, refresh automático e carrinho:

JavaScript
const API_URL = 'https://storefront.acessocomercial.com/graphql';
const HOSTNAME = 'minha-loja';

// --- Token Storage ---

function getTokens() {
  return {
    jwt: sessionStorage.getItem('jwt'),
    refresh: sessionStorage.getItem('refreshToken')
  };
}

function saveTokens(jwtToken, refreshToken) {
  sessionStorage.setItem('jwt', jwtToken);
  sessionStorage.setItem('refreshToken', refreshToken);
}

function clearTokens() {
  sessionStorage.removeItem('jwt');
  sessionStorage.removeItem('refreshToken');
}

function isLoggedIn() {
  return !!sessionStorage.getItem('jwt');
}

// --- Cart Storage ---

function getCartData() {
  return localStorage.getItem('CartData') || '';
}

function saveCartData(xCookiesHeader) {
  if (!xCookiesHeader) return;
  try {
    const cookies = JSON.parse(xCookiesHeader);
    for (const cookie of cookies) {
      if (cookie.name === 'CartData') {
        localStorage.setItem('CartData', JSON.stringify(cookie.value));
      }
    }
  } catch { /* ignore parse errors */ }
}

// --- GraphQL Client ---

async function graphql(query, variables = {}) {
  const { jwt } = getTokens();
  const cartData = getCartData();

  const headers = {
    'Content-Type': 'application/json',
    'X-Auth-Mode': 'bearer'
  };

  if (jwt) {
    headers['Authorization'] = `Bearer ${jwt}`;
  }

  if (cartData) {
    headers['X-Cart'] = JSON.stringify({
      CartData: JSON.parse(cartData)
    });
  }

  const response = await fetch(API_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify({ query, variables })
  });

  // Save updated cart data from response
  saveCartData(response.headers.get('X-Cart'));

  const result = await response.json();

  // If token expired, try refresh and retry
  const isAuthError = result.errors?.some(
    (e) => e.extensions?.code === 'UNAUTHENTICATED'
  );

  if (isAuthError && getTokens().refresh) {
    const refreshed = await refreshSession();
    if (refreshed) {
      return graphql(query, variables); // retry with new token
    }
  }

  return result;
}

// --- Auth ---

async function login(email, password) {
  const result = await graphql(`
    mutation Login($input: ClientLoginMutationInput!) {
      clientLogin(input: $input) {
        auth
        jwtToken
        refreshToken
      }
    }
  `, {
    input: { email, password, hostname: HOSTNAME }
  });

  const { auth, jwtToken, refreshToken } = result.data.clientLogin;

  if (auth) {
    saveTokens(jwtToken, refreshToken);
  }

  return auth;
}

async function refreshSession() {
  const { refresh } = getTokens();
  if (!refresh) return false;

  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Auth-Mode': 'bearer'
    },
    body: JSON.stringify({
      query: `mutation {
        refreshSession(input: {
          refreshToken: "${refresh}"
        }) {
          success
          jwtToken
          refreshToken
        }
      }`
    })
  });

  const result = await response.json();
  const data = result.data?.refreshSession;

  if (data?.success) {
    saveTokens(data.jwtToken, data.refreshToken);
    return true;
  }

  clearTokens();
  return false;
}

function logout() {
  clearTokens();
}

// --- Usage ---

// Login
await login('user@example.com', 'secret');

// Authenticated query (token injected automatically)
const cart = await graphql(`
  query { shoppingCart(hostname: "${HOSTNAME}") {
    id totalPrice products { id title quantity }
  }}
`);

// Add to cart
const updated = await graphql(`
  mutation { addShoppingCartItem(input: {
    hostname: "${HOSTNAME}" productId: "abc123" quantity: 1
  }) { shoppingCart { id totalPrice } }}
`);

Carrinho de compras (Cart)

O carrinho funciona via header X-Cart, que transporta o identificador do carrinho. Este mecanismo funciona cross-domain pois usa um header HTTP customizado (não depende de cookies do navegador).

  • Envie X-Cart com o valor do CartData em cada request
  • Leia o header X-Cart da resposta para obter o CartData atualizado
  • Armazene o CartData localmente (localStorage) entre requests
  • O exemplo do API Client acima já gerencia isso automaticamente

Login com Google (Cross-Domain)

Para domínios externos, o login com Google usa um fluxo de proxy centralizado, pois o Google requer que cada domínio seja registrado como JavaScript Origin autorizado.

  1. Redirecione o usuário para /auth/google/start?hostname=STORE&redirect=CALLBACK_URL
  2. O usuário faz login com Google na página da plataforma
  3. Após sucesso, o usuário é redirecionado de volta com um código temporário: CALLBACK_URL?google_auth_code=CODE
  4. O frontend extrai o código e chama POST /auth/google/exchange com o código para obter os tokens

Recomendações de segurança para o Modo 3

  • Armazene tokens em sessionStorage (limpo ao fechar a aba) — nunca em localStorage
  • Implemente Content Security Policy (CSP) no frontend
  • Não exponha tokens em URLs ou logs
  • Migre para o Modo 2 (proxy reverso) quando possível para produção

CORS

A Storefront API aceita requests de qualquer origem HTTPS. Não é necessário configurar CORS adicionalmente. Em desenvolvimento local, origens http://localhost também são permitidas.

Modo 2: Login via Proxy

JavaScript
// Frontend chama o proxy local (same-origin)
const response = await fetch('/api/graphql', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: `mutation {
      clientLogin(input: {
        email: "user@example.com"
        password: "secret"
        hostname: "minha-loja"
      }) {
        auth
      }
    }`
  })
});

// Cookies definidos automaticamente pelo servidor.
// Nenhum token retornado ao JavaScript.

Modo 3: Login Bearer

JavaScript
const response = await fetch(
  'https://storefront.acessocomercial.com/graphql',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Auth-Mode': 'bearer'
    },
    body: JSON.stringify({
      query: `mutation {
        clientLogin(input: {
          email: "user@example.com"
          password: "secret"
          hostname: "minha-loja"
        }) {
          auth
          jwtToken
          refreshToken
        }
      }`
    })
  }
);

const { data } = await response.json();
const { jwtToken, refreshToken } = data.clientLogin;

sessionStorage.setItem('jwt', jwtToken);
sessionStorage.setItem('refresh', refreshToken);

Modo 3: Request Autenticada

JavaScript
const jwt = sessionStorage.getItem('jwt');

const response = await fetch(
  'https://storefront.acessocomercial.com/graphql',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${jwt}`,
      'X-Auth-Mode': 'bearer'
    },
    body: JSON.stringify({
      query: `query {
        shoppingCart(hostname: "minha-loja") {
          id
          totalPrice
          products { id title price quantity }
        }
      }`
    })
  }
);

Modo 3: Renovar Token

JavaScript
const refreshToken = sessionStorage.getItem('refresh');

const response = await fetch(
  'https://storefront.acessocomercial.com/graphql',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Auth-Mode': 'bearer'
    },
    body: JSON.stringify({
      query: `mutation {
        refreshSession(input: {
          refreshToken: "${refreshToken}"
        }) {
          success
          jwtToken
          refreshToken
        }
      }`
    })
  }
);

const { data } = await response.json();
if (data.refreshSession.success) {
  sessionStorage.setItem('jwt', data.refreshSession.jwtToken);
  sessionStorage.setItem('refresh', data.refreshSession.refreshToken);
}

Modo 3: Cart com X-Cart

JavaScript
const cartData = localStorage.getItem('CartData') || '';

const response = await fetch(
  'https://storefront.acessocomercial.com/graphql',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${jwt}`,
      'X-Cart': JSON.stringify({
        CartData: cartData ? JSON.parse(cartData) : ''
      })
    },
    body: JSON.stringify({
      query: `mutation {
        addShoppingCartItem(input: {
          hostname: "minha-loja"
          productId: "abc123"
          quantity: 1
        }) {
          shoppingCart {
            id
            totalPrice
            products { id title quantity }
          }
        }
      }`
    })
  }
);

const xCookies = response.headers.get('X-Cart');
if (xCookies) {
  const cookies = JSON.parse(xCookies);
  for (const cookie of cookies) {
    if (cookie.name === 'CartData') {
      localStorage.setItem('CartData',
        JSON.stringify(cookie.value));
    }
  }
}

Modo 3: Google Login

JavaScript
// 1. Redirect para login Google
const hostname = 'minha-loja';
const callback = encodeURIComponent(
  window.location.origin + '/auth/callback'
);
window.location.href =
  'https://storefront.acessocomercial.com' +
  '/auth/google/start' +
  `?hostname=${hostname}&redirect=${callback}`;

// 2. Na pagina de callback, trocar o code
const params = new URLSearchParams(
  window.location.search
);
const code = params.get('google_auth_code');

if (code) {
  const response = await fetch(
    'https://storefront.acessocomercial.com' +
    '/auth/google/exchange',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Auth-Mode': 'bearer'
      },
      body: JSON.stringify({ code })
    }
  );

  const { auth, jwtToken } = await response.json();
  if (auth) {
    sessionStorage.setItem('jwt', jwtToken);
  }
}

cURL: Login Bearer

cURL
curl -X POST \
  https://storefront.acessocomercial.com/graphql \
  -H 'Content-Type: application/json' \
  -H 'X-Auth-Mode: bearer' \
  -d '{
    "query": "mutation { clientLogin(input: { email: \"user@example.com\" password: \"secret\" hostname: \"minha-loja\" }) { auth jwtToken refreshToken } }"
  }'