JWT Decode Dart
ライブラリ
JWT Decode Dart
概要
JWT Decode Dartは、Dartアプリケーションで軽量なJSON Web Token(JWT)デコード機能を提供するライブラリです。
詳細
jwt_decodeは、Flutter/Dartアプリケーションでの認証トークン処理を簡素化するために設計された軽量ライブラリです。主要な機能として、JWTトークンのデコード、有効期限の確認、クレーム情報の抽出を提供します。このライブラリはトークンの検証は行わず、デコードに特化しているため、セキュリティクリティカルな用途では追加の検証手順が必要です。pub.devで公開されており、Dart 3に対応し、iOS、Android、Web、デスクトップの全プラットフォームで動作します。jwt_decoderという代替ライブラリも存在し、より多くのダウンロード実績を持ちますが、どちらも同様の機能を提供します。
メリット・デメリット
メリット
- 軽量設計: 最小限の依存関係で高速なJWTデコード処理
- クロスプラットフォーム: iOS、Android、Web、デスクトップ全対応
- シンプルAPI: 直感的なメソッドで簡単にJWTデータを取得
- 有効期限チェック: トークンの期限切れを自動判定
- クレーム抽出: ペイロード内の任意のクレーム情報にアクセス
- Flutter統合: Flutterアプリケーションとの親和性が高い
デメリット
- 検証機能なし: トークンの署名検証は別途実装が必要
- セキュリティ限定: デコードのみでセキュリティ機能は最小限
- エラーハンドリング: 不正なトークンでの例外処理が必要
- ドキュメント: 機能が限定的でドキュメントも最小限
- 依存ライブラリ: 別途JWT検証ライブラリとの併用が必要
主要リンク
書き方の例
基本的なJWTデコード
import 'package:jwt_decode/jwt_decode.dart';
void main() {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
// JWTをデコード
Map<String, dynamic> payload = Jwt.parseJwt(token);
print('User ID: ${payload['sub']}');
print('Email: ${payload['email']}');
print('Role: ${payload['role']}');
}
有効期限の確認
import 'package:jwt_decode/jwt_decode.dart';
bool isTokenValid(String token) {
try {
// 有効期限をチェック
bool isExpired = Jwt.isExpired(token);
return !isExpired;
} catch (e) {
print('Invalid token: $e');
return false;
}
}
void checkToken() {
String token = "your-jwt-token-here";
if (isTokenValid(token)) {
print('Token is valid');
Map<String, dynamic> payload = Jwt.parseJwt(token);
print('User: ${payload['name']}');
} else {
print('Token is expired or invalid');
}
}
Flutter認証クラスでの使用
import 'package:jwt_decode/jwt_decode.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
static const String _tokenKey = 'jwt_token';
// トークンを保存
Future<void> saveToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_tokenKey, token);
}
// 保存されたトークンを取得・検証
Future<Map<String, dynamic>?> getCurrentUser() async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString(_tokenKey);
if (token == null) return null;
try {
// 有効期限をチェック
if (Jwt.isExpired(token)) {
await logout(); // 期限切れの場合はログアウト
return null;
}
// ユーザー情報を取得
final payload = Jwt.parseJwt(token);
return {
'id': payload['sub'],
'email': payload['email'],
'name': payload['name'],
'role': payload['role'],
};
} catch (e) {
print('Token parsing error: $e');
await logout();
return null;
}
}
// ログアウト
Future<void> logout() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_tokenKey);
}
// 管理者権限チェック
Future<bool> isAdmin() async {
final user = await getCurrentUser();
return user?['role'] == 'admin';
}
}
エラーハンドリングとバリデーション
import 'package:jwt_decode/jwt_decode.dart';
class JwtUtil {
static Map<String, dynamic>? safeParseJwt(String token) {
try {
return Jwt.parseJwt(token);
} catch (e) {
print('JWT parsing failed: $e');
return null;
}
}
static bool isValidJwt(String token) {
if (token.isEmpty) return false;
// JWTの基本形式チェック(3つの部分がドットで区切られている)
final parts = token.split('.');
if (parts.length != 3) return false;
try {
// デコード可能かチェック
Jwt.parseJwt(token);
return true;
} catch (e) {
return false;
}
}
static DateTime? getExpirationDate(String token) {
try {
final payload = Jwt.parseJwt(token);
final exp = payload['exp'] as int?;
if (exp != null) {
return DateTime.fromMillisecondsSinceEpoch(exp * 1000);
}
} catch (e) {
print('Error getting expiration date: $e');
}
return null;
}
static Duration? getTimeUntilExpiration(String token) {
final expDate = getExpirationDate(token);
if (expDate != null) {
return expDate.difference(DateTime.now());
}
return null;
}
}
権限ベースのルーティング(Flutter)
import 'package:flutter/material.dart';
import 'package:jwt_decode/jwt_decode.dart';
class AuthGuard extends StatelessWidget {
final Widget child;
final String? requiredRole;
final String? token;
final Widget fallback;
const AuthGuard({
Key? key,
required this.child,
this.requiredRole,
this.token,
this.fallback = const Text('Access Denied'),
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (token == null || token!.isEmpty) {
return fallback;
}
try {
// トークンの有効期限チェック
if (Jwt.isExpired(token!)) {
return const Text('Token Expired');
}
// 必要な権限のチェック
if (requiredRole != null) {
final payload = Jwt.parseJwt(token!);
final userRole = payload['role'] as String?;
if (userRole != requiredRole) {
return fallback;
}
}
return child;
} catch (e) {
return Text('Authentication Error: $e');
}
}
}
// 使用例
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AuthGuard(
token: "your-jwt-token",
requiredRole: "admin",
child: AdminDashboard(),
fallback: LoginScreen(),
),
);
}
}
リアルタイムトークン監視
import 'dart:async';
import 'package:jwt_decode/jwt_decode.dart';
class TokenMonitor {
Timer? _expirationTimer;
final Function() onTokenExpired;
TokenMonitor({required this.onTokenExpired});
void startMonitoring(String token) {
stopMonitoring();
try {
final payload = Jwt.parseJwt(token);
final exp = payload['exp'] as int?;
if (exp != null) {
final expirationDate = DateTime.fromMillisecondsSinceEpoch(exp * 1000);
final timeUntilExpiration = expirationDate.difference(DateTime.now());
if (timeUntilExpiration.isNegative) {
// 既に期限切れ
onTokenExpired();
} else {
// 期限切れ直前にコールバックを実行
_expirationTimer = Timer(timeUntilExpiration, onTokenExpired);
}
}
} catch (e) {
print('Error monitoring token: $e');
onTokenExpired();
}
}
void stopMonitoring() {
_expirationTimer?.cancel();
_expirationTimer = null;
}
void dispose() {
stopMonitoring();
}
}