Como habilitar CORS com cookie HTTPOnly para proteger o token?

Publicados: 2021-12-29

Neste artigo, vemos como habilitar CORS (Cross-Origin Resource Sharing) com cookie HTTPOnly para proteger nossos tokens de acesso.

Hoje em dia, servidores back-end e clientes front-end são implantados em domínios diferentes. Portanto, o servidor deve habilitar o CORS para permitir que os clientes se comuniquem com o servidor nos navegadores.

Além disso, os servidores estão implementando autenticação sem estado para melhor escalabilidade. Os tokens são armazenados e mantidos no lado do cliente, mas não no lado do servidor, como na sessão. Por segurança, é melhor armazenar tokens em cookies HTTPOnly.

Por que as solicitações de origem cruzada são bloqueadas?

Vamos supor que nosso aplicativo de front-end foi implantado em https://app.geekflare.com . Um script carregado em https://app.geekflare.com pode solicitar apenas recursos da mesma origem.

Sempre que tentamos enviar uma solicitação de origem cruzada para outro domínio https://api.geekflare.com ou outra porta https://app.geekflare.com:3000 ou outro esquema http://app.geekflare.com , o a solicitação de origem cruzada será bloqueada pelo navegador.

Mas por que a mesma solicitação bloqueada pelo navegador pode ser enviada de qualquer servidor backend usando curl request ou enviada usando ferramentas como o carteiro sem nenhum problema de CORS. Na verdade, é para segurança proteger os usuários de ataques como CSRF (Cross-Site Request Forgery).

Vamos dar um exemplo, suponha que se algum usuário logou em sua própria conta do PayPal em seu navegador. Se pudermos enviar uma solicitação de origem cruzada para paypal.com partir de um script carregado em outro domínio malicious.com sem nenhum erro / bloqueio de CORS, como enviamos a solicitação de mesma origem.

Os invasores podem enviar facilmente sua página maliciosa https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account , convertendo-a em URL curto para ocultar o URL real. Quando o usuário clica em um link malicioso, o script carregado no domínio malicious.com enviará uma solicitação de origem cruzada ao PayPal para transferir a quantidade do usuário para que a conta do PayPal do invasor seja executada. Todos os usuários que efetuaram login em sua conta do PayPal e clicaram neste link malicioso perderão seu dinheiro. Qualquer pessoa pode facilmente roubar dinheiro sem o conhecimento do usuário de uma conta do PayPal.

Pelo motivo acima, os navegadores bloqueiam todas as solicitações de origem cruzada.

O que é CORS (Cross-Origin Resource Sharing)?

CORS é um mecanismo de segurança baseado em cabeçalho usado pelo servidor para instruir o navegador a enviar uma solicitação de origem cruzada de domínios confiáveis.
O servidor habilitado com cabeçalhos CORS usados ​​para evitar solicitações de origem cruzada bloqueadas por navegadores.

Como funciona o CORS?

Como o servidor já definiu seu domínio confiável em sua configuração CORS. Quando enviamos uma solicitação ao servidor, a resposta dirá ao navegador se o domínio solicitado é confiável ou não em seu cabeçalho.

Existem dois tipos de solicitações CORS:

  • Pedido simples
  • Solicitação de pré-voo

Solicitação simples:

