JWT (JSON Web Token) — компактный стандарт передачи данных между сторонами в виде JSON-объекта, подписанного или зашифрованного. В практической разработке чаще всего используется вариант с подписью, позволяющий проверить подлинность и целостность данных без хранения сессии на сервере.
В JavaScript для работы с JWT одной из наиболее распространённых библиотек является Jsrsasign. Она предоставляет инструменты для создания, подписи, проверки и декодирования JWT, а также поддержку различных криптографических алгоритмов (HS256, RS256, ES256 и др.).
Библиотека позволяет:
В Node.js среде:
npm install jsrsasign
Использование:
const jsrsasign = require("jsrsasign");
В браузере:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.8.6/jsrsasign-all-min.js"></script>
JWT состоит из трёх частей:
Формат:
xxxxx.yyyyy.zzzzz
Jsrsasign автоматически формирует все три части при создании токена.
HS256 использует общий секретный ключ для подписи и проверки.
const header = { alg: "HS256", typ: "JWT" };
const payload = {
sub: "user123",
role: "admin",
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
};
const secret = "super_secret_key";
const token = jsrsasign.KJUR.jws.JWS.sign(
"HS256",
JSON.stringify(header),
JSON.stringify(payload),
secret
);
console.log(token);
const isValid = jsrsasign.KJUR.jws.JWS.verifyJWT(token, secret, {
alg: ["HS256"]
});
console.log(isValid);
const parsed = jsrsasign.KJUR.jws.JWS.parse(token);
console.log(parsed.payloadObj);
Этот способ полезен только для отладки, поскольку не гарантирует подлинность данных.
function generateToken(user) {
const header = { alg: "HS256", typ: "JWT" };
const payload = {
sub: user.id,
email: user.email,
role: user.role,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 7200
};
return jsrsasign.KJUR.jws.JWS.sign(
"HS256",
JSON.stringify(header),
JSON.stringify(payload),
process.env.JWT_SECRET
);
}
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) {
res.status(401).send("No token provided");
return;
}
const token = authHeader.split(" ")[1];
const isValid = jsrsasign.KJUR.jws.JWS.verifyJWT(
token,
process.env.JWT_SECRET,
{ alg: ["HS256"] }
);
if (!isValid) {
res.status(401).send("Invalid token");
return;
}
const decoded = jsrsasign.KJUR.jws.JWS.parse(token);
req.user = decoded.payloadObj;
next();
}
RS256 использует пару ключей: приватный (подпись) и публичный (проверка). Это более безопасный вариант для распределённых систем.
const privateKey = `-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----`;
const header = { alg: "RS256", typ: "JWT" };
const payload = {
sub: "user123",
role: "editor",
exp: Math.floor(Date.now() / 1000) + 3600
};
const token = jsrsasign.KJUR.jws.JWS.sign(
"RS256",
JSON.stringify(header),
JSON.stringify(payload),
privateKey
);
const publicKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`;
const isValid = jsrsasign.KJUR.jws.JWS.verifyJWT(
token,
publicKey,
{ alg: ["RS256"] }
);
Ключевые поля:
iat — время выпускаexp — срок истеченияnbf — не активен до указанного времениПример проверки срока действия:
const decoded = jsrsasign.KJUR.jws.JWS.parse(token);
const exp = decoded.payloadObj.exp;
const now = Math.floor(Date.now() / 1000);
if (now > exp) {
console.log("Token expired");
}
Обычно:
fetch("/api/profile", {
headers: {
Authorization: "Bearer " + token
}
});
try {
jsrsasign.KJUR.jws.JWS.verifyJWT(token, secret, { alg: ["HS256"] });
} catch (e) {
console.log("Invalid signature");
}
try {
jsrsasign.KJUR.jws.JWS.parse(token);
} catch (e) {
console.log("Malformed token");
}
Ключевые аспекты:
expfunction authorizeRole(role) {
return function (req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
const decoded = jsrsasign.KJUR.jws.JWS.parse(token);
if (decoded.payloadObj.role !== role) {
res.status(403).send("Forbidden");
return;
}
next();
};
}
JWT обычно не обновляется напрямую. Используется пара:
Логика:
const refreshToken = jsrsasign.KJUR.jws.JWS.sign(
"HS256",
JSON.stringify({ alg: "HS256", typ: "JWT" }),
JSON.stringify({
sub: user.id,
type: "refresh",
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30
}),
secret
);
const decoded = jsrsasign.KJUR.jws.JWS.parse(token);
if (decoded.payloadObj.type !== "refresh") {
throw new Error("Invalid token type");
}
async function login() {
const res = await fetch("/login", {
method: "POST",
body: JSON.stringify({ email, password })
});
const data = await res.json();
localStorage.setItem("token", data.token);
}
async function getProfile() {
const token = localStorage.getItem("token");
const res = await fetch("/profile", {
headers: {
Authorization: "Bearer " + token
}
});
return await res.json();
}