coding

Compartilhando cookies encriptados do Laravel com Next.js

Conectando Laravel e Next.js: Como sincronizamos cookies encriptados entre dois frameworks diferentes.

Escrito em 25 de outubro de 2024 - 🕒 9 min. de leitura

Muitas vezes, amigos me perguntam: “Pablo, como você consegue manter a calma e serenidade diante de desafios técnicos complexos?” Bem, a verdade é que eu não consigo. Eu apenas escrevo posts no blog sobre eles para parecer que eu consigo. Esta é uma dessas histórias.

Recentemente, me vi precisando desvendar os valores dos cookies vindos de uma aplicação Laravel, lê-los, atualizá-los e, em seguida, remontá-los de uma forma que a aplicação Laravel nem percebesse. Parece o enredo de um filme do Missão Impossível, certo? Vamos seguir com essa ideia, e eu sou o Tom Cruise.

A infame migração para o Next.js

Depois de migrar do CoffeeScript para o ES6 e depois do ES6 para o TypeScript, era hora de mais uma grande refatoração: separar nosso frontend do Laravel em um app Next.js independente.

Para melhorar o desempenho, a experiência do desenvolvedor e a atratividade do empregador (merchan descarado, estamos contratando no Studocu wink-wink), precisávamos de um framework frontend robusto que pudesse lidar com nossa crescente base de usuários e conjunto de funcionalidades. O Next.js parecia a escolha perfeita.

Teoricamente, isso nos ajudaria a parar de reinventar a roda - como escrever infinitas linhas de configurações do Webpack - e focar no que realmente importa: construir funcionalidades que nossos usuários amam. Então, o plano é que, em vez de ter um código espaguete gigante, teríamos dois menores, como fios de macarrão.

Código espaguete
Código espaguete

Mas por quê?

Como o uma vez indicado ao Grammy, Ryan Reynolds disse uma vez: Mas por quê?

Filmagem real dos leitores deste post

Boa pergunta! A questão é a seguinte: decidimos que a melhor maneira de implementar essa transição para o Next.js era mover uma página por vez. Isso significa que precisaríamos compartilhar cookies entre as aplicações Laravel e Next.js. Embora isso traga muitos benefícios, também apresenta alguns desafios únicos.

O maior desafio é que Laravel e Next.js não falam exatamente a mesma linguagem quando se trata de criptografia de cookies. O Laravel usa seu próprio algoritmo de criptografia e, por padrão, criptografa todos os cookies usando uma chave privada. Sem essa chave, e sem reverter o algoritmo do Laravel, é impossível ler os dados no lado do Next.js.

Por outro lado, o Next.js é bastante flexível sobre como você armazena seus cookies. Como estávamos criando uma nova aplicação Next.js do zero, essa foi uma oportunidade perfeita para alinhar sua criptografia ao método do Laravel, permitindo-nos descriptografar e criptografar os cookies do Laravel diretamente no Next.js sem o risco de quebrar nada.

Mergulhando na criptografia de cookies do Laravel

Antes de resolvermos esse problema, precisávamos entender como o Laravel criptografa seus cookies. Descobri que não há muitas informações sobre isso. Em uma época em que é difícil encontrar algo único para escrever em um post de blog, pensei que essa seria a oportunidade perfeita para compartilhar algumas ideias.

Então, como descobrimos como o Laravel criptografa seus cookies? Explorando o código-fonte, é claro! Felizmente, o código-fonte do Laravel é bastante legível e bem organizado, então não demorou muito para localizar as partes relevantes.

Chave da Aplicação

O Laravel usa uma chave privada, conhecida como Chave da Aplicação (Application Key), para a criptografia, que pode ser gerada usando o comando php artisan key:generate. Ele emprega o cifrador AES-256-CBC para criptografia, que requer um Vetor de Inicialização (IV). Além disso, o Laravel usa um Código de Autenticação de Mensagem (MAC) para garantir a integridade dos dados criptografados.

Vetor de Inicialização

O IV é um valor aleatório usado para garantir que o mesmo texto simples não seja criptografado para o mesmo texto cifrado. O Laravel gera um IV aleatório para cada cookie e o armazena junto com o valor criptografado.

Código de Autenticação de Mensagem