O fluxo de solicitação simples do CORS informa que ele envia uma solicitação de origem cruzada, mas quando recebe uma resposta. Ele verifica os cabeçalhos.

  • O navegador envia a solicitação para um domínio de origem cruzada com origem (https://app.geekflare.com).
  • O servidor envia de volta a resposta correspondente com métodos permitidos e origem permitida.
  • Depois de receber a solicitação, o navegador verificará se o valor do cabeçalho de origem enviado ( https://app.geekflare.com ) e o valor access-control-allow-origin ( https://app.geekflare.com ) recebido são iguais ou curinga (*). Caso contrário, ele gerará um erro CORS.

Solicitação de comprovação:

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • Dependendo do parâmetro de solicitação personalizado da solicitação de origem cruzada como métodos (PUT, DELETE) ou cabeçalhos personalizados ou tipo de conteúdo diferente, etc. O navegador decidirá enviar uma solicitação OPTIONS de preflight para verificar se a solicitação real é segura para enviar ou não.
  • Depois de receber a resposta (código de status: 204, o que significa nenhum conteúdo), o navegador verificará os parâmetros de permissão de controle de acesso para a solicitação real. Se os parâmetros de solicitação forem permitidos pelo servidor. A solicitação de origem cruzada real enviada e recebida

Se access-control-allow-origin: * , a resposta é permitida para todas as origens. Mas não é seguro, a menos que você precise.

Como habilitar o CORS?

Para habilitar CORS para qualquer domínio, habilite cabeçalhos CORS para permitir origem, métodos, cabeçalhos personalizados, credenciais, etc.

O navegador lê o cabeçalho CORS do servidor e permite solicitações reais do cliente somente após verificar os parâmetros da solicitação.

  • Access-Control-Allow-Origin: Para especificar domínios exatos (https://app.geekflate.com, https://lab.geekflare.com) ou curinga (*)
  • Access-Control-Allow-Methods: Para permitir os métodos HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) que só nós precisamos.
  • Access-Control-Allow-Headers: para permitir apenas cabeçalhos específicos (autorização, csrf-token)
  • Access-Control-Allow-Credentials: valor booleano usado para permitir credenciais de origem cruzada (cookies, cabeçalho de autorização).
  • Access-Control-Max-Age: Diz ao navegador para armazenar em cache a resposta do preflight por algum tempo.
  • Access-Control-Expose-Headers: Especifique os cabeçalhos que podem ser acessados ​​pelo script do lado do cliente.

Para ativar o CORS no apache e no servidor da web Nginx, siga este tutorial.

Habilitando CORS em ExpressJS

Vamos dar um exemplo de aplicativo ExpressJS sem CORS:

 const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })

No exemplo acima, habilitamos o endpoint da API dos usuários para os métodos POST, PUT e GET, mas não para o método DELETE.

Para facilitar a ativação do CORS no aplicativo ExpressJS, você pode instalar o cors

 npm install cors

Access-Control-Allow-Origin

Habilitando CORS para todos os domínios

 app.use(cors({ origin: '*' }));

Habilitando CORS para um único domínio

 app.use(cors({ origin: 'https://app.geekflare.com' }));

Se você deseja permitir CORS para origem https://app.geekflare.com e https://lab.geekflare.com

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));

Métodos de permissão de controle de acesso

Para ativar o CORS para todos os métodos, omita esta opção no módulo CORS no ExpressJS. Mas para habilitar métodos específicos (GET, POST, PUT).

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));

Access-Control-Allow-Headers

Usado para permitir que outros cabeçalhos além dos padrões sejam enviados com solicitações reais.

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));

Access-Control-Allow-Credentials

Omita isso se você não quiser dizer ao navegador para permitir credenciais mediante solicitação, mesmo quando withCredentials estiver definido como true .

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));

Access-Control-Max-Age

Para intimar o navegador a armazenar em cache as informações de resposta da comprovação no cache por um segundo especificado. Omita isso se você não quiser armazenar a resposta em cache.

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));

A resposta do preflight em cache ficará disponível por 10 minutos no navegador.

Access-Control-Expose-Headers

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));

Se colocarmos o curinga (*) em exposHeaders, ele não exporá o cabeçalho de autorização. Portanto, temos que expor explicitamente como abaixo

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));

O código acima irá expor todos os cabeçalhos e cabeçalhos de autorização também.

O que é um cookie HTTP?

Um cookie é um pequeno pedaço de dado que o servidor enviará ao navegador do cliente. Em solicitações posteriores, o navegador enviará todos os cookies relacionados ao mesmo domínio em todas as solicitações.

