Skip to content

Commit 0f1a053

Browse files
committed
fix: use dart_jsonwebtoken for verifying jwt
1 parent 892874c commit 0f1a053

File tree

3 files changed

+29
-126
lines changed

3 files changed

+29
-126
lines changed

packages/gotrue/lib/src/gotrue_client.dart

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import 'dart:async';
22
import 'dart:convert';
33
import 'dart:math';
4-
import 'dart:typed_data';
54

65
import 'package:collection/collection.dart';
6+
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
77
import 'package:gotrue/gotrue.dart';
88
import 'package:gotrue/src/constants.dart';
99
import 'package:gotrue/src/fetch.dart';
@@ -14,7 +14,6 @@ import 'package:http/http.dart';
1414
import 'package:jwt_decode/jwt_decode.dart';
1515
import 'package:logging/logging.dart';
1616
import 'package:meta/meta.dart';
17-
import 'package:pointycastle/export.dart';
1817
import 'package:retry/retry.dart';
1918
import 'package:rxdart/subjects.dart';
2019

@@ -1342,17 +1341,19 @@ class GoTrueClient {
13421341
return exception;
13431342
}
13441343

1345-
Future<JWK?> _fetchJwk(String kid, JWKSet suppliedJwks) async {
1344+
Future<JWTKey?> _fetchJwk(String kid, JWKSet suppliedJwks) async {
13461345
// try fetching from the supplied jwks
1347-
final jwk = suppliedJwks.keys.firstWhereOrNull((jwk) => jwk.kid == kid);
1346+
final jwk = suppliedJwks.keys
1347+
.firstWhereOrNull((jwk) => jwk.toJWK(keyID: kid)['kid'] == kid);
13481348
if (jwk != null) {
13491349
return jwk;
13501350
}
13511351

13521352
final now = DateTime.now();
13531353

13541354
// try fetching from cache
1355-
final cachedJwk = _jwks?.keys.firstWhereOrNull((jwk) => jwk.kid == kid);
1355+
final cachedJwk = _jwks?.keys
1356+
.firstWhereOrNull((jwk) => jwk.toJWK(keyID: kid)['kid'] == kid);
13561357

13571358
// jwks exists and it isn't stale
13581359
if (cachedJwk != null &&
@@ -1378,7 +1379,8 @@ class GoTrueClient {
13781379
_jwksCachedAt = now;
13791380

13801381
// find the signing key
1381-
return jwks.keys.firstWhereOrNull((jwk) => jwk.kid == kid);
1382+
return jwks.keys
1383+
.firstWhereOrNull((jwk) => jwk.toJWK(keyID: kid)['kid'] == kid);
13821384
}
13831385

13841386
/// Extracts the JWT claims present in the access token by first verifying the
@@ -1431,26 +1433,14 @@ class GoTrueClient {
14311433
signature: decoded.signature);
14321434
}
14331435

1434-
final publicKey = RSAPublicKey(signingKey['n'], signingKey['e']);
1435-
final signer = RSASigner(SHA256Digest(), '0609608648016503040201'); // PKCS1
1436-
1437-
// initialize with false, which means verify
1438-
signer.init(false, PublicKeyParameter<RSAPublicKey>(publicKey));
1439-
1440-
final signature = RSASignature(Uint8List.fromList(decoded.signature));
1441-
final isValidSignature = signer.verifySignature(
1442-
Uint8List.fromList(
1443-
utf8.encode('${decoded.raw.header}.${decoded.raw.payload}')),
1444-
signature,
1445-
);
1446-
1447-
if (!isValidSignature) {
1448-
throw AuthInvalidJwtException('Invalid JWT signature');
1436+
try {
1437+
JWT.verify(token, signingKey);
1438+
return GetClaimsResponse(
1439+
claims: decoded.payload,
1440+
header: decoded.header,
1441+
signature: decoded.signature);
1442+
} catch (e) {
1443+
throw AuthInvalidJwtException('Invalid JWT signature: $e');
14491444
}
1450-
1451-
return GetClaimsResponse(
1452-
claims: decoded.payload,
1453-
header: decoded.header,
1454-
signature: decoded.signature);
14551445
}
14561446
}

packages/gotrue/lib/src/types/jwt.dart

Lines changed: 5 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
2+
13
/// JWT Header structure
24
class JwtHeader {
35
/// Algorithm used to sign the JWT (e.g., 'RS256', 'ES256', 'HS256')
@@ -157,109 +159,21 @@ class GetClaimsOptions {
157159
}
158160

159161
class JWKSet {
160-
final List<JWK> keys;
162+
final List<JWTKey> keys;
161163

162164
JWKSet({required this.keys});
163165

164166
factory JWKSet.fromJson(Map<String, dynamic> json) {
165167
final keys = (json['keys'] as List<dynamic>?)
166-
?.map((e) => JWK.fromJson(e as Map<String, dynamic>))
168+
?.map((e) => JWTKey.fromJWK(e as Map<String, dynamic>))
167169
.toList() ??
168170
[];
169171
return JWKSet(keys: keys);
170172
}
171173

172174
Map<String, dynamic> toJson() {
173175
return {
174-
'keys': keys.map((e) => e.toJson()).toList(),
175-
};
176-
}
177-
}
178-
179-
/// {@template jwk}
180-
/// JSON Web Key (JWK) representation.
181-
/// {@endtemplate}
182-
class JWK {
183-
/// The "kty" (key type) parameter identifies the cryptographic algorithm
184-
/// family used with the key, such as "RSA" or "EC".
185-
final String kty;
186-
187-
/// The "key_ops" (key operations) parameter identifies the cryptographic
188-
/// operations for which the key is intended to be used.
189-
final List<String> keyOps;
190-
191-
/// The "alg" (algorithm) parameter identifies the algorithm intended for
192-
/// use with the key.
193-
final String? alg;
194-
195-
/// The "kid" (key ID) parameter is used to match a specific key.
196-
final String? kid;
197-
198-
/// Additional arbitrary properties of the JWK.
199-
final Map<String, dynamic> _additionalProperties;
200-
201-
/// {@macro jwk}
202-
JWK({
203-
required this.kty,
204-
required this.keyOps,
205-
this.alg,
206-
this.kid,
207-
Map<String, dynamic>? additionalProperties,
208-
}) : _additionalProperties = additionalProperties ?? {};
209-
210-
/// Creates a [JWK] from a JSON map.
211-
factory JWK.fromJson(Map<String, dynamic> json) {
212-
final kty = json['kty'] as String;
213-
final keyOps =
214-
(json['key_ops'] as List<dynamic>?)?.map((e) => e as String).toList() ??
215-
[];
216-
final alg = json['alg'] as String?;
217-
final kid = json['kid'] as String?;
218-
219-
final Map<String, dynamic> additionalProperties = Map.from(json);
220-
additionalProperties.remove('kty');
221-
additionalProperties.remove('key_ops');
222-
additionalProperties.remove('alg');
223-
additionalProperties.remove('kid');
224-
225-
return JWK(
226-
kty: kty,
227-
keyOps: keyOps,
228-
alg: alg,
229-
kid: kid,
230-
additionalProperties: additionalProperties,
231-
);
232-
}
233-
234-
/// Allows accessing additional properties using operator[].
235-
dynamic operator [](String key) {
236-
switch (key) {
237-
case 'kty':
238-
return kty;
239-
case 'key_ops':
240-
return keyOps;
241-
case 'alg':
242-
return alg;
243-
case 'kid':
244-
return kid;
245-
default:
246-
return _additionalProperties[key];
247-
}
248-
}
249-
250-
/// Converts this [JWK] to a JSON map.
251-
Map<String, dynamic> toJson() {
252-
final Map<String, dynamic> json = {
253-
'kty': kty,
254-
'key_ops': keyOps,
255-
..._additionalProperties,
176+
'keys': keys.map((e) => e.toJWK()).toList(),
256177
};
257-
if (alg != null) {
258-
json['alg'] = alg;
259-
}
260-
if (kid != null) {
261-
json['kid'] = kid;
262-
}
263-
return json;
264178
}
265179
}

packages/gotrue/pubspec.yaml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
name: gotrue
22
description: A dart client library for the GoTrue API.
33
version: 2.15.0
4-
homepage: 'https://supabase.com'
5-
repository: 'https://github.com/supabase/supabase-flutter/tree/main/packages/gotrue'
6-
documentation: 'https://supabase.com/docs/reference/dart/auth-signup'
4+
homepage: "https://supabase.com"
5+
repository: "https://github.com/supabase/supabase-flutter/tree/main/packages/gotrue"
6+
documentation: "https://supabase.com/docs/reference/dart/auth-signup"
77

88
environment:
9-
sdk: '>=3.3.0 <4.0.0'
9+
sdk: ">=3.3.0 <4.0.0"
1010

1111
dependencies:
1212
collection: ^1.15.0
1313
crypto: ^3.0.2
14-
http: '>=0.13.0 <2.0.0'
14+
http: ">=0.13.0 <2.0.0"
1515
jwt_decode: ^0.3.1
1616
retry: ^3.1.0
17-
rxdart: '>=0.27.7 <0.29.0'
17+
rxdart: ">=0.27.7 <0.29.0"
1818
meta: ^1.7.0
1919
logging: ^1.2.0
20-
web: '>=0.5.0 <2.0.0'
21-
pointycastle: ^4.0.0
20+
web: ">=0.5.0 <2.0.0"
21+
dart_jsonwebtoken: ^3.3.0
2222

2323
dev_dependencies:
24-
dart_jsonwebtoken: ^3.3.0
2524
dotenv: ^4.1.0
2625
lints: ^3.0.0
2726
test: ^1.16.4

0 commit comments

Comments
 (0)