В прошлом посте мы с вами разобрались, что такое JWT и зачем он нужен. А сегодня поговорим о том, как же нам реализовать авторизацию при помощи JWT во Flutter.
Для этого нам понадобится библиотека dart_jsonwebtoken, которая позволит нам создавать и верифицировать токены, а также библиотека для безопасного хранения данных на устройстве flutter_secure_storage.
✔️Серверная часть
Создание и подпись токена
Эта логика находится на сервере, который генерирует токен после успешной аутентификации пользователя.
Напомню, JWT состоит из трех частей: header, payload, signature. В коде это можно описать так (создание и подпись):
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:uuid/uuid.dart';
// Создаем полезную нагрузку (payload) для токена
final payload = {
// jti — уникальный идентификатор токена, необходимый для Blacklist
'jti': const Uuid().v4(),
'userId': 123,
'role': 'user',
};
// Создаем экземпляр JWT
final jwt = JWT(payload);
// Подписываем токен с помощью секретного ключа
// Секретный ключ должен храниться только на сервере и никогда не передаваться на клиент!
// expiresIn задает срок действия токена в секундах
final token = jwt.sign(
SecretKey('ВАШ_СЕКРЕТНЫЙ_КЛЮЧ'),
expiresIn: const Duration(minutes: 15),
);
Библиотека dart_jsonwebtoken автоматически добавляет header и signature, а также поле exp (срок действия) на основе expiresIn.
Также возможно создание ключей по другим алгоритмам (RSA SHA-256, ECDSA P-256, ECDSA secp256k и другим). Для этого необходимо прокинуть key с нужным алгоритмом в sign.
Например:
// Читаем приватный ключ из файла
final pem = File('./rsa_private.pem').readAsStringSync();
final key = RSAPrivateKey(pem);
// Подписываем токен с указанием алгоритма
final token = jwt.sign(key, algorithm: JWTAlgorithm.RS256);
Проверка токена (верификация)
Чтобы убедиться, что токен подлинный и не был изменен, его нужно верифицировать с тем же секретным ключом.
try {
// Верифицируем токен с помощью секретного ключа
final jwt = JWT.verify(token, SecretKey('SECRET_KEY'));
// Если верификация успешна, можно получить полезную нагрузку
print('Payload: ${jwt.payload}');
} on JWTExpiredException {
// Срок действия токена истёк
print('Token expired');
} on JWTInvalidException {
// Токен недействителен (изменён, некорректная подпись)
print('Token is invalid');
}dart_jsonwebtoken автоматически проверяет срок действия токена и выбрасывает исключение, если он истек.
Отзыв токена: Refresh Token и Blacklist
Основная проблема JWT: даже если пользователь вышел из системы, токен остается валидным до истечения срока действия. Чтобы решить эту проблему, используются короткоживущие Access Token и долгоживущие Refresh Token, а также черный список (Blacklist). Подробнее об этом было в прошлом посте.
Реализовать blacklist вы можете примерно таким образом:
Добавляете таблицу blacklisted_tokens для хранения jti (JWT ID). Далее при генерации токена задаете уникальный ID, например, с помощью пакета uuid
import 'package:uuid/uuid.dart';
final uuid = Uuid();
final payload = {
…,
'jti': uuid.v4(), // Уникальный ID токена
};
А при проверке учитываете blacklist:
bool valid(String secretKey, {Set<String> blackList = const {}}) {
final jwt = JWT.verify(token, SecretKey('SECRET_KEY'));
final jti = jwt.payload['jti'] as String?;
final notBlacklisted = !(jti != null && blackList.contains(jti));
return notBlacklisted;
}Клиентская часть в комментариях👇