Cookie tem seu atributo, que pode ser definido para fazer um cookie funcionar de maneira diferente conforme a nossa necessidade.

  • Nome Nome do cookie.
  • valor: dados do cookie respectivos ao nome do cookie
  • Domínio: os cookies serão enviados apenas para o domínio definido
  • Caminho: cookies enviados somente após o caminho de prefixo de URL definido. Suponha que definimos nosso caminho de cookie como path = 'admin /'. Cookies não enviados para o URL https://geekflare.com/expire/, mas enviados com o prefixo de URL https://geekflare.com/admin/
  • Max-Age / Expires (número em segundos): Quando o cookie deve expirar. O tempo de vida do cookie torna o cookie inválido após o tempo especificado.
  • HTTPOnly (Booleano): O servidor de back-end pode acessar esse cookie HTTPOnly, mas não o script do lado do cliente quando verdadeiro.
  • Seguro (Booleano): Cookies enviados apenas por um domínio SSL / TLS quando verdadeiros.
  • sameSite (string [Strict, Lax, None]): Usado para habilitar / restringir cookies enviados em solicitações entre sites. Para saber mais detalhes sobre os cookies do sameSite consulte MDN. Aceita três opções Strict, Lax, None. Valor seguro do cookie definido como verdadeiro para a configuração do cookie sameSite = None.

Por que cookie HTTPOnly para tokens?

Armazenar o token de acesso enviado do servidor no armazenamento do lado do cliente como armazenamento local , banco de dados indexado e cookie (HTTPOnly não definido como verdadeiro) é mais vulnerável a ataques XSS. Suponha que qualquer uma de suas páginas seja fraca para um ataque XSS. Os invasores podem usar indevidamente os tokens de usuário armazenados no navegador.

Os cookies HTTPOnly são apenas definidos / obtidos pelo servidor / backend, mas não no lado do cliente.

O script do lado do cliente tem acesso restrito a esse cookie HTTPonly. Portanto, os cookies HTTPOnly não são vulneráveis ​​a ataques XSS e são mais seguros. Porque só pode ser acessado pelo servidor.

Habilitar cookie HTTPOnly no back-end habilitado para CORS

A ativação do Cookie no CORS requer a configuração abaixo no aplicativo / servidor.

  • Defina o cabeçalho Access-Control-Allow-Credentials como verdadeiro.
  • Access-Control-Allow-Origin e Access-Control-Allow-Headers não devem ser um caractere curinga (*).
  • O atributo cookie sameSite deve ser Nenhum.
  • Para ativar o valor sameSite como nenhum, defina o valor seguro como verdadeiro: Ative o back-end com certificado SSL / TLS para funcionar no nome de domínio.

Vamos ver um exemplo de código que define um token de acesso no cookie HTTPOnly após verificar as credenciais de login.

 const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });

Você pode configurar cookies CORS e HTTPOnly implementando as quatro etapas acima em sua linguagem de back-end e servidor da web.

Você pode seguir este tutorial para apache e Nginx para habilitar CORS seguindo as etapas acima.

withCredentials para solicitação de origem cruzada

Credenciais (Cookie, Autorização) enviadas com a solicitação de mesma origem por padrão. Para origem cruzada, temos que especificar withCredentials para true.

API XMLHttpRequest:

 var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.geekflare.com/user', true); xhr.withCredentials = true; xhr.send(null);

Fetch API:

 fetch('http://api.geekflare.com/user', { credentials: 'include' });

JQuery Ajax:

 $.ajax({ url: 'http://api.geekflare.com/user', xhrFields: { withCredentials: true } });

Axios:

 axios.defaults.withCredentials = true

Conclusão

Espero que o artigo acima ajude você a entender como funciona o CORS e habilite o CORS para solicitações de origem cruzada no servidor. Por que armazenar cookies em HTTPOnly é seguro e como usar credenciais em clientes para solicitações de origem cruzada.