Autenticação JWT (JSON Web Token)
Configuração e utilização da autenticação com token.
Introdução
O JWT (JSON Web Token) é um padrão para garantir a segurança e contexto da autenticação em aplicações web, onde o frontend se encontra desacoplado do backend.
É comum ser utilizado quando o frontend de uma aplicação web é desenvolvida em ReactJS, AngularJS, VueJS, etc. e o backend proporciona uma API REST para fornecer e receber dados dinâmicos da aplicação.
O token (JWT) é um código seguro que identifica o usuário.
Com o JWT (JSON Web Token) a API REST do backend permite aos serviços privados obterem o contexto de autenticação, para saber quem é o usuário autenticado, ou seja, a partir do token podemos saber qual é o usuário, sendo que o token no caso é JWT (JSON Web Token).
CORS
A API REST pode ficar num subdomínio diferente do website, o que implica configurações de CORS (Cross-Origin Resource Sharing) para os serviços poderem ser acedidos.
Portanto, caso a API REST esteja em um domínio/subdomínio diferente do frontend, mesmo utilizando JWT, é necessário que o CORS esteja corretamente configurado.
CORS é uma política de segurança de consumo de recursos em domínios, subdomínios ou portas, diferentes. Para garantir que um endereço pode realmente integrar com outro endereço.
Isto acontece devido aos navegadores bloquearem a utilização de serviços, ou outros tipos de recursos, em endereços externos, visando evitar ataques de obtenção de dados confidenciais.
Ativação e Configuração
Para ativar e configurar o JWT na sua aplicação no Netuno, é necessário editar o arquivo de configuração da aplicação referente ao ambiente que está a utilizar, como:
config/_development.json
config/_production.json
Depende do ambiente configurado no Netuno, no arquivo
netuno/config.js
ver a parametrização doconfig.env
.
Insira e ajuste os seguintes parâmetros:
...
"auth": {
"jwt": {
"enabled": true,
"secret": "SegredoTemQueTer32Characteres!!!",
"expires": {
"access": 1440,
"refresh": 1440
}
}
},
...
No parâmetro secret
coloque uma sequência de caracteres complexa e aleatória, é a chave que vai garantir a
segurança da encriptação do JWT.
Os parâmetros de expires
são definidos em minutos, por exemplo: 60
que equivale a 1 hora e 1440
a um dia.
Acesso Restrito à Grupos
Adicionalmente pode ser passado o parâmetro groups
, que define o código de Grupos dos usuários que podem
autenticar com JWT, por exemplo:
...
"auth": {
"jwt": {
...
"groups": ["cliente", "fornecedor"],
...
}
},
...
No exemplo acima, apenas os usuários que pertencem aos grupos dos:
- Clientes, sendo o código do grupo
cliente
. - Fornecedores, sendo o código do grupo
fornecedor
Apenas os usuários nestes grupos podem autenticar com JWT.
Como Obter o Access Token
Para obter o Access Token do JWT o Netuno fornece o serviço _auth
que valida a autenticação e, se a mesma for
bem sucedida, devolve o Access Token e o Refresh Token.
O Access Token é obtido da seguinte forma:
Fetch - JavaScript Puro
O JavaScript nos navegadores fornece o fetch
para integrar com os serviços da API REST do backend.
Segue alguns exemplos de como realizar a autenticação com o JWT e a renovação do token.
Autenticação
Exemplo de como obter o Access Token com o fetch:
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
username: payload.username,
password: payload.password,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log('Autenticação falhou com o status inesperado:', response.status);
}
alert('Login inválido.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`Meu Acccess Token: ${res.access_token}`);
console.log(`Meu Refresh Token: ${res.refresh_token}`);
console.log(`Expira em: ${res.expires_in} minutos`);
sessionStorage.setItem('token', JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('Houve um problema técnico, tente novamente mais tarde.');
});
Repare que o token
deverá ficar guardado como sessão, portanto é preferível utilizar a sessionStorage
em vez do localStorage para maior segurança.
Com o refresh_token
é possível gerar um novo token
antes do tempo de expiração (expires_in
em minutos).
Utilizar o Access Token em outros Serviços
Para executar serviços programados à medida que exijam autenticação prévia em aplicações Netuno, deve passar o Access Token no Header do protocolo HTTP, como valor da chave Authorization, por exemplo:
Authorization: Bearer eyJhbGciOiJIUzU...
Exemplo de como executar um serviço programado à medida através do frontend utilizando o fetch, onde o
Authorization
é passado nos headers
:
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/meu-servico", {
method: 'post',
credentials: 'include',
headers: {
'Authorization': `${token.token_type} ${token.access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
meuParametro1: '...',
meuParametro2: '...'
})
}).then((response) => {
if (response.status != 200) {
console.log(`Serviço falhou com o status ${response.status}.`);
if (response.status == 403) {
alert('Não autenticado.');
} else {
alert('Houve uma falha com o serviço.');
}
return null;
}
return response.json();
}).then((res) => {
if (res) {
console.log(`Dados de resposta do meu serviço:`, res);
alert('Serviço executado com sucesso.');
}
}).catch((error)=> {
console.log(error);
alert('Houve um problema técnico, tente novamente mais tarde.');
});
Repare que, nos Headers do HTTP, no parâmetro Authorization
é utilizado o token_type
e o access_token
obtidos do objeto JWT guardado previamente na autenticação no
sessionStorage.
Atualizar com o Refresh Token
Para atualizar o token, e prolongar a autenticação, deve chamar novamente o serviço _auth
, mas além do parâmetro
do jwt: true
é necessário passar também o parâmetro do refresh_token
com o valor recebido na autenticação bem
sucedida anteriormente.
A atualização do token deve ser feita antes do tempo de expiração (expires_in
em minutos).
Então para prolongar a autenticação e renovar o token, veja o exemplo:
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
refresh_token: token.refresh_token,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log('A renovação do token falhou com o status inesperado:', response.status);
}
alert('Renovação do acesso com token inválido.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`Meu Novo Acccess Token: ${res.access_token}`);
console.log(`Meu Novo Refresh Token: ${res.refresh_token}`);
console.log(`Expira em: ${res.expires_in} minutos`);
sessionStorage.setItem("token", JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('Houve um problema técnico, tente novamente mais tarde.');
});
Ao obter o novo token deve passar a utiliza-lo, ao invés do anterior (antigo) nas próximas chamadas aos serviços.
Desta forma a autenticação é prolongada com sucessivas renovações do token, é uma forma de garantir que o usuário continua autenticado.
Auth Client - NPM
Para facilmente realizar a integração com o frontend é disponibilizado o módulo do NPM:
No frontend pode realizar a instalação com o PNPM:
pnpm install @netuno/auth-client
Pode ser utilizado outro gestor de dependências de frontend como NPM, YARN, ou outro.
Configuração
Este módulo depende do @netuno/service-client, ao definir o endereço dos serviços, por exemplo:
import _service from '@netuno/service-client';
...
_service.config({
prefix: 'http://localhost:9000/services/'
});
O auth-client
utiliza o prefixo do service-client
para construir internamente o endereço do serviço _auth
,
neste caso, seguindo o exemplo será:
http://localhost:9000/services/_auth
Então este endereço é construído automaticamente utilizando o prefixo de endereço dos serviços da API REST da aplicação Netuno.
Veja como customizar as configurações do auth-client
:
_auth.config({
storage: 'local', // session é o valor padrão.
onLogin: () => { alert("Logged in!"); },
onLogout: () => { alert("Logged out!"); }
});
Caso queira que o token seja salvo no localStorage
então é necessário definir na configuração o
storage: 'local'
, porque por padrão é storage: 'session'
utilizando o sessionStorage
.
Autenticação
Para efetuar o login utilizando o _auth
:
import _auth from '@netuno/auth-client';
...
_auth.login({
username: "admin",
password: "secret",
success: ()=> {
alert("Success.");
},
fail: ()=> {
alert("Fail.");
}
});
O token é salvo automaticamente, no session
(sessionStorage
) ou local
(localStorage
), dependendo da
configuração, por padrão é session
.
Atualizar o Token
Exemplo de como atualizar o token:
import _auth from '@netuno/auth-client';
...
_auth.refreshToken({
success: ()=> {
alert("Success.");
},
fail: ()=> {
alert("Fail.");
}
});
O token é salvo automaticamente, no session
(sessionStorage
) ou local
(localStorage
), dependendo da
configuração, por padrão é session
.
Remover o Token
Exemplo de como terminar a sessão, ou seja, o processo de sair e eliminar o token:
import _auth from '@netuno/auth-client';
...
_auth.logout();
O token é removido do sessionStorage
ou do localStorage
dependendo da configuração, o padrão é o
sessionStorage
.
Backend - Serviços da API REST
Se o header Authorization
é passado na integração com os serviços com um token válido, então é possível
identificar qual é o usuário e o grupo.
No backend podemos utilizar o recurso _user
e o _group
, que identifica qual é o usuário e o grupo
respectivamente.
Para ver os dados do usuário autenticado utilizamos o recurso User, por exemplo:
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Usuário autenticado:",
_val.map()
.set("id", _user.id())
.set("nome", _user.name())
.set("codigo", _user.code())
.set("tudo", _user.data())
)
_log.info(
"Usuário autenticado:",
_val.map()
.set("id", _user.id())
.set("nome", _user.name())
.set("codigo", _user.code())
.set("tudo", _user.data())
)
_log.info(
"Usuário autenticado:",
_val.map()
.set("id", _user.id())
.set("nome", _user.name())
.set("codigo", _user.code())
.set("tudo", _user.data())
)
_log.info(
"Usuário autenticado:",
_val.map()
.set("id", _user.id())
.set("nome", _user.name())
.set("codigo", _user.code())
.set("tudo", _user.data())
)
_log.info(
"Usuário autenticado:",
_val.map()
.set("id", _user.id())
.set("nome", _user.name())
.set("codigo", _user.code())
.set("tudo", _user.data())
)
Para ver o grupo do usuário autenticado utilizamos o recurso Group, por exemplo:
- JavaScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Grupo autenticado:",
_val.map()
.set("id", _group.id())
.set("nome", _group.name())
.set("codigo", _group.code())
.set("tudo", _group.data())
)
_log.info(
"Grupo autenticado:",
_val.map()
.set("id", _group.id())
.set("nome", _group.name())
.set("codigo", _group.code())
.set("tudo", _group.data())
)
_log.info(
"Grupo autenticado:",
_val.map()
.set("id", _group.id())
.set("nome", _group.name())
.set("codigo", _group.code())
.set("tudo", _group.data())
)
_log.info(
"Grupo autenticado:",
_val.map()
.set("id", _group.id())
.set("nome", _group.name())
.set("codigo", _group.code())
.set("tudo", _group.data())
)
_log.info(
"Grupo autenticado:",
_val.map()
.set("id", _group.id())
.set("nome", _group.name())
.set("codigo", _group.code())
.set("tudo", _group.data())
)
Segurança
Caso o token seja inválido, ou se não for passado o Authorization
, então o Netuno garante que o serviço não
é executado, exceto se o serviço estiver configurado como acesso público, por padrão os serviços são privados.
Para gerir se o serviço é público ou privado configure na sua aplicação em:
server/core/_service_config.js
Sendo que em desenvolvimento o Netuno permite executar os serviços privados diretamente sem autenticação, por questões de praticidade, porque a maioria dos serviços programados normalmente não precisam do contexto de autenticação, então torna-se mais simples no geral o desenvolvimento e os testes.
Não sendo em desenvolvimento por, exemplo em produção, então os serviços apenas podem ser executados com autenticação, como é óbvio.
Recurso de Autenticação
Normalmente o recurso _user
e _group
são suficientes, mas há situações que queremos realizar operações mais
avançadas e neste caso temos o recurso _auth
.
O recurso _auth
, é o recurso principal de autenticação, gere as configurações e tem todas as operações
relacionadas com o processo de autenticação.
Para saber mais, veja a documentação do recurso Auth.
ReAuthKit
O Netuno fornece um projeto base para criar plataformas com autenticação online, que tem a criação de conta, login, perfil, avatar, recuperação da senha, e muito mais.
Confere aí no GitHub:
O ReAuthKit é uma aplicação Netuno com o frontend feito em React, Ant.Design e React Router, e a base de dados em PostgreSQL.
Com o ReAuthKit acelera muito a criação de novos projetos com autenticação, porque já tem todos os mecanismos necessários para qualquer projeto que necessite de autenticação.
Conclusão
De forma simples, é possível configurar e ativar a autenticação com JWT (JSON Web Token) nas aplicações.
Utilize a autenticação com JWT para desenvolver serviços seguros na API REST.
A integração do JWT no frontend pode ser utilizando diretamente com o fetch
.
Para agilizar e padronizar a implementação é disponibilizado o módulo NPM @netuno/auth-client, que pode ser utilizado com qualquer tecnologia de frontend.
Sempre que for preciso identificar o usuário na execução de serviços da API REST utilize a autenticação com JWT.
No backend é muito simples a configuração e a obtenção do usuário autenticado.