DEV Community

Cover image for Shopify POS Extensions: Conectando o Backend com Segurança
Diego Chueri
Diego Chueri

Posted on

Shopify POS Extensions: Conectando o Backend com Segurança

English Version

Construir uma Shopify POS UI Extension é uma ótima maneira de adicionar funcionalidades personalizadas ao seu Shopify POS. Você usa o Shopify CLI, gera sua extensão, personaliza o componente React e tudo parece funcionar bem... até você precisar fazer aquela chamada fetch para o seu backend.

Nesse momento podem surgir as seguintes perguntas:

  • Como meu backend pode saber que essa requisição é legítima?
  • Como ele sabe de qual loja (shop) essa chamada está vindo sem expor esse dado na requisição?
  • Como impedir que alguém mal-intencionado finja ser a nossa extensão e envie dados falsos?

Se você já se deparou com isso, você sabe que a resposta é autenticação JWT. A Shopify resolve isso de forma elegante usando Session Tokens.

A documentação oficial mostra o caminho, mas pode ser um pouco abstrata.

Neste artigo, vou mostrar o guia prático e direto de como nós implementamos essa autenticação de ponta a ponta: do fetch na POS Extension até o middleware de verificação no nosso backend Node.js, utilizando a biblioteca oficial.

Vamos lá!

O Fluxo Geral

Antes de mergulhar no código, aqui está o resumo do fluxo de autenticação em 5 etapas:

  1. Frontend (POS Extension): Nossa extensão usa o Session API (useApi) para pedir um JWT exclusivo para a Shopify no momento da requisição.
  2. A Requisição: O frontend envia esse JWT no cabeçalho Authorization: Bearer <token> para o nosso backend.
  3. Backend (Seu Servidor): Nosso middleware intercepta a chamada, usa a biblioteca @shopify/shopify-api para verificar se o token é válido e, se for, identifica a loja e permite que a requisição continue.

Um diagrama simples mostrando: POS Extension -> (JWT) -> Backend -> (Verifica JWT) -> OK

Frontend - Pegando o token

Importante: A forma de resgate do token pode variar conforme a versão da API Shopify utlizada. Neste artigo estamos utilizando a versão 2025-07.

A Shopify torna essa etapa bem simples. Dentro da sua extensão (que provavelmente utiliza React), você pode usar o hook useApi do pacote @shopify/ui-extensions-react/point-of-sale.

// extensions/pos-ui-sua-extensao/src/SeuComponente.jsx

import React, {useState} from 'react';
import {
  reactExtension,
  useApi,
  Screen,
  Text,
} from '@shopify/ui-extensions-react/point-of-sale';

const SmartGridModal = () => {
  const {currentSession, getSessionToken} =
    useApi<'pos.home.modal.render'>().session;

  const {shopId, userId, locationId, staffMemberId} = currentSession;
  const [sessionToken, setSessionToken] = useState<string>();

  useEffect(() => {
    getSessionToken().then((newToken) => {
        setSessionToken(newToken);
    });
  }, [])

    return (
    <Screen name="ScreenOne" title="Screen One Title">
      <Text>
        shopId: {shopId}, userId: {userId}, locationId: {locationId}, staffId:
        {staffMemberId}
      </Text>
      <Text>sessionToken: {sessionToken}</Text>
    </Screen>
  );
};

export default reactExtension('pos.home.modal.render', () => (
  <SmartGridModal />
));
Enter fullscreen mode Exit fullscreen mode

Obs: Nos casos onde a chamada é provocada por uma ação (ex.: ao clicar no botão Enviar, é realizada uma fetch para o backend com um body que será processado) eu particularmente dou preferência para acionar o getSessionToken dentro do handle que realizará a chamada. Isso garante que o sessionToken estará valido no momento do envio.

const onClickHandle = () => {
  getSessionToken().then((newToken) => {
    const headers = { Authorization: `Bearer ${newToken}` };
    fetch("https://myapi.com", { headers }).then((res) => console.log(res));
  });
};

Backend - Verificando o Token

Essa é a parte crucial. O backend vai receber o token da POS Extension e precisa fazer duas coisas:

  • Autenticar: É um token válido e foi realmente emitido pela Shopify?
  • Autorizar: Essa loja (que o token diz representar) existe no nosso banco de dados?

Vamos usar a biblioteca oficial da Shopify para fazer o trabalho pesado.

Configuração da API (Seu shopifyApi.js)

Primeiro, precisamos garantir que o nosso backend conheça nossas chaves de API. Você provavelmente tem um arquivo de configuração parecido com esse (vamos chamar de config/shopifyApi.js) que inicializa a biblioteca da Shopify:

