Pular para o conteúdo principal

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).

jwt-flow

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 do config.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:

_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:

_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.