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
-
O cliente se autentica no servidor informando suas credenciais;
-
A aplicação valida as credenciais fornecendo um token;
-
O servidor devolve uma mensagem de sucesso para o cliente;
-
O cliente utiliza os recursos da API sempre fornecendo o token recebido;
-
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