diff --git a/packages/jwt/.gitignore b/packages/jwt/.gitignore index 526da158..ac1d378f 100644 --- a/packages/jwt/.gitignore +++ b/packages/jwt/.gitignore @@ -4,4 +4,7 @@ .dart_tool/ .packages build/ -pubspec.lock \ No newline at end of file +pubspec.lock + +# Test related files +coverage/ diff --git a/packages/jwt/lib/src/jwt.dart b/packages/jwt/lib/src/jwt.dart index 5e098858..97b1d88e 100644 --- a/packages/jwt/lib/src/jwt.dart +++ b/packages/jwt/lib/src/jwt.dart @@ -149,9 +149,13 @@ void _verifyPayload(JwtPayload payload, String issuer, String audience) { throw const JwtVerificationFailure('Token issued at a future time.'); } - final authTime = DateTime.fromMillisecondsSinceEpoch(payload.authTime * 1000); - if (authTime.isAfter(now)) { - throw const JwtVerificationFailure('Authenticated at a future time.'); + if (payload.authTime != null) { + final authTime = DateTime.fromMillisecondsSinceEpoch( + payload.authTime! * 1000, + ); + if (authTime.isAfter(now)) { + throw const JwtVerificationFailure('Authenticated at a future time.'); + } } if (payload.aud != audience) { diff --git a/packages/jwt/lib/src/models/jwt_payload.dart b/packages/jwt/lib/src/models/jwt_payload.dart index 469e2050..850226b7 100644 --- a/packages/jwt/lib/src/models/jwt_payload.dart +++ b/packages/jwt/lib/src/models/jwt_payload.dart @@ -14,7 +14,7 @@ class JwtPayload { required this.aud, required this.iss, required this.sub, - required this.authTime, + this.authTime, }); /// Decode a [JwtPayload] from a `Map`. @@ -38,5 +38,5 @@ class JwtPayload { final String sub; /// Time when authentication occurred. - final int authTime; + final int? authTime; } diff --git a/packages/jwt/lib/src/models/jwt_payload.g.dart b/packages/jwt/lib/src/models/jwt_payload.g.dart index b2cc38bf..98dd8ef9 100644 --- a/packages/jwt/lib/src/models/jwt_payload.g.dart +++ b/packages/jwt/lib/src/models/jwt_payload.g.dart @@ -18,7 +18,7 @@ JwtPayload _$JwtPayloadFromJson(Map json) => $checkedCreate( aud: $checkedConvert('aud', (v) => v as String), iss: $checkedConvert('iss', (v) => v as String), sub: $checkedConvert('sub', (v) => v as String), - authTime: $checkedConvert('auth_time', (v) => v as int), + authTime: $checkedConvert('auth_time', (v) => v as int?), ); return val; }, diff --git a/packages/jwt/test/src/jwt_test.dart b/packages/jwt/test/src/jwt_test.dart index 152eaeee..76a96e51 100644 --- a/packages/jwt/test/src/jwt_test.dart +++ b/packages/jwt/test/src/jwt_test.dart @@ -10,6 +10,8 @@ import 'package:test/test.dart'; void main() { const token = '''eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxMGM5MGJhNGMzNjYzNTE2ZTA3MDdkMGU5YTg5NDgxMDYyODUxNTgiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vbXktYXBwIiwiYXVkIjoibXktYXBwIiwiYXV0aF90aW1lIjoxNjQzNjg0MjY2LCJ1c2VyX2lkIjoiRzR1MzdXdk90dmVWR0pRb1pCWGpxcHVWazZWMiIsInN1YiI6Ikc0dTM3V3ZPdHZlVkdKUW9aQlhqcXB1Vms2VjIiLCJpYXQiOjE2NDM2ODQyNjYsImV4cCI6MTY0MzY4Nzg2NiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInRlc3RAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.bUWnX_XmR1d9EmeFeYSsK_CHU1u9NPIHgyaQueZ6urYOtxvuL_QodjPl0c9CBJwctwPnxVyRmkeNCw0oF9xBgph0NApLL4FIG6vpDPZfW9txZBYr8xIvaqvmD0diACENAQdjRT2XmyEdQ2-U7SsTonybHmLoU9FMQTjAgw4NCALQvExfB6rtQ9GDsOBt1xoBkB3Vo7a5OmugZ1aHXF69b8As6137-Dggf5qx5R3oLRFovICMMesQziE3vGi-WKcbQxSeiD-9a6ShPAhk41XiyjFGDEOtUCQo63uwQnMw3g0KVtC6bzIyFq-E91vhxumxXzxPYC-kg7iUYiSZy7Y-Aw'''; + const tokenNoAuthTime = + '''eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxMGM5MGJhNGMzNjYzNTE2ZTA3MDdkMGU5YTg5NDgxMDYyODUxNTgiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vbXktYXBwIiwiYXVkIjoibXktYXBwIiwidXNlcl9pZCI6Ikc0dTM3V3ZPdHZlVkdKUW9aQlhqcXB1Vms2VjIiLCJzdWIiOiJHNHUzN1d2T3R2ZVZHSlFvWkJYanFwdVZrNlYyIiwiaWF0IjoxNjQzNjg0MjY2LCJleHAiOjE2NDM2ODc4NjYsImVtYWlsIjoidGVzdEBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ0ZXN0QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.ZWCadE43mUk43cPQdNCCi4WhDgB4ZsDT9rhPGQq_1uFPhzkVrCSRcjUhwkzH11VLap_MVurNvI_pGWbu9Z4CRPvGFzXPpuNveWy2qFPEa4jcM-R40vsbrP30vNnrp4PrmqgLar0vWs6FZ2g9fbjU8L1LaU5ik31OKSXufTIKn_hPHhyIC33tYTpWzG3Abq3H9EELHUXKW9nEcN8YYnOHAZ3A6ymb3DyBguhf2O-XAIlrn1WoxRRqlukFGSmprk7heonbVUTzoc3sIDZcC-Cj1U9wTee1NmqmU7v3SvpBRGnuXz-5rzSRHblyVxn_EEfCYwjsDUwetYpyFcCs5dqPlQ'''; const publicKeysUrl = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; const issuer = 'https://securetoken.google.com/my-app'; @@ -133,6 +135,25 @@ void main() { expect(jwt, isA()); }); }); + + test('can verify a valid jwt w/out auth_time', () async { + await withClock(Clock.fixed(validTime), () async { + getOverride = (Uri uri) async { + return Response( + body, + HttpStatus.ok, + headers: {'cache-control': 'max-age=3600'}, + ); + }; + final jwt = await verify( + tokenNoAuthTime, + audience: audience, + issuer: issuer, + publicKeysUrl: publicKeysUrl, + ); + expect(jwt, isA()); + }); + }); }); group('base64Padded', () {