APIs REST

Segurança em

Segurança

 

É um requisito fundamental nos sistemas há muito tempo. Cada dia é necessário mais cuidados ao garantir que não existam acessos indevidos em um software, protegendo as informações armazenadas.

Segurança

 

Em uma aplicação web tradicional, a forma mais comum de se manter dados seguros é a utilização de sessão, iniciada logo após a autenticação de um usuário.

Nos serviços RESTful isso é diferente e precisamos encontrar alternativas.

Quanta segurança eu preciso?

Segurança

 

Isso vai depender muito se sua API é publica ou privada.
APIs públicas tem um grau de exposição muito maior e seus atacantes são desconhecidos, exigindo mais verificações para garantia de acessos legítimos e autorizados.

Segurança

 

APIs privadas podem ser mais blindadas pela infraestrutura onde está rodando. Isso requer menos preocupações para os desenvolvedores.

Mais preocupações

 

  • Quão sensíveis são seus dados?

  • Uma brecha em seu sistema abre acesso para outros sistemas internos?

Mecanismos de Segurança

Restrição pela infraestrutura

 

  • Restrições de acesso por IP;

  • Firewalls;

  • Balancedores de carga;

 

Recursos também disponíveis na nuvem

Comunicação HTTP

 

Carregando um certificado SSL

Certificado SSL

 

Secure Sockets Layer é tecnologia global de segurança padrão que permite a comunicação criptografada entre um navegador da Internet e um servidor da web.

mkdir certs
cd certs
mkcert -install
mkcert localhost

Gerando um certificado SSL

 

Faça o download do pacote mkcert em no GitHub, você precisará instalar o Chocolatey executando o PowerShell com administrador.

//...
const fs = require('fs');
const https = require('https');
//...
const portaHttp = 443;
//...
const key = fs.readFileSync('config/localhost-key.pem', 'utf8');
const cert = fs.readFileSync('config/localhost.pem', 'utf8');

const credentials = { key, cert };

const httpsServer = https.createServer(credentials, app);

httpsServer.listen(portaHttp, () => {
  console.log(`API rodando na porta ${portaHttp}`);
});

Adicionando a API

CORS

CORS

 

Ao enviar uma requisição para uma origem diferente, o navegador utiliza de um header específico enviado pelo servidor chamado Access-Control-Allow-Origin. A partir desse e mais alguns headers o navegador determina se aquele recurso será carregado ou não.

CORS

 