O MAC é uma soma de verificação criptográfica que garante a integridade dos dados criptografados. O Laravel gera um MAC usando o IV e o valor criptografado, que é então usado para verificar a integridade dos dados durante a descriptografia.

Diagrama do processo

Fluxo de criptografia
Fluxo de criptografia

Tudo isso pode mudar dependendo da sua versão do Laravel ou de quanto você personalizou suas configurações de criptografia. Você pode verificar o arquivo config/app.php para ver como suas configurações de criptografia estão configuradas.

Aqui está um resumo simplificado do processo:

  1. Valor do Cookie: O valor do cookie é transformado em string e então prefixado com um hash SHA-1 baseado no nome do cookie e em uma string de versão ("v2").
  2. Criptografia: O valor prefixado é criptografado usando AES-256-CBC.
  3. Geração do MAC: Um MAC é gerado usando HMAC-SHA256 sobre o IV e o valor criptografado.
  4. Montagem do Payload: O valor criptografado, o IV, o MAC e uma tag são montados em um objeto JSON.
  5. Codificação Base64: Este objeto JSON é então codificado em base64 para formar o valor final do cookie.

Um exemplo da estrutura do cookie criptografado é assim:

{
  "value": "encryptedValue",
  "iv": "base64-encoded-IV",
  "mac": "hex-encoded-MAC",
  "tag": "base64-encoded-tag"
}

Entender essa estrutura foi crucial para replicar os processos de criptografia e descriptografia no Next.js.

Recriando a criptografia do Laravel no Next.js

Para ler e atualizar os cookies criptografados do Laravel no Next.js, precisávamos replicar a lógica de criptografia do Laravel em nossa aplicação Next.js. Aqui está como abordamos isso.

Mesmos cookies
Mesmos cookies

Descriptografando cookies

Criamos uma função decryptCookieValue que espelha o processo de descriptografia do Laravel. Aqui está uma versão simplificada:

export const decryptCookieValue = async (encryptedCookieValue) => {
  const publicKey = process.env.APP_KEY;
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  // Decodificar e analisar o cookie
  const decodedResult = Buffer.from(encryptedCookieValue, 'base64').toString('utf-8');
  const parsedResult = JSON.parse(decodedResult);

  const { iv, mac, value } = parsedResult;

  // Verificar o MAC
  const macPayload = iv + value;
  const keyMaterial = encoder.encode(publicKey);
  const hmacKey = await crypto.subtle.importKey('raw', keyMaterial, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
  const isMacValid = await crypto.subtle.verify('HMAC', hmacKey, Buffer.from(mac, 'hex'), encoder.encode(macPayload));

  if (!isMacValid) {
    throw new Error('Falha na validação do MAC');
  }

  // Descriptografar o valor
  const ivArray = Buffer.from(iv, 'base64');
  const encryptedData = Buffer.from(value, 'base64');
  const cryptoKey = await crypto.subtle.importKey('raw', keyMaterial, { name: 'AES-CBC' }, false, ['decrypt']);
  const decryptedData = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: ivArray }, cryptoKey, encryptedData);

  const decryptedString = decoder.decode(decryptedData);

  // Remover o prefixo do cookie do Laravel
  return decryptedString.slice(41);
};

Essa função descriptografa o valor do cookie, verifica o MAC e retorna a string descriptografada. Também tivemos que remover o prefixo do cookie do Laravel com um slice - exatamente como é feito no código original - para obter o valor original do cookie.

Criptografando cookies

Também precisávamos criptografar cookies no Next.js para que o Laravel pudesse entender. Aqui está a função encryptCookieValue:

export const encryptCookieValue = async (cookieName, value) => {
  const publicKey = process.env.APP_KEY;
  const encoder = new TextEncoder();

  // Criar o prefixo do cookie do Laravel
  const cookiePrefix = await createLaravelCookiePrefix(cookieName);
  const prefixedValue = cookiePrefix + value;

  // Gerar um IV aleatório
  const iv = crypto.getRandomValues(new Uint8Array(16));
  const keyMaterial = encoder.encode(publicKey);

  // Criptografar o valor
  const cryptoKey = await crypto.subtle.importKey('raw', keyMaterial, { name: 'AES-CBC' }, false, ['encrypt']);
  const encryptedData = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, encoder.encode(prefixedValue));

  // Gerar o MAC
  const encryptedBase64 = Buffer.from(encryptedData).toString('base64');
  const ivBase64 = Buffer.from(iv).toString('base64');
  const macPayload = ivBase64 + encryptedBase64;
  const hmacKey = await crypto.subtle.importKey('raw', keyMaterial, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
  const macData = await crypto.subtle.sign('HMAC', hmacKey, encoder.encode(macPayload));
  const macHex = Buffer.from(macData).toString('hex');

  // Montar o payload
  return Buffer.from(
    JSON.stringify({
      iv: ivBase64,
      mac: macHex,
      tag: '',
      value: encryptedBase64,
    })
  ).toString('base64');
};

