Autentificare JWT (JSON Web Token)
Configurarea și utilizarea autentificării cu token.
Introducere
JWT (JSON Web Token) este un standard pentru a garanta securitatea și contextul autentificării în aplicațiile web, unde frontend-ul este decuplat de backend.
Este utilizat frecvent atunci când frontend-ul unei aplicații web este dezvoltat în ReactJS, AngularJS, VueJS etc., iar backend-ul oferă o API REST pentru a furniza și primi date dinamice ale aplicației.
Tokenul (JWT) este un cod sigur care identifică utilizatorul.
Cu JWT (JSON Web Token), API REST a backend-ului permite serviciilor private să obțină contextul de autentificare, pentru a ști cine este utilizatorul autentificat; adică, pe baza tokenului putem afla care este utilizatorul, tokenul fiind în acest caz JWT (JSON Web Token).

CORS
API REST poate fi găsit într-un subdomeniu diferit de cel al site-ului web, ceea ce implică configurații CORS (Cross-Origin Resource Sharing) pentru ca serviciile să poată fi accesate.
Prin urmare, dacă API REST se află într-un domeniu/subdomeniu diferit de frontend, chiar și utilizând JWT, este necesar ca CORS să fie configurat corect.
CORS este o politică de securitate privind consumul de resurse în domenii, subdomenii sau porturi diferite. Pentru a garanta că o adresă poate chiar să se integreze cu o altă adresă.
Acest lucru se întâmplă deoarece browserele blochează utilizarea serviciilor sau a altor tipuri de resurse la adrese externe, pentru a evita atacuri de obținere a datelor confidențiale.
Activare și configurare
Pentru a activa și configura JWT în aplicația dvs. din Netuno, este necesar să editați fișierul de configurare al aplicației aferent mediului pe care îl utilizați, cum ar fi:
config/_development.jsonconfig/_production.json
Depinde de mediul configurat în Netuno; în fișierul
netuno/config.js, consultați parametrizareaconfig.env.
Introduceți și ajustați următorii parametri:
...
"auth": {
"jwt": {
"enabled": true,
"secret": "SecretulTrebuieSaAiba32Caractere!!!",
"expires": {
"access": 1440,
"refresh": 1440
}
}
},
...
În parametrul secret puneți o secvență complexă și aleatorie de caractere; este cheia care va garanta securitatea
criptării JWT.
Parametrii expires sunt definiți în minute, de exemplu: 60, care echivalează cu 1 oră, și 1440 cu o zi.
Acces restricționat la grupuri
În plus, se poate transmite parametrul groups, care definește codul grupurilor de utilizatori care se pot autentifica
cu JWT, de exemplu:
...
"auth": {
"jwt": {
...
"groups": ["client", "furnizor"],
...
}
},
...
În exemplul de mai sus, doar utilizatorii care aparțin grupurilor:
- Clienți, codul grupului fiind
client. - Furnizori, codul grupului fiind
furnizor.
Doar utilizatorii din aceste grupuri se pot autentifica cu JWT.
Cum se obține Access Token
Pentru a obține Access Token-ul JWT, Netuno furnizează serviciul _auth, care validează autentificarea și, dacă
aceasta are succes, returnează Access Token și Refresh Token.
Access Token este obținut în felul următor:
Fetch - JavaScript pur
JavaScript-ul din browsere furnizează fetch pentru integrarea cu serviciile API REST ale backend-ului.
Urmează câteva exemple despre cum să realizați autentificarea cu JWT și reînnoirea tokenului.
Autentificare
Exemplu de obținere a Access Token-ului cu fetch:
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
username: dateAutentificare.username,
password: dateAutentificare.password,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log('Autentificarea a eșuat cu un status neașteptat:', response.status);
}
alert('Login invalid.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`Tokenul meu de acces: ${res.access_token}`);
console.log(`Tokenul meu de reîmprospătare: ${res.refresh_token}`);
console.log(`Expiră în: ${res.expires_in} minute`);
sessionStorage.setItem('token', JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('A apărut o problemă tehnică, încercați din nou mai târziu.');
});
Observați că token trebuie păstrat ca sesiune, prin urmare este preferabil să utilizați sessionStorage
în locul localStorage pentru o mai mare securitate.
Cu refresh_token este posibil să generați un token nou înainte de timpul de expirare (expires_in în minute).
Utilizarea Access Token în alte servicii
Pentru a executa servicii programate la comandă care necesită autentificare prealabilă în aplicațiile Netuno, trebuie să transmiteți Access Token în header-ul protocolului HTTP, ca valoare a cheii Authorization, de exemplu:
Authorization: Bearer eyJhbGciOiJIUzU...
Exemplu de executare a unui serviciu programat la comandă prin frontend folosind fetch, unde
Authorization este transmis în headers:
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/serviciul-meu", {
method: 'post',
credentials: 'include',
headers: {
'Authorization': `${token.token_type} ${token.access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
parametrulMeu1: '...',
parametrulMeu2: '...'
})
}).then((response) => {
if (response.status != 200) {
console.log(`Serviciul a eșuat cu statusul ${response.status}.`);
if (response.status == 403) {
alert('Neautentificat.');
} else {
alert('A apărut o eroare la serviciu.');
}
return null;
}
return response.json();
}).then((res) => {
if (res) {
console.log(`Datele de răspuns ale serviciului meu:`, res);
alert('Serviciul a fost executat cu succes.');
}
}).catch((error)=> {
console.log(error);
alert('A apărut o problemă tehnică, încercați din nou mai târziu.');
});
Observați că, în header-ele HTTP, la parametrul Authorization sunt utilizați token_type și access_token
obținuți din obiectul JWT salvat anterior la autentificare în
sessionStorage.
Actualizare cu Refresh Token
Pentru a reînnoi tokenul și a prelungi autentificarea, trebuie să apelați din nou serviciul _auth, dar pe lângă
parametrul jwt: true este necesar să transmiteți și parametrul refresh_token cu valoarea primită la autentificarea
reușită anterior.
Reînnoirea tokenului trebuie făcută înainte de timpul de expirare (expires_in în minute).
Așadar, pentru a prelungi autentificarea și a reînnoi tokenul, vedeți exemplul:
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('Reînnoirea tokenului a eșuat cu un status neașteptat:', response.status);
}
alert('Reînnoirea accesului cu token invalid.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`Noul meu token de acces: ${res.access_token}`);
console.log(`Noul meu token de reîmprospătare: ${res.refresh_token}`);
console.log(`Expiră în: ${res.expires_in} minute`);
sessionStorage.setItem("token", JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('A apărut o problemă tehnică, încercați din nou mai târziu.');
});
După obținerea noului token, trebuie să îl utilizați în locul celui anterior (vechi) în următoarele apeluri către servicii.
Astfel, autentificarea este prelungită prin reînnoiri succesive ale tokenului, o modalitate de a garanta că utilizatorul rămâne autentificat.
Auth Client - NPM
Pentru a realiza cu ușurință integrarea cu frontend-ul este disponibil modulul NPM:
În frontend puteți realiza instalarea cu Bun:
bun install @netuno/auth-client
Poate fi utilizat un alt manager de dependențe de frontend precum PNPM, NPM, YARN sau altul.
Configurare
Acest modul depinde de @netuno/service-client, la definirea adresei serviciilor, de exemplu:
import _service from '@netuno/service-client';
...
_service.config({
prefix: 'http://localhost:9000/services/'
});
auth-client utilizează prefixul din service-client pentru a construi intern adresa serviciului _auth,
în acest caz, urmând exemplul, va fi:
http://localhost:9000/services/_auth
Astfel, această adresă este construită automat utilizând prefixul de adresă al serviciilor API REST ale aplicației Netuno.
Vedeți cum să personalizați configurațiile auth-client:
În versiunea ES module, config() este asincron. Așteptați finalizarea sa înainte de a utiliza clientul configurat:
import _auth from '@netuno/auth-client';
await _auth.config({
storage: 'local', // session este valoarea implicită.
onLogin: () => { alert("Conectat!"); },
onLogout: () => { alert("Deconectat!"); }
});
Dacă doriți ca tokenul să fie salvat în localStorage, atunci este necesar să definiți în configurare
storage: 'local', deoarece implicit este storage: 'session', utilizând sessionStorage.
Autentificare
Pentru a efectua login-ul utilizând _auth:
import _auth from '@netuno/auth-client';
...
_auth.login({
username: "admin",
password: "parola",
success: ()=> {
alert("Succes.");
},
fail: ()=> {
alert("Eșec.");
}
});
Tokenul este salvat automat, în session (sessionStorage) sau local (localStorage), în funcție de
configurare; implicit este session.
Actualizarea tokenului
Exemplu de reînnoire a tokenului:
import _auth from '@netuno/auth-client';
...
_auth.refreshToken({
success: ()=> {
alert("Succes.");
},
fail: ()=> {
alert("Eșec.");
}
});
Tokenul este salvat automat, în session (sessionStorage) sau local (localStorage), în funcție de
configurare; implicit este session.
Eliminarea tokenului
Exemplu de încheiere a sesiunii, adică procesul de deconectare și eliminare a tokenului:
import _auth from '@netuno/auth-client';
...
_auth.logout();
Tokenul este eliminat din sessionStorage sau din localStorage în funcție de configurare; implicit este
sessionStorage.
Backend - Servicii API REST
Dacă header-ul Authorization este transmis în integrarea cu serviciile cu un token valid, atunci este posibil
să identificați utilizatorul și grupul.
În backend putem utiliza resursa _user și _group, care identifică utilizatorul și, respectiv, grupul.
Pentru a vedea datele utilizatorului autentificat folosim resursa User, de exemplu:
- JavaScript
- TypeScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Utilizator autentificat:",
_val.map()
.set("id", _user.id())
.set("nume", _user.name())
.set("cod", _user.code())
.set("toate", _user.data())
)
_log.info(
"Utilizator autentificat:",
_val.map()
.set("id", _user.id())
.set("nume", _user.name())
.set("cod", _user.code())
.set("toate", _user.data())
)
_log.info(
"Utilizator autentificat:",
_val.map()
.set("id", _user.id())
.set("nume", _user.name())
.set("cod", _user.code())
.set("toate", _user.data())
)
_log.info(
"Utilizator autentificat:",
_val.map()
.set("id", _user.id())
.set("nume", _user.name())
.set("cod", _user.code())
.set("toate", _user.data())
)
_log.info(
"Utilizator autentificat:",
_val.map()
.set("id", _user.id())
.set("nume", _user.name())
.set("cod", _user.code())
.set("toate", _user.data())
)
_log.info(
"Utilizator autentificat:",
_val.map()
.set("id", _user.id())
.set("nume", _user.name())
.set("cod", _user.code())
.set("toate", _user.data())
)
Pentru a vedea grupul utilizatorului autentificat folosim resursa Group, de exemplu:
- JavaScript
- TypeScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Grup autentificat:",
_val.map()
.set("id", _group.id())
.set("nume", _group.name())
.set("cod", _group.code())
.set("toate", _group.data())
)
_log.info(
"Grup autentificat:",
_val.map()
.set("id", _group.id())
.set("nume", _group.name())
.set("cod", _group.code())
.set("toate", _group.data())
)
_log.info(
"Grup autentificat:",
_val.map()
.set("id", _group.id())
.set("nume", _group.name())
.set("cod", _group.code())
.set("toate", _group.data())
)
_log.info(
"Grup autentificat:",
_val.map()
.set("id", _group.id())
.set("nume", _group.name())
.set("cod", _group.code())
.set("toate", _group.data())
)
_log.info(
"Grup autentificat:",
_val.map()
.set("id", _group.id())
.set("nume", _group.name())
.set("cod", _group.code())
.set("toate", _group.data())
)
_log.info(
"Grup autentificat:",
_val.map()
.set("id", _group.id())
.set("nume", _group.name())
.set("cod", _group.code())
.set("toate", _group.data())
)
Securitate
Dacă tokenul este invalid sau dacă nu este transmis Authorization, Netuno garantează că serviciul nu
este executat, cu excepția cazului în care serviciul este configurat ca acces public; implicit, serviciile sunt private.
Pentru a gestiona dacă serviciul este public sau privat, configurați în aplicația dvs. la:
server/core/_service_config.js
Deoarece în dezvoltare Netuno permite executarea serviciilor private direct, fără autentificare, din motive de practicitate, deoarece majoritatea serviciilor programate nu au de obicei nevoie de contextul de autentificare, astfel dezvoltarea și testarea devin în general mai simple.
Dacă nu este în dezvoltare, de exemplu în producție, atunci serviciile pot fi executate doar cu autentificare, evident.
Resursa de autentificare
De obicei, resursele _user și _group sunt suficiente, dar există situații în care dorim să realizăm operații mai
avansate, iar în acest caz avem resursa _auth.
Resursa _auth este resursa principală de autentificare, gestionează configurațiile și are toate operațiile
legate de procesul de autentificare.
Pentru a afla mai multe, consultați documentația resursei Auth.
ReAuthKit
Netuno oferă un proiect de bază pentru crearea de platforme cu autentificare online, care include crearea contului, login, profil, avatar, recuperarea parolei și multe altele.
Verificați pe GitHub:
ReAuthKit este o aplicație Netuno cu frontend-ul realizat în React, Ant.Design și React Router, iar baza de date în PostgreSQL.
Cu ReAuthKit se accelerează foarte mult crearea de noi proiecte cu autentificare, deoarece are deja toate mecanismele necesare pentru orice proiect care necesită autentificare.
Concluzie
Într-un mod simplu, este posibil să configurați și să activați autentificarea cu JWT (JSON Web Token) în aplicații.
Utilizați autentificarea cu JWT pentru a dezvolta servicii sigure în API REST.
Integrarea JWT în frontend poate fi utilizată direct cu fetch.
Pentru a accelera și standardiza implementarea este disponibil modulul NPM @netuno/auth-client, care poate fi utilizat cu orice tehnologie de frontend.
Ori de câte ori este necesar să identificați utilizatorul la executarea serviciilor API REST, utilizați autentificarea cu JWT.
În backend, configurarea și obținerea utilizatorului autentificat sunt foarte simple.