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á

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.

get_request
Captura de tela do Postman mostrando o JSON retornado do nosso novo back-end

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.