@@ -17,9 +17,8 @@ library;
1717
1818import 'dart:convert' ;
1919
20+ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart' ;
2021import 'package:http/http.dart' as http;
21- import 'package:jose/jose.dart'
22- show JoseException, JsonWebKey, JsonWebKeyStore, JsonWebToken;
2322
2423import '../https/error.dart' ;
2524
@@ -48,8 +47,8 @@ class AuthBlockingTokenVerifier {
4847 final bool isEmulator;
4948 final http.Client _httpClient;
5049
51- /// Cached JsonWebKeyStore with Google's public keys.
52- static JsonWebKeyStore ? _cachedKeyStore ;
50+ /// Cached public keys.
51+ static Map < String , JWTKey > ? _cachedKeys ;
5352
5453 /// When the cached keys expire.
5554 static DateTime ? _keysExpireAt;
@@ -75,21 +74,40 @@ class AuthBlockingTokenVerifier {
7574 return _unsafeDecode (token);
7675 }
7776
78- // Get the key store with Google's keys
79- final keyStore = await _getGoogleKeyStore ();
77+ // Decode the token first to get the header
78+ JWT decoded;
79+ try {
80+ decoded = JWT .decode (token);
81+ } catch (e) {
82+ throw UnauthenticatedError ('Invalid JWT format: $e ' );
83+ }
84+
85+ final kid = decoded.header? ['kid' ];
86+ if (kid is ! String ) {
87+ throw UnauthenticatedError (
88+ 'Invalid JWT: missing "kid" header or not String' ,
89+ );
90+ }
91+
92+ // Get the keys from Google
93+ final keys = await _getGoogleKeys ();
94+ final key = keys[kid];
8095
81- // Verify the token using jose
82- JsonWebToken jwt;
96+ if (key == null ) {
97+ throw UnauthenticatedError ('Invalid JWT: unknown "kid"' );
98+ }
99+
100+ // Verify the token using dart_jsonwebtoken
83101 try {
84- jwt = await JsonWebToken . decodeAndVerify (token, keyStore );
85- } on JoseException catch (e) {
102+ JWT . verify (token, key );
103+ } on JWTException catch (e) {
86104 throw UnauthenticatedError ('Invalid JWT: ${e .message }' );
87105 } catch (e) {
88106 throw UnauthenticatedError ('Invalid JWT: $e ' );
89107 }
90108
91109 // Extract the payload as a map
92- final payload = jwt.claims. toJson () ;
110+ final payload = decoded.payload as Map < String , dynamic > ;
93111
94112 // Validate Firebase-specific claims
95113 _validateClaims (payload, audience);
@@ -99,31 +117,24 @@ class AuthBlockingTokenVerifier {
99117
100118 /// Decodes a JWT without verification (for emulator mode only).
101119 Map <String , dynamic > _unsafeDecode (String token) {
102- final parts = token.split ('.' );
103- if (parts.length != 3 ) {
120+ try {
121+ final decoded = JWT .decode (token);
122+ return decoded.payload as Map <String , dynamic >;
123+ } catch (e) {
104124 throw InvalidArgumentError ('Invalid JWT format' );
105125 }
106-
107- final payloadJson = _decodeBase64Url (parts[1 ]);
108- return jsonDecode (payloadJson) as Map <String , dynamic >;
109- }
110-
111- /// Decodes a base64url string to a UTF-8 string.
112- String _decodeBase64Url (String input) {
113- final normalized = base64Url.normalize (input);
114- return utf8.decode (base64Url.decode (normalized));
115126 }
116127
117- /// Fetches Google's public keys and creates a JsonWebKeyStore .
128+ /// Fetches Google's public keys and returns a map of kid to JWTKey .
118129 ///
119130 /// Uses the JWK endpoint which returns keys directly in JSON Web Key format,
120131 /// so no manual certificate parsing is needed.
121- Future <JsonWebKeyStore > _getGoogleKeyStore () async {
122- // Return cached key store if still valid
123- if (_cachedKeyStore != null &&
132+ Future <Map < String , JWTKey >> _getGoogleKeys () async {
133+ // Return cached keys if still valid
134+ if (_cachedKeys != null &&
124135 _keysExpireAt != null &&
125136 DateTime .now ().isBefore (_keysExpireAt! )) {
126- return _cachedKeyStore ! ;
137+ return _cachedKeys ! ;
127138 }
128139
129140 // Fetch keys from Google's JWK endpoint
@@ -137,15 +148,18 @@ class AuthBlockingTokenVerifier {
137148
138149 // Parse the JWK Set response
139150 final jwksJson = jsonDecode (response.body) as Map <String , dynamic >;
140- final keyStore = JsonWebKeyStore () ;
151+ final newKeys = < String , JWTKey > {} ;
141152
142153 // The response contains a "keys" array with JWK objects
143154 final keys = jwksJson['keys' ] as List <dynamic >? ;
144155 if (keys != null ) {
145156 for (final keyJson in keys) {
146157 try {
147- final jwk = JsonWebKey .fromJson (keyJson as Map <String , dynamic >);
148- keyStore.addKey (jwk);
158+ final jwk = keyJson as Map <String , dynamic >;
159+ final kid = jwk['kid' ] as String ? ;
160+ if (kid != null ) {
161+ newKeys[kid] = JWTKey .fromJWK (jwk);
162+ }
149163 } catch (e) {
150164 // Skip keys that fail to parse
151165 continue ;
@@ -164,10 +178,10 @@ class AuthBlockingTokenVerifier {
164178 }
165179 }
166180
167- _cachedKeyStore = keyStore ;
181+ _cachedKeys = newKeys ;
168182 _keysExpireAt = DateTime .now ().add (cacheDuration);
169183
170- return keyStore ;
184+ return newKeys ;
171185 }
172186
173187 /// Validates JWT claims.
@@ -238,7 +252,7 @@ class AuthBlockingTokenVerifier {
238252
239253 /// Clears the key cache (useful for testing).
240254 static void clearCertificateCache () {
241- _cachedKeyStore = null ;
255+ _cachedKeys = null ;
242256 _keysExpireAt = null ;
243257 }
244258}
0 commit comments