Artigo original: How to avoid exposing your API key in your public front-end apps
Escrito por: Jackson Bates
O problema
Tudo o que você quer fazer é buscar algum JSON de um endpoint de API para o clima, algumas resenhas de livros ou algo similarmente simples.
A consulta fetch no seu front-end é suficientemente fácil, mas você tem que colar sua chave de API secreta bem ali no código do front-end para qualquer um encontrar com um mínimo de esforço!
Além disso, enviar suas chaves de API para o seu repositório do GitHub é um grande problema: um desenvolvedor colocou suas chaves da AWS no Github e COISAS RUINS aconteceram (texto em inglês).
"Por que isso é tão difícil?!" – Você, provavelmente há 15 minutos
A solução
Você deve usar um servidor de back-end para buscar os resultados da API para você e depois passá-los para o seu front-end.
O novo problema
Você está apenas tentando fazer uma demonstração de front-end para seu portfólio! Você ainda não aprendeu nada sobre tecnologias de back-end! Por que isso é tão difícil?
Demonstração
Encontrei esse problema com frequência suficiente para decidir parar de inventar truques bobos e implementar uma solução que funciona com código back-end mínimo.
Nessa demonstração, eu configuro um back-end que escuta por requisições POST e as envia para a API do GoodReads. Para usar isso, você precisa implementar seu próprio front-end que possa enviar a requisição POST apropriada para esse back-end. Seu front-end não se comunicará diretamente com o GoodReads. Portanto, nenhuma chave de API é exposta.
Você precisará
- Node (o teste foi feito com a versão v10.16.0 – versões posteriores não terão problemas, versões anteriores podem encontrar alguns)
- git
- Este repositório: https://github.com/JacksonBates/example-goodreads-api-relay
Comece
git clone https://github.com/JacksonBates/example-goodreads-api-relay.git
O README.md (em inglês) contém tudo o que você precisa saber, incluindo instalação e configuração.
Incluí os pontos principais aqui (traduzidos) para sua conveniência:
README.md
Instale as dependências:
npm i
Você precisa criar seu próprio arquivo .env
para sua chave:
cp .env.example .env
Então, abra o novo arquivo .env
e cole suas chaves no local correto.
Exemplo:
GOODREADS_API_KEY=AABBCCDDEEFF00112233445566778899
Agora execute o servidor:
node app.js
No navegador, navegue até localhost:3000
para confirmar que o servidor está funcionando. Você deve ver um simples Hello World!
O que vem a seguir?
Agora, leia o arquivo app.js
cuidadosamente.
Comentei o código extensivamente para ajudá-lo a entender o que está acontecendo se você não viu muito sobre o node e o express antes.
// app.js
// Aqui, importamos os módulos necessários e definimos algumas variáveis iniciais
require("dotenv").config();
const express = require("express");
const fetch = require("node-fetch");
const convert = require("xml-js");
const rateLimit = require("express-rate-limit");
const app = express();
const port = 3000;
// Limitação de taxa – o Goodreads limita para 1/seg, então devemos fazer
// isso também
// Ative se você estiver atrás de um proxy reverso (Heroku, Bluemix, AWS ELB, // Nginx etc)
// veja https://expressjs.com/en/guide/behind-proxies.html
// app.set('trust proxy', 1);
const limiter = rateLimit({
windowMs: 1000, // 1 segundo
max: 1, // limita cada IP a 1 requisição por windowMs
})
// Aplicar a todas as requisições
app.use(limiter)
// Rotas
// Rota de teste, visite localhost:3000 para confirmar se está funcionando
// deve mostrar 'Hello World!' no navegador
app.get("/", (req, res) => res.send("Hello World!"));
// Nossa rota do Goodreads!
app.get("/api/search", async (req, res) => {
try {
// Isso usa interpolação de strings para fazer nossa string de
// consulta de busca
// tira o parâmetro de consulta postado e o reformata para o
// goodreads
const searchString = `q=${req.query.q}`;
// Usa o node-fetch para chamar a API do goodreads e lê a chave do
// .env
const response = await fetch(`https://www.goodreads.com/search/index.xml?key=${process.env.GOODREADS_API_KEY}&${searchString}`);
//mais informações aqui https://www.goodreads.com/api/index#search.books
const xml = await response.text();
// A API do Goodreads retorna XML, então para usá-la facilmente no front-end, podemos
// converter isso para JSON:
const json = convert.xml2json(xml, { compact: true, spaces: 2 });
// A API retorna coisas que não nos interessam, então podemos muito bem eliminar
// tudo exceto os resultados:
const results = JSON.parse(json).GoodreadsResponse.search.results;
return res.json({
success: true,
results
})
} catch (err) {
return res.status(500).json({
success: false,
message: err.message,
})
}
})
// Isso ativa nosso servidor e gera logs para usarmos.
// As declarações console.log que você usar no node para depuração
// aparecerão no seu terminal, não no console do navegador!
app.listen(port, () => console.log(`Exemplo de aplicação escutando na porta ${port}!`));
Atualização: um grande agradecimento a Gouri Shankar Kumawat por contribuir com um PR que melhorou este código! Você pode segui-lo no Twitter ou no GitHub.
Use o Postman para testar a API.
Configure o Postman para GET e cole isso no URL: localhost:3000/api/search?q=hobbit
O Postman mostrará a resposta JSON abaixo.

Como usar isso no seu front-end?
Esta aplicação simples está escutando requisições POST em /api/search
, então interaja com ela em sua aplicação de front-end da mesma forma que você fazia anteriormente com a API original.
Ela está configurada apenas para lidar com consultas de pesquisa – se você quiser usar outros endpoints/métodos da API do Goodreads, precisará pensar em como implementá-los por conta própria!
Hospedagem
Você não pode implantar seu front-end e ainda ter isso no localhost – obviamente você precisa implantar isso também.
Recomendo o Heroku (site em inglês).
Nota da tradução: no momento da tradução deste artigo, o Heroku não está mais disponibilizando um tier gratuito para uso. Aos que procuram uma alternativa gratuita, recomendamos a busca por outras fontes.
Bônus
Se você quiser expandir essa solução, pode considerar como torná-la acessível apenas a partir de um intervalo restrito de endereços IP para aumentar a segurança – o que estava fora do escopo deste tutorial/demonstração.
A solução foi montada rapidamente em resposta a uma discussão no fórum. Se você encontrar algum problema neste artigo ou no código de exemplo, não hesite em responder ao tópico do fórum (em inglês) que deu início a tudo. Manterei o artigo e o repositório atualizados com melhorias.
Sinta-se à vontade para enviar PRs se tiver contribuições valiosas a fazer. 😀
Você também pode entrar em contato com o autor via Twitter.