Tenha uma atenção especial ao header Access-Control-Allow-Origin. Você pode utilizar * como sua origin ou até em partes dela (https://*.example.com por exemplo), mas tente sempre limitar para origens conhecidas da sua aplicação, evitando assim que origens desconhecidas acessem seus servidores.

//...
const cors = require('cors');

// Configure para aceitar requisições de qualquer origem
app.use(cors());

// ou
// Especifique uma origem para limitar o acesso aos recursos
app.use(cors({origin: ["https://*.example.com"] }));

Adicionando o CORS a API

yarn add cors

Autenticação vs
Autorização

Autenticação

 

A autenticação verifica a identidade digital do usuário, ou seja, processo de verificação de uma identidade.
Em termos mais simples, é quando o usuário prova de fato quem ele é por meio de informações como:

  • Login e Senha;

  • Token;

  • Certificado Digital.

Autorização

 

Autorização é o processo que ocorre após ser validada a autenticação.
Serve para verificar se determinado usuário terá a permissão para utilizar, executar recursos ou manipular determinadas ações.

Quem é você?

O que você pode fazer?

Token-based

Authorization

Token-based Authorization

 

É uma forma simples e segura de controlar autenticação e autorização de serviços entre servidores.
Ela requer que usuários obtenham um código gerado por computador, chamado token.

 

Token-based Authorization

 

  1. O cliente se autentica no servidor informando suas credenciais;

  2. A aplicação valida as credenciais fornecendo um token;

  3. O servidor devolve uma mensagem de sucesso para o cliente;

  4. O cliente utiliza os recursos da API sempre fornecendo o token recebido;

  5. A aplicação valida cada requisição verificando se o token é válido.

JWT

JWT

 

Sigla para JSON Web Token, é uma técnica definida na RFC 7519 para autenticação remota entre duas partes. Ele é uma das formas mais utilizadas para autenticar usuários em APIs RESTful.

Autenticando

yarn add jsonwebtoken
// ou
npm install jsonwebtoken

Gerando um token JWT

{
  "secret": "palavraSecreta"
}

config/security.json

const jwt = require('jsonwebtoken');
const { Usuario } = require('../models');
const { secret } = require('../config/security.json');
const controller = {};

controller.login = async (email, senha) => {
  try {
    const usuario = await Usuario.findOne({ where: { email } });

    if (usuario.senha != senha) return false;

    return jwt.sign({ id: usuario.id }, secret, {
      expiresIn: '24h',
    });
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
};

module.exports = controller;

controller/usuario.js

const { Router } = require('express');
const router = Router();
const { login } = require('../controller/usuario');

router.post('/', async (req, res) => {
  try {
    const { email, senha } = req.body;

    const token = await login(email, senha);

    if (token) {
      res.send({ token });
    } else {
      res.status(401).send({ error: 'Login ou senha inválidos' });
    }
  } catch (error) {
    res.status(500).send({ error });
  }
});

module.exports = router;

login.js

// ...
app.use('/login', login);
app.use('/usuario', usuario);
// ...

app.js

Autorizando

Utilizando Middlewares

 

São funções que tem acesso ao objeto de solicitação (req), o objeto de resposta (res), e a próxima função de middleware no ciclo solicitação-resposta do aplicativo.

A próxima função middleware é comumente representada por uma variável chamada next.

const jwt = require('jsonwebtoken');
const { secret } = require('../config/security');

module.exports = (req, res, next) => {
  const token = req.headers['Authorization'];

  if (!token) return res.status(401).send({ error: 'Token não informado' });

  jwt.verify(token, secret, (error, decoded) => {
    if (error) return res.status(500).send(error);
    
    const { id } = jwt.decode(token);

    req.usuarioId = id;

    next();
  });
};

middlewares/auth.js

// ...
app.use('/login', login);
app.use(auth);
app.use('/usuario', usuario);
// ...

app.js

Tratando Dados Sensíveis

Exposição de dados

 

Caso uma API apresente problemas, esteja desprotegida ou seja hackeada, muito provavelmente você terá seus dados violados.
O resultado é a exposição pública de dados financeiros, médicos ou pessoais. Uma verdadeira invasão de privacidade.

Acesso a dados sensíveis

 

Dados sensíveis são informações privadas. Um dado sensível pode ser seu nome completo, endereço de residência ou trabalho, número de telefone, CPF, RG e número de cartão de crédito. Já deu para perceber que as consequências da exposição de tais dados seria grave.
Quando falamos de senhas a situação piora mais ainda.

Pacote BCrypt

 

Desenvolvido com o propósito de esconder senhas em forma de texto puro, ele utiliza o algoritmo hash.
Ideal para armazenar senhas em banco de dados, podendo ser utilizado em diversas linguagens.

 

Salt

 

Uma das maiores vantagens do pacote é acrescentar sequências de caracteres a senha que aumentam a segurança contra ataques de força bruta.

Senha = 123456
Salt = 4137445196
Senha + Salt = 1413744519665432
Senha criptografada = 4564137445196321

yarn add bcrypt
// ou 
npm install bcrypt

Pacote BCrypt

{
  "secret": "palavraSecreta",
  "saltos": 10
}

config/token.json

const bcrypt = require('bcrypt');
const { saltos } = require('../config/token');

module.exports = function (sequelize, DataTypes) {
  return sequelize.define(
    'usuario',    
    {
      // atributos
    },
   	{
      //...
      hooks: {
        beforeValidate: (usuario) => {
          if (usuario.senha) usuario.senha = bcrypt.hashSync(usuario.senha, saltos);
        },
      },
    }
  );
};

bd/usuario.js

const bcrypt = require('bcrypt');
const { Usuario } = require('../bd');
const jwt = require('jsonwebtoken');
const { secret } = require('../config/token.json');
const controller = {};

controller.login = async (email, senha) => {
  try {
    const usuario = await Usuario.findOne({ where: { email } });
    const senhaCorreta = await bcrypt.compare(senha, usuario.senha);

    if (!senhaCorreta) return false;

    return jwt.sign({ id: usuario.id }, secret, {
      expiresIn: '24h',
    });
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
};

module.exports = controller;

controle/usuario.js

module.exports = function (sequelize, DataTypes) {
  return sequelize.define(
    'usuario',    
    {
      // atributos
    },
   	{
      //...
      defaultScope: {
        attributes: {
          exclude: ['senha'],
        },
      },
      scopes: {
        login: {
          attributes: ['id', 'senha'],
        },
      }
    }
  );
};

bd/usuario.js

const bcrypt = require('bcrypt');
const { Usuario } = require('../bd');
const jwt = require('jsonwebtoken');
const { secret } = require('../config/token.json');
const controller = {};

controller.login = async (email, senha) => {
  try {
    const usuario = await Usuario.scope('login').findOne({ where: { email } });
    const senhaCorreta = await bcrypt.compare(senha, usuario.senha);

    if (!senhaCorreta) return false;

    return jwt.sign({ id: usuario.id }, secret, {
      expiresIn: '24h',
    });
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
};

module.exports = controller;

controle/usuario.js

Limitando acesso por usuário

 

Já que possuímos informações do usuário no token JWT, precisamos adequar nossa aplicação para organizar a visualização dos dados.

const { Router } = require("express");
const { Nota, Checklist, sequelize } = require("../bd");
const router = Router();

router.post("/", async (req, res) => {
  //...
  const { body, usuarioId } = req;

  body = { ...body, usuarioId };
  checklists = body.checklists;

  //..

  res.send(nota);
});

rotas/usuario.js

const { Router } = require("express");
const { Nota, Checklist, sequelize } = require("../bd");
const router = Router();

router.get("/:id?", async (req, res) => {
  //...
  const { usuarioId } = req;
  
  if (req.params.id) {
    resultado = await Usuario.findOne({
      where: {
        id: req.params.id,
        usuarioId
      }
    });    
  } else {
    result = await Usuario.findAll({
      where: {
        usuarioId,
      },
    });
  }
  //..

  res.send(result);
});

rotas/usuario.js

Segurança em APIs Rest

By Alan Ferreira dos Santos

Segurança em APIs Rest

  • 524