import "@shopify/shopify-api/adapters/node";
import { shopifyApi, ApiVersion } from "@shopify/shopify-api";
import * as dotenv from "dotenv";
dotenv.config();

// Inicializamos o objeto 'shopify' que será utilizado como instância principal da Shopify em toda a api
const shopify = shopifyApi({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET,
  hostName: process.env.HOST,
  scopes: process.env.SCOPES,
  apiVersion: "2025-07", // Use sua versão
});
Enter fullscreen mode Exit fullscreen mode

Caso você não esteja familiarizado com a @shopify/shopify-api, acesse aqui a documentação completa.

Criando o Middleware

Agora finalmente iremos criar o middleware de autenticação e autorização para as rotas que serão utilizadas pelo POS Extension. Ele será executado antes das rotas protegidas.

// src/middlewares/auth.js

import { StoreRepository } from "../repositories/StoreRepository.js";
import { shopify } from "../config/shopifyApi.js"; // A config da Etapa 2a

export default async (req, res, next) => {
  try {
    // 1. Pegue o token do header da requisição
    const token = req.headers["authorization"].split(" ")[1];
    if (!token) {
      throw new Error("Unauthorized");
    }

    // 2. A MÁGICA: Decodifica e Verifica o Token
    // O `decodeSessionToken` faz tudo:
    // - Pega as chaves da Shopify
    // - Verifica a assinatura do token
    // - Verifica se não expirou
    // - Se tudo estiver OK, retorna o payload (os dados)
    const payload = await shopify.session.decodeSessionToken(token);

    // 3. Pega a URL da loja (ex: "https://minha-loja.myshopify.com")
    const shopId = payload.dest;

    // 4. Autorização: Verifica se essa loja existe no NOSSO banco
    const store = await Store.findOne({ shopId });
    if (!store) {
      // O token é válido, mas a loja não está instalada no nosso app
      throw new Error("Store not found");
    }

    // 5. Feito! Anexamos a loja na requisição
    // Agora, todas as rotas seguintes terão acesso a `req.store`
    req.store = store;

    return next(); // Continua o fluxo para a rota
  } catch (error) {
    // Se `decodeSessionToken` falhar ou a loja não for encontrada,
    // retornamos 401 Unauthorized.
    return res.status(401).json({ message: "Unauthorized" });
  }
};
Enter fullscreen mode Exit fullscreen mode

Resumindo o que ocorre no código acima:

  • shopify.session.decodeSessionToken(token) realiza a autenticação (prova que o usuário é quem diz ser, ou seja, a Shopify).
  • Store.findOne() realiza a autorização (prova que o usuário tem permissão para acessar o recurso, no caso do exemplo, verifica se a loja está cadastrada no sistema).

Aplicando o Middleware nas rotas

Com o middleware pronto, basta adicionar nas rotas que precisam de proteção.

// src/routes.js

import shopifyAuthMiddleware from "./middleware/shopifyAuth.js";

// ... demais rotas da aplicação

// Esta rota é pública, não precisa de token
routes.get("/api/public/health-check", (req, res) => {
  res.json({ status: "ok" });
});

// Esta rota é PROTEGIDA pelo nosso middleware
routes.get(
  "/api/private/meus-dados",
  shopifyAuthMiddleware, // <-- O middleware entra aqui!
  async (req, res) => {
    // Se o código chegou aqui, o token é válido e req.store existe!
    const store = req.store;

    res.json({
      message: `Olá, loja ${store.name}!`,
    });
  }
);
Enter fullscreen mode Exit fullscreen mode

Conclusão

Está feito! Com essa arquitetura fechamos o ciclo de autenticação de ponta a ponta.

Vamos recapitular o que fizemos:

  • No Frontend (POS Extension): Usamos o hook getSessionToken para pegar um session token (JWT) válido a cada chamada de API.

  • No Backend (Middleware): Usamos o método shopify.session.decodeSessionToken para validar a assinatura do token contra as chaves públicas da Shopify.

  • Na Rota: Verificamos se a loja do token existe em nosso banco de dados (Store.findOne) antes de permitir o acesso.

Agora, seu backend tem uma forma robusta e segura de garantir que cada requisição vinda da sua POS Extension é legítima e autenticada. Chega de "token inválido" ou chamadas misteriosas!

E aí, como você faz?

Essa foi a maneira que encontramos para implementar a autenticação seguindo as melhores práticas da Shopify.

Como sua equipe lida com a autenticação de extensões? Você usa uma abordagem diferente? Encontrou algum problema no caminho?

Deixe seu comentário abaixo! Vamos trocar experiências.

Top comments (0)