Essa função criptografa o valor do cookie, gera um MAC e monta o payload do cookie criptografado.

E aqui está a função auxiliar para criar o prefixo do cookie do Laravel:

const createLaravelCookiePrefix = async (cookieName) => {
  const publicKey = process.env.APP_KEY;
  const encoder = new TextEncoder();
  const data = encoder.encode(`${cookieName}v2`);
  const key = encoder.encode(publicKey);

  const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
  const signature = await crypto.subtle.sign('HMAC', cryptoKey, data);
  const hexSignature = Buffer.from(signature).toString('hex');

  return `${hexSignature}|`;
};

Essas funções garantem que os cookies criptografados no Next.js possam ser descriptografados pelo Laravel, permitindo uma integração perfeita entre frameworks.

Desafios e considerações de segurança

Claro, esse processo não foi sem obstáculos:

  • Compatibilidade criptográfica: Igualar a criptografia do Laravel no Node.js exige um tratamento cuidadoso de codificação, tipos de dados e funções criptográficas.
  • Restrições de ambiente: Tivemos que garantir que essas funções rodassem apenas no lado do servidor, pois dependem de variáveis de ambiente do servidor e APIs de criptografia.
  • Considerações de segurança: Tivemos um cuidado especial para garantir que a APP_KEY permanecesse segura e nunca fosse exposta ao lado do cliente. O tratamento de erros, a verificação do MAC e garantir que o IV fosse único para cada criptografia foram fundamentais para evitar vulnerabilidades de segurança.
  • Testes e depuração: Verificar se nossa criptografia e descriptografia combinavam com a do Laravel exigiu muito teste e erro, além de um entendimento profundo de ambos os frameworks.
  • Decidir quais cookies criptografar: Nem todos os cookies precisavam ser criptografados, então selecionamos cuidadosamente apenas aqueles que manipulam dados sensíveis.

Também vale ressaltar que esta solução é específica para o nosso caso de uso e pode não ser adequada para todos os cenários. É essencial entender as implicações de segurança ao replicar mecanismos de criptografia e garantir que sua implementação seja segura e esteja em conformidade com as melhores práticas.

A recompensa: Uma experiência de usuário perfeita

Totalmente integrado
Totalmente integrado

Ao replicar os mecanismos de criptografia do Laravel no Next.js, alcançamos uma experiência de usuário perfeita. Os usuários podem navegar entre as páginas do Laravel e Next.js sem qualquer interrupção, e seus dados de sessão permanecem consistentes. Honestamente, é impressionante como o fluxo todo funciona sem que os usuários percebam a transição entre os dois frameworks.

Essa solução também abre novas possibilidades para integrar outras plataformas com o Laravel, desde que consigam replicar o processo de criptografia do Laravel. É um testemunho do poder de entender e adaptar sistemas complexos para alcançar a interoperabilidade.

Conclusão

Assim o cookie se desmancha

Integrar cookies entre dois frameworks e linguagens de programação diferentes não é exatamente uma caminhada no parque - a menos que o parque seja o Central Park, e você seja John Wick logo após ser excomungado. Mas, com uma imersão profunda em como cada framework lida com criptografia e um pouco de perseverança (e talvez um pouco de mate), é definitivamente possível.

Espero que esta explicação ilumine como enfrentar esse tipo de desafio. Se você acha esse tipo de solução de problemas empolgante, talvez queira se juntar a nós. Estamos sempre à procura de pessoas talentosas - confira nossas vagas de emprego. Sei que pode ter parecido um pesadelo, mas confie em mim, é o tipo divertido!

Happy coding!

Tags:


Publicar um comentário

Comentários

Nenhum comentário.