JWT – JSON Web Token

JWT е технология (или по-точно open standard – RFC 7519), с която сигурно да се пренасят данни в Интернет, които са форматирани като JSON обект и криптирани по определен начин.

Едно практическо приложение би било, ако например искаме да се Аутентикираме и след това съответно – Ауторизираме, без да използваме стандартният начин (със session cookies), a като при всеки request изпращаме гореспоменатият JSON, който съдържа всичко необходимо за Authorisation.

Нищо не се запазва на сърва.

При успешна Authentication сървът изпраща на клиента този JWT, криптиран с някакъв secret key, с което все едно му казва „Разбрах кой си, но не сте един или два, затова всеки път, при request от теб, пращай ми и този JWT за да преизчисля на ново signature на базата на енкоднатите header и payload, и да ги сравня дали са енакви.

Сървът, като получи Authentication JSON-a с например user и password, проверява ги и ако всичко е ОК, връща на клиента т.н JWT (JSON Web Token)

Важно е да се знае, че самата идея е JWT да се използва само след Authentication. Authentication служи само да се установи дали даденият потребител съществува и да се върне даденият JWT.
JWT се използва за Authorization в последващите рекуести.

Веднъж щом клиентът получи JWT той трябва някак да го запази при себе си, защото ще му трябва.

Голям недостатък е, че ако някой ти открадне JWT-то, може да действа от твое име.

Структура:
header.payload.signature

И трите са JSON-и (header, payload и signature), кодирани с някой от трите алгоритъма (HMAC, SHA256 или RSA) и конкатенирани с „.“

Header
Хедърът се състои от „alg“ и „typ“,
alg е алгоритъмът на подписване, например HMAC, SHA256 или RSA,
typ е „JWT“.
Т.е. хедърът изглежда най-често така:

{
    "alg": "HS256",
    "typ": "JWT"
}

След което бива Base64Url енкоднат.

Payload
„Полезният товар“ се състои от т.н. твърдения (claims), които биват три вида:
registered
public
private

Но може да има и много други полета. По принцип, в пейлодът сърва може да си сложи само някакво ID на потребителя, по което после по-лесно да го установи, когато получи обратно този JWT и го декодне. Може и поле с някакъв таймстамп, да покава до кога ще смята комуникацията за неизтекла и т.н…

Примерен payload би бил:

{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
}

Също бива Base64Url енкоднат.

Signature
За да получим signature ни трябват хедъра и пейлоуда.
Тези двете се конкатенират с „.“ и полученият стринг се хешира с HMACSHA256 като се използва даден secret ключ, който е на сърва.
В PHP има функция hash_hmac()

Putting all together
След като имаме вече и трите части, конкатенираме ги с „.“ и това ни е JWT токенът, който можем да върнем на клиента.
При всеки последващ request от страна на клиента, той връща въпросният JWT чрез хедърът Authorization: Bearer <token>
Тогава сървът разбива въпросният JWT токен на трите части, с първите две (header и payload) изчислява отново signature и го сравнява с третата част.
Ако подаденият от клента signature съвпада с преизчисленият, всичко е ОК

Къде JWT може да има практическо приложение?

Имаш примерно акаунт на даден сърв. Логваш се с потр. име и парола (Authentication).
При успешен логин, сърва ти връща JWT токен, който съдържа информация за теб. Нищо не се пази на сървъра както при сесиите.
Клиентът (ти) пазиш този JWT при теб.
Всеки път като изпрати рекуест към сърва, изпращаш и този JWT токен. Сървът го проверява като пак изчислява хеша от хедъра и пейлоуда (използвайки сикрета за ключ) за да е сигурен, че не е променен. Ако е ОК, base64 декодва пейлоуда и вижда кой си.

Естествено, съществено важно е сигурно да пазиш този JWT при теб.

Няма как някой да си създаде JWT с твоите данни и да излъже сърва, че си ти, освен ако няма този сикрет, с който е хешнато signature-то.

Защо се казва, че JWT се използва за ауторизация, а не за аутентикация?
Защото флоуът е следният:
1. потребителят се логва на сърва
2. сърва изпраща към клиента JWT токен с инфомация за потребителя
3. при всяка следваща заявка от клиента, се изпраща и JWT-то за да знае сърва за кой потребител иде реч.
Демек, идеята е JWT да се използва в последствие, а не само първоначално.

Примерен код за генериране на JWT:

define('SECRET_KEY', 'alabalaportokala');

function base64UrlEncode(string $str): string
{
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
}

function generateJwt(array $headers, array $payload, string $secret): string
{
$headers_encoded = base64UrlEncode(json_encode($headers, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
$payload_encoded = base64UrlEncode(json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));

$signature = hash_hmac('SHA256',
$headers_encoded . '.' . $payload_encoded,
$secret,
true
);
$signature_encoded = base64UrlEncode($signature);

$jwt = $headers_encoded . '.' . $payload_encoded . '.' . $signature_encoded;

return $jwt;
}

$headers = array('alg' => 'HS256', 'typ' => 'JWT');
$payload = array('sub' => '1234567890', 'name' => 'John Doe', 'iat' => 1516239022);

echo $jwt = generateJwt($headers, $payload, SECRET_KEY);

echo PHP_EOL . PHP_EOL;

function verifyJwtToken(string $jwt): string
{
$header_encoded = explode('.', $jwt)[0];
$payload_encoded = explode('.', $jwt)[1];

$signature_encoded = hash_hmac('SHA256',
$header_encoded . '.' . $payload_encoded,
SECRET_KEY,
true
);

return (base64UrlEncode($signature_encoded) === explode('.', $jwt)[2])
? base64_decode($payload_encoded)
: '';
}

var_dump(verifyJwtToken($jwt));

Литература

https://jwt.io/introduction

https://cryptii.com/